tstyche 2.1.1 → 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/README.md CHANGED
@@ -2,10 +2,8 @@
2
2
 
3
3
  [![version][version-badge]][version-url]
4
4
  [![license][license-badge]][license-url]
5
- [![requirements][requirements-badge]][requirements-url]
6
5
  [![install-size][install-size-badge]][install-size-url]
7
6
  [![coverage][coverage-badge]][coverage-url]
8
- [![discord][discord-badge]][discord-url]
9
7
 
10
8
  The Essential Type Testing Tool.
11
9
 
@@ -80,7 +78,7 @@ Visit [https://tstyche.org](https://tstyche.org) to view the full documentation.
80
78
 
81
79
  ## Feedback
82
80
 
83
- If you have any questions or suggestions, [start a discussion](https://github.com/tstyche/tstyche/discussions/new/choose) or [open an issue](https://github.com/tstyche/tstyche/issues/new/choose) on GitHub. Preferring a chat? Join our [Discord server][discord-url].
81
+ If you have any questions or suggestions, [start a discussion](https://github.com/tstyche/tstyche/discussions/new/choose) or [open an issue](https://github.com/tstyche/tstyche/issues/new/choose) on GitHub. Preferring a chat? Join our [Discord server](https://discord.gg/gCSasd3QJq).
84
82
 
85
83
  ## License
86
84
 
@@ -90,11 +88,7 @@ If you have any questions or suggestions, [start a discussion](https://github.co
90
88
  [version-url]: https://npmjs.com/package/tstyche
91
89
  [license-badge]: https://badgen.net/github/license/tstyche/tstyche
92
90
  [license-url]: https://github.com/tstyche/tstyche/blob/main/LICENSE.md
93
- [requirements-badge]: https://badgen.net/npm/node/tstyche
94
- [requirements-url]: https://tstyche.org/reference/requirements
95
91
  [install-size-badge]: https://badgen.net/packagephobia/install/tstyche
96
92
  [install-size-url]: https://packagephobia.com/result?p=tstyche
97
93
  [coverage-badge]: https://badgen.net/codacy/coverage/a581ca5c323a455886b7bdd9623c4ec8
98
94
  [coverage-url]: https://app.codacy.com/gh/tstyche/tstyche/coverage/dashboard
99
- [discord-badge]: https://badgen.net/static/chat/on%20Discord
100
- [discord-url]: https://discord.gg/gCSasd3QJq
@@ -1,26 +1,11 @@
1
1
  import type ts from 'typescript';
2
2
 
3
- declare enum CancellationReason {
4
- ConfigChange = "configChange",
5
- ConfigError = "configError",
6
- FailFast = "failFast",
7
- WatchClose = "watchClose"
8
- }
9
-
10
- declare class CancellationToken {
11
- #private;
12
- get isCancellationRequested(): boolean;
13
- get reason(): CancellationReason | undefined;
14
- cancel(reason: CancellationReason): void;
15
- reset(): void;
16
- }
17
-
18
3
  declare class StoreService {
19
4
  #private;
20
5
  constructor();
21
6
  getSupportedTags(): Promise<Array<string>>;
22
- install(tag: string, cancellationToken?: CancellationToken): Promise<string | undefined>;
23
- load(tag: string, cancellationToken?: CancellationToken): Promise<typeof ts | undefined>;
7
+ install(tag: string): Promise<string | undefined>;
8
+ load(tag: string): Promise<typeof ts | undefined>;
24
9
  open(): Promise<void>;
25
10
  update(): Promise<void>;
26
11
  validateTag(tag: string): Promise<boolean | undefined>;
@@ -147,7 +132,6 @@ declare class Diagnostic {
147
132
  static error(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
148
133
  extendWith(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
149
134
  static fromDiagnostics(diagnostics: Array<ts.Diagnostic>, compiler: typeof ts): Array<Diagnostic>;
150
- static fromError(text: string | Array<string>, error: unknown): Diagnostic;
151
135
  static warning(text: string | Array<string>, origin?: DiagnosticOrigin): Diagnostic;
152
136
  }
153
137
 
@@ -320,7 +304,7 @@ interface TextProps {
320
304
  }
321
305
  declare function Text({ children, color, indent }: TextProps): ScribblerJsx.Element;
322
306
 
323
- declare function addsPackageStepText(compilerVersion: string, installationPath: string): ScribblerJsx.Element;
307
+ declare function addsPackageStepText(packageVersion: string, packagePath: string): ScribblerJsx.Element;
324
308
 
325
309
  declare function describeNameText(name: string, indent?: number): ScribblerJsx.Element;
326
310
 
@@ -485,6 +469,21 @@ declare class SelectService {
485
469
  selectFiles(): Promise<Array<string>>;
486
470
  }
487
471
 
472
+ declare enum CancellationReason {
473
+ ConfigChange = "configChange",
474
+ ConfigError = "configError",
475
+ FailFast = "failFast",
476
+ WatchClose = "watchClose"
477
+ }
478
+
479
+ declare class CancellationToken {
480
+ #private;
481
+ get isCancellationRequested(): boolean;
482
+ get reason(): CancellationReason | undefined;
483
+ cancel(reason: CancellationReason): void;
484
+ reset(): void;
485
+ }
486
+
488
487
  declare class TSTyche {
489
488
  #private;
490
489
  static version: string;
@@ -512,6 +511,10 @@ declare class Environment {
512
511
  * Specifies whether interactive elements should be disabled in the output.
513
512
  */
514
513
  static get noInteractive(): boolean;
514
+ /**
515
+ * The base URL of the 'npm' registry to use.
516
+ */
517
+ static get npmRegistry(): string;
515
518
  /**
516
519
  * The directory where to store the 'typescript' packages.
517
520
  */
@@ -537,8 +540,8 @@ type Event = ["config:error", {
537
540
  }] | ["run:end", {
538
541
  result: Result;
539
542
  }] | ["store:info", {
540
- compilerVersion: string;
541
- installationPath: string;
543
+ packagePath: string;
544
+ packageVersion: string;
542
545
  }] | ["store:error", {
543
546
  diagnostics: Array<Diagnostic>;
544
547
  }] | ["target:start", {
@@ -656,7 +659,6 @@ declare class ExpectDiagnosticText {
656
659
  static argumentMustBeProvided(argumentNameText: string): string;
657
660
  static componentAcceptsProps(isTypeNode: boolean): string;
658
661
  static componentDoesNotAcceptProps(isTypeNode: boolean): string;
659
- static matcherIsDeprecated(matcherNameText: string): Array<string>;
660
662
  static matcherIsNotSupported(matcherNameText: string): string;
661
663
  static overloadGaveTheFollowingError(index: number, count: number, signatureText: string): string;
662
664
  static raisedTypeError(count?: number): string;
@@ -729,7 +731,6 @@ declare class ExpectService {
729
731
  toAcceptProps: ToAcceptProps;
730
732
  toBe: ToBe;
731
733
  toBeAny: PrimitiveTypeMatcher;
732
- toBeAssignable: ToBeAssignableWith;
733
734
  toBeAssignableTo: ToBeAssignableTo;
734
735
  toBeAssignableWith: ToBeAssignableWith;
735
736
  toBeBigInt: PrimitiveTypeMatcher;
@@ -743,12 +744,10 @@ declare class ExpectService {
743
744
  toBeUniqueSymbol: PrimitiveTypeMatcher;
744
745
  toBeUnknown: PrimitiveTypeMatcher;
745
746
  toBeVoid: PrimitiveTypeMatcher;
746
- toEqual: ToBe;
747
747
  toHaveProperty: ToHaveProperty;
748
748
  toMatch: ToMatch;
749
749
  toRaiseError: ToRaiseError;
750
750
  constructor(compiler: typeof ts, typeChecker: TypeChecker);
751
- static assertTypeChecker(typeChecker: ts.TypeChecker): typeChecker is TypeChecker;
752
751
  match(assertion: Assertion, onDiagnostics: DiagnosticsHandler): MatchResult | undefined;
753
752
  }
754
753
 
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"]);
@@ -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) {
@@ -994,7 +1004,7 @@ class RunReporter extends Reporter {
994
1004
  break;
995
1005
  }
996
1006
  case "store:info": {
997
- this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
1007
+ this.outputService.writeMessage(addsPackageStepText(payload.packageVersion, payload.packagePath));
998
1008
  this.#hasReportedAdds = true;
999
1009
  break;
1000
1010
  }
@@ -1118,7 +1128,7 @@ class RunReporter extends Reporter {
1118
1128
  class SetupReporter extends Reporter {
1119
1129
  handleEvent([eventName, payload]) {
1120
1130
  if (eventName === "store:info") {
1121
- this.outputService.writeMessage(addsPackageStepText(payload.compilerVersion, payload.installationPath));
1131
+ this.outputService.writeMessage(addsPackageStepText(payload.packageVersion, payload.packagePath));
1122
1132
  return;
1123
1133
  }
1124
1134
  if ("diagnostics" in payload) {
@@ -1317,17 +1327,6 @@ class Diagnostic {
1317
1327
  return new Diagnostic(text, "error", origin).add({ code, related });
1318
1328
  });
1319
1329
  }
1320
- static fromError(text, error) {
1321
- const messageText = Array.isArray(text) ? text : [text];
1322
- if (error instanceof Error && error.stack != null) {
1323
- if (messageText.length > 1) {
1324
- messageText.push("");
1325
- }
1326
- const stackLines = error.stack.split("\n").map((line) => line.trimStart());
1327
- messageText.push(...stackLines);
1328
- }
1329
- return Diagnostic.error(messageText);
1330
- }
1331
1330
  static #isTsDiagnosticWithLocation(diagnostic) {
1332
1331
  return diagnostic.file != null && diagnostic.start != null && diagnostic.length != null;
1333
1332
  }
@@ -1453,10 +1452,21 @@ class SelectService {
1453
1452
  #onDiagnostics(diagnostic) {
1454
1453
  EventEmitter.dispatch(["select:error", { diagnostics: [diagnostic] }]);
1455
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
+ }
1456
1467
  async selectFiles() {
1457
- const currentPath = ".";
1458
1468
  const testFilePaths = [];
1459
- await this.#visitDirectory(currentPath, testFilePaths);
1469
+ await this.#visitDirectory(".", testFilePaths);
1460
1470
  if (testFilePaths.length === 0) {
1461
1471
  this.#onDiagnostics(Diagnostic.error(SelectDiagnosticText.noTestFilesWereSelected(this.#resolvedConfig)));
1462
1472
  }
@@ -1464,38 +1474,20 @@ class SelectService {
1464
1474
  }
1465
1475
  async #visitDirectory(currentPath, testFilePaths) {
1466
1476
  const targetPath = Path.join(this.#resolvedConfig.rootPath, currentPath);
1467
- let entries;
1468
1477
  try {
1469
- entries = await fs.readdir(targetPath, { withFileTypes: true });
1470
- }
1471
- catch {
1472
- }
1473
- if (!entries) {
1474
- return;
1475
- }
1476
- for (const entry of entries) {
1477
- let entryMeta;
1478
- if (entry.isSymbolicLink()) {
1479
- try {
1480
- 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);
1481
1484
  }
1482
- catch {
1485
+ else if (entryMeta?.isFile() && this.#isFileIncluded(entryPath)) {
1486
+ testFilePaths.push([targetPath, entry.name].join("/"));
1483
1487
  }
1484
1488
  }
1485
- else {
1486
- entryMeta = entry;
1487
- }
1488
- if (!entryMeta) {
1489
- continue;
1490
- }
1491
- const entryPath = [currentPath, entry.name].join("/");
1492
- if (entryMeta.isDirectory() && this.#isDirectoryIncluded(entryPath)) {
1493
- await this.#visitDirectory(entryPath, testFilePaths);
1494
- continue;
1495
- }
1496
- if (entryMeta.isFile() && this.#isFileIncluded(entryPath)) {
1497
- testFilePaths.push([targetPath, entry.name].join("/"));
1498
- }
1489
+ }
1490
+ catch {
1499
1491
  }
1500
1492
  }
1501
1493
  }
@@ -1895,6 +1887,132 @@ var TestMemberFlags;
1895
1887
  TestMemberFlags[TestMemberFlags["Todo"] = 8] = "Todo";
1896
1888
  })(TestMemberFlags || (TestMemberFlags = {}));
1897
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
+
1898
2016
  class ExpectDiagnosticText {
1899
2017
  static argumentOrTypeArgumentMustBeProvided(argumentNameText, typeArgumentNameText) {
1900
2018
  return `An argument for '${argumentNameText}' or type argument for '${typeArgumentNameText}' must be provided.`;
@@ -1911,12 +2029,6 @@ class ExpectDiagnosticText {
1911
2029
  static componentDoesNotAcceptProps(isTypeNode) {
1912
2030
  return `${isTypeNode ? "Component type" : "Component"} does not accept props of the given type.`;
1913
2031
  }
1914
- static matcherIsDeprecated(matcherNameText) {
1915
- return [
1916
- `The '.${matcherNameText}()' matcher is deprecated and will be removed in TSTyche 3.`,
1917
- "To learn more, visit https://tstyche.org/releases/tstyche-2",
1918
- ];
1919
- }
1920
2032
  static matcherIsNotSupported(matcherNameText) {
1921
2033
  return `The '.${matcherNameText}()' matcher is not supported.`;
1922
2034
  }
@@ -2490,7 +2602,6 @@ class ExpectService {
2490
2602
  toAcceptProps;
2491
2603
  toBe;
2492
2604
  toBeAny;
2493
- toBeAssignable;
2494
2605
  toBeAssignableTo;
2495
2606
  toBeAssignableWith;
2496
2607
  toBeBigInt;
@@ -2504,7 +2615,6 @@ class ExpectService {
2504
2615
  toBeUniqueSymbol;
2505
2616
  toBeUnknown;
2506
2617
  toBeVoid;
2507
- toEqual;
2508
2618
  toHaveProperty;
2509
2619
  toMatch;
2510
2620
  toRaiseError;
@@ -2514,7 +2624,6 @@ class ExpectService {
2514
2624
  this.toAcceptProps = new ToAcceptProps(compiler, typeChecker);
2515
2625
  this.toBe = new ToBe();
2516
2626
  this.toBeAny = new PrimitiveTypeMatcher(compiler.TypeFlags.Any);
2517
- this.toBeAssignable = new ToBeAssignableWith();
2518
2627
  this.toBeAssignableTo = new ToBeAssignableTo();
2519
2628
  this.toBeAssignableWith = new ToBeAssignableWith();
2520
2629
  this.toBeBigInt = new PrimitiveTypeMatcher(compiler.TypeFlags.BigInt);
@@ -2528,28 +2637,12 @@ class ExpectService {
2528
2637
  this.toBeUniqueSymbol = new PrimitiveTypeMatcher(compiler.TypeFlags.UniqueESSymbol);
2529
2638
  this.toBeUnknown = new PrimitiveTypeMatcher(compiler.TypeFlags.Unknown);
2530
2639
  this.toBeVoid = new PrimitiveTypeMatcher(compiler.TypeFlags.Void);
2531
- this.toEqual = new ToBe();
2532
2640
  this.toHaveProperty = new ToHaveProperty(compiler);
2533
2641
  this.toMatch = new ToMatch();
2534
2642
  this.toRaiseError = new ToRaiseError(compiler);
2535
2643
  }
2536
- static assertTypeChecker(typeChecker) {
2537
- return "isTypeRelatedTo" in typeChecker && "relation" in typeChecker;
2538
- }
2539
- #handleDeprecated(matcherNameText, assertion) {
2540
- switch (matcherNameText) {
2541
- case "toBeAssignable":
2542
- case "toEqual": {
2543
- const text = ExpectDiagnosticText.matcherIsDeprecated(matcherNameText);
2544
- const origin = DiagnosticOrigin.fromNode(assertion.matcherName);
2545
- EventEmitter.dispatch(["deprecation:info", { diagnostics: [Diagnostic.warning(text, origin)] }]);
2546
- break;
2547
- }
2548
- }
2549
- }
2550
2644
  match(assertion, onDiagnostics) {
2551
2645
  const matcherNameText = assertion.matcherName.getText();
2552
- this.#handleDeprecated(matcherNameText, assertion);
2553
2646
  if (!assertion.source[0]) {
2554
2647
  this.#onSourceArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
2555
2648
  return;
@@ -2558,10 +2651,8 @@ class ExpectService {
2558
2651
  switch (matcherNameText) {
2559
2652
  case "toAcceptProps":
2560
2653
  case "toBe":
2561
- case "toBeAssignable":
2562
2654
  case "toBeAssignableTo":
2563
2655
  case "toBeAssignableWith":
2564
- case "toEqual":
2565
2656
  case "toMatch": {
2566
2657
  if (!assertion.target[0]) {
2567
2658
  this.#onTargetArgumentOrTypeArgumentMustBeProvided(assertion, onDiagnostics);
@@ -2618,132 +2709,6 @@ class ExpectService {
2618
2709
  }
2619
2710
  }
2620
2711
 
2621
- class Version {
2622
- static isGreaterThan(source, target) {
2623
- return !(source === target) && Version.#satisfies(source, target);
2624
- }
2625
- static isSatisfiedWith(source, target) {
2626
- return source === target || Version.#satisfies(source, target);
2627
- }
2628
- static isVersionTag(target) {
2629
- return /^\d+/.test(target);
2630
- }
2631
- static #satisfies(source, target) {
2632
- const sourceElements = source.split(/\.|-/);
2633
- const targetElements = target.split(/\.|-/);
2634
- function compare(index = 0) {
2635
- const sourceElement = sourceElements[index];
2636
- const targetElement = targetElements[index];
2637
- if (sourceElement > targetElement) {
2638
- return true;
2639
- }
2640
- if (sourceElement < targetElement) {
2641
- return false;
2642
- }
2643
- if (index === sourceElements.length - 1 || index === targetElements.length - 1) {
2644
- return true;
2645
- }
2646
- return compare(index + 1);
2647
- }
2648
- return compare();
2649
- }
2650
- }
2651
-
2652
- class ProjectService {
2653
- #compiler;
2654
- #service;
2655
- constructor(compiler) {
2656
- this.#compiler = compiler;
2657
- function doNothing() {
2658
- }
2659
- function returnFalse() {
2660
- return false;
2661
- }
2662
- function returnUndefined() {
2663
- return undefined;
2664
- }
2665
- const noopWatcher = { close: doNothing };
2666
- const noopLogger = {
2667
- close: doNothing,
2668
- endGroup: doNothing,
2669
- getLogFileName: returnUndefined,
2670
- hasLevel: returnFalse,
2671
- info: doNothing,
2672
- loggingEnabled: returnFalse,
2673
- msg: doNothing,
2674
- perftrc: doNothing,
2675
- startGroup: doNothing,
2676
- };
2677
- const host = {
2678
- ...this.#compiler.sys,
2679
- clearImmediate,
2680
- clearTimeout,
2681
- setImmediate,
2682
- setTimeout,
2683
- watchDirectory: () => noopWatcher,
2684
- watchFile: () => noopWatcher,
2685
- };
2686
- this.#service = new this.#compiler.server.ProjectService({
2687
- allowLocalPluginLoads: true,
2688
- cancellationToken: this.#compiler.server.nullCancellationToken,
2689
- host,
2690
- logger: noopLogger,
2691
- session: undefined,
2692
- useInferredProjectPerProjectRoot: true,
2693
- useSingleInferredProject: false,
2694
- });
2695
- this.#service.setCompilerOptionsForInferredProjects(this.#getDefaultCompilerOptions());
2696
- }
2697
- closeFile(filePath) {
2698
- this.#service.closeClientFile(filePath);
2699
- }
2700
- #getDefaultCompilerOptions() {
2701
- const defaultCompilerOptions = {
2702
- allowJs: true,
2703
- checkJs: true,
2704
- esModuleInterop: true,
2705
- jsx: "preserve",
2706
- module: "esnext",
2707
- moduleResolution: "node",
2708
- resolveJsonModule: true,
2709
- strictFunctionTypes: true,
2710
- strictNullChecks: true,
2711
- target: "esnext",
2712
- };
2713
- if (Version.isSatisfiedWith(this.#compiler.version, "5.4")) {
2714
- defaultCompilerOptions.module = "preserve";
2715
- }
2716
- if (Version.isSatisfiedWith(this.#compiler.version, "5.0")) {
2717
- defaultCompilerOptions.allowImportingTsExtensions = true;
2718
- defaultCompilerOptions.moduleResolution = "bundler";
2719
- }
2720
- return defaultCompilerOptions;
2721
- }
2722
- getDefaultProject(filePath) {
2723
- return this.#service.getDefaultProjectForFile(this.#compiler.server.toNormalizedPath(filePath), true);
2724
- }
2725
- getLanguageService(filePath) {
2726
- const project = this.getDefaultProject(filePath);
2727
- if (!project) {
2728
- return;
2729
- }
2730
- return project.getLanguageService(true);
2731
- }
2732
- openFile(filePath, sourceText, projectRootPath) {
2733
- const { configFileErrors, configFileName } = this.#service.openClientFile(filePath, sourceText, undefined, projectRootPath);
2734
- EventEmitter.dispatch([
2735
- "project:info",
2736
- { compilerVersion: this.#compiler.version, projectConfigFilePath: configFileName },
2737
- ]);
2738
- if (configFileErrors && configFileErrors.length > 0) {
2739
- EventEmitter.dispatch([
2740
- "project:error",
2741
- { diagnostics: Diagnostic.fromDiagnostics(configFileErrors, this.#compiler) },
2742
- ]);
2743
- }
2744
- }
2745
- }
2746
-
2747
2712
  class TestTreeWorker {
2748
2713
  #compiler;
2749
2714
  #cancellationToken;
@@ -2958,11 +2923,6 @@ class TestFileRunner {
2958
2923
  return;
2959
2924
  }
2960
2925
  const typeChecker = program.getTypeChecker();
2961
- if (!ExpectService.assertTypeChecker(typeChecker)) {
2962
- const text = "The required 'isTypeRelatedTo()' method is missing in the provided type checker.";
2963
- EventEmitter.dispatch(["file:error", { diagnostics: [Diagnostic.error(text)], result: fileResult }]);
2964
- return;
2965
- }
2966
2926
  const testTreeWorker = new TestTreeWorker(this.#resolvedConfig, this.#compiler, typeChecker, {
2967
2927
  cancellationToken,
2968
2928
  fileResult,
@@ -3010,7 +2970,7 @@ class TaskRunner {
3010
2970
  for (const versionTag of this.#resolvedConfig.target) {
3011
2971
  const targetResult = new TargetResult(versionTag, testFiles);
3012
2972
  EventEmitter.dispatch(["target:start", { result: targetResult }]);
3013
- const compiler = await this.#storeService.load(versionTag, cancellationToken);
2973
+ const compiler = await this.#storeService.load(versionTag);
3014
2974
  if (compiler) {
3015
2975
  const testFileRunner = new TestFileRunner(this.#resolvedConfig, compiler);
3016
2976
  for (const testFile of testFiles) {
@@ -3039,7 +2999,7 @@ class TSTyche {
3039
2999
  #selectService;
3040
3000
  #storeService;
3041
3001
  #taskRunner;
3042
- static version = "2.1.1";
3002
+ static version = "3.0.0-beta.0";
3043
3003
  constructor(resolvedConfig, outputService, selectService, storeService) {
3044
3004
  this.#resolvedConfig = resolvedConfig;
3045
3005
  this.#outputService = outputService;
@@ -3619,32 +3579,124 @@ var OptionGroup;
3619
3579
  })(OptionGroup || (OptionGroup = {}));
3620
3580
 
3621
3581
  class StoreDiagnosticText {
3622
- static failedToFetchMetadata(registryUrl) {
3623
- 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}'.`;
3624
3587
  }
3625
- static failedWithStatusCode(code) {
3626
- return `Request failed with status code ${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}'.`;
3627
3593
  }
3628
3594
  static maybeNetworkConnectionIssue() {
3629
3595
  return "Might be there is an issue with the registry or the network connection.";
3630
3596
  }
3631
- static setupTimeoutExceeded(timeout) {
3632
- return `Setup timeout of ${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));
3633
3685
  }
3634
3686
  }
3635
3687
 
3636
3688
  class ManifestWorker {
3637
- #manifestFileName = "store-manifest.json";
3689
+ #fetcher;
3638
3690
  #manifestFilePath;
3639
- #onDiagnostics;
3640
- #registryUrl = new URL("https://registry.npmjs.org");
3691
+ #npmRegistry;
3641
3692
  #storePath;
3642
3693
  #timeout = Environment.timeout * 1000;
3643
- #version = "1";
3644
- constructor(storePath, onDiagnostics) {
3694
+ #version = "2";
3695
+ constructor(storePath, npmRegistry, fetcher) {
3645
3696
  this.#storePath = storePath;
3646
- this.#onDiagnostics = onDiagnostics;
3647
- this.#manifestFilePath = Path.join(storePath, this.#manifestFileName);
3697
+ this.#npmRegistry = npmRegistry;
3698
+ this.#fetcher = fetcher;
3699
+ this.#manifestFilePath = Path.join(storePath, "store-manifest.json");
3648
3700
  }
3649
3701
  async #create() {
3650
3702
  const manifest = await this.#load();
@@ -3660,59 +3712,48 @@ class ManifestWorker {
3660
3712
  return false;
3661
3713
  }
3662
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
+ }
3663
3727
  const manifest = {
3664
3728
  $version: this.#version,
3665
3729
  lastUpdated: Date.now(),
3730
+ npmRegistry: this.#npmRegistry,
3666
3731
  resolutions: {},
3732
+ packages: {},
3667
3733
  versions: [],
3668
3734
  };
3669
- let packageMetadata;
3670
- try {
3671
- const response = await fetch(new URL("typescript", this.#registryUrl), {
3672
- headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3673
- signal: AbortSignal.timeout(this.#timeout),
3674
- });
3675
- if (!response.ok) {
3676
- this.#onDiagnostics(Diagnostic.error([
3677
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3678
- StoreDiagnosticText.failedWithStatusCode(response.status),
3679
- ]));
3680
- return;
3681
- }
3682
- packageMetadata = (await response.json());
3683
- }
3684
- catch (error) {
3685
- if (options?.quite === true) {
3686
- return;
3687
- }
3688
- if (error instanceof Error && error.name === "TimeoutError") {
3689
- this.#onDiagnostics(Diagnostic.error([
3690
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3691
- StoreDiagnosticText.setupTimeoutExceeded(this.#timeout),
3692
- ]));
3693
- }
3694
- else {
3695
- this.#onDiagnostics(Diagnostic.error([
3696
- StoreDiagnosticText.failedToFetchMetadata(this.#registryUrl),
3697
- StoreDiagnosticText.maybeNetworkConnectionIssue(),
3698
- ]));
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 };
3699
3740
  }
3700
- return;
3701
3741
  }
3702
- manifest.versions = Object.keys(packageMetadata.versions)
3703
- .filter((version) => /^(4|5)\.\d\.\d$/.test(version))
3704
- .sort();
3705
3742
  const minorVersions = [...new Set(manifest.versions.map((version) => version.slice(0, -2)))];
3706
3743
  for (const tag of minorVersions) {
3707
- const resolvedVersion = manifest.versions.filter((version) => version.startsWith(tag)).pop();
3744
+ const resolvedVersion = manifest.versions.findLast((version) => version.startsWith(tag));
3708
3745
  if (resolvedVersion != null) {
3709
3746
  manifest.resolutions[tag] = resolvedVersion;
3710
3747
  }
3711
3748
  }
3712
- for (const tagKey of ["beta", "latest", "next", "rc"]) {
3713
- const distributionTagValue = packageMetadata["dist-tags"][tagKey];
3714
- if (distributionTagValue != null) {
3715
- 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
+ }
3716
3757
  }
3717
3758
  }
3718
3759
  return manifest;
@@ -3722,28 +3763,18 @@ class ManifestWorker {
3722
3763
  if (!existsSync(this.#manifestFilePath)) {
3723
3764
  return this.#create();
3724
3765
  }
3725
- let manifestText;
3726
- try {
3727
- manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3728
- }
3729
- catch (error) {
3730
- this.#onDiagnostics(Diagnostic.fromError("Failed to open store manifest.", error));
3731
- }
3732
- if (!manifestText) {
3733
- return;
3734
- }
3766
+ const manifestText = await fs.readFile(this.#manifestFilePath, { encoding: "utf8" });
3735
3767
  try {
3736
3768
  manifest = JSON.parse(manifestText);
3737
3769
  }
3738
3770
  catch {
3739
3771
  }
3740
- if (!manifest || manifest.$version !== this.#version) {
3772
+ if (!manifest || manifest.$version !== this.#version || manifest.npmRegistry !== this.#npmRegistry) {
3741
3773
  await fs.rm(this.#storePath, { force: true, recursive: true });
3742
3774
  return this.#create();
3743
3775
  }
3744
3776
  if (this.isOutdated(manifest) || options?.refresh === true) {
3745
- const quite = options?.refresh !== true;
3746
- const freshManifest = await this.#load({ quite });
3777
+ const freshManifest = await this.#load({ suppressErrors: !options?.refresh });
3747
3778
  if (freshManifest != null) {
3748
3779
  await this.persist(freshManifest);
3749
3780
  return freshManifest;
@@ -3759,136 +3790,106 @@ class ManifestWorker {
3759
3790
  }
3760
3791
  }
3761
3792
 
3762
- class Lock {
3763
- #lockFilePath;
3764
- static #lockSuffix = "__lock__";
3765
- constructor(targetPath) {
3766
- this.#lockFilePath = Lock.#getLockFilePath(targetPath);
3767
- writeFileSync(this.#lockFilePath, "");
3768
- process.on("exit", () => {
3769
- this.release();
3770
- });
3771
- }
3772
- static #getLockFilePath(targetPath) {
3773
- return `${targetPath}${Lock.#lockSuffix}`;
3774
- }
3775
- static async isLocked(targetPath, options) {
3776
- let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3777
- if (!isLocked) {
3778
- return isLocked;
3779
- }
3780
- if (!options?.timeout) {
3781
- return isLocked;
3782
- }
3783
- const waitStartTime = Date.now();
3784
- while (isLocked) {
3785
- 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) {
3786
3801
  break;
3787
3802
  }
3788
- if (Date.now() - waitStartTime > options.timeout) {
3789
- options.onDiagnostics?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
3790
- 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;
3791
3809
  }
3792
- await Lock.#sleep(1000);
3793
- isLocked = existsSync(Lock.#getLockFilePath(targetPath));
3794
3810
  }
3795
- return isLocked;
3796
3811
  }
3797
- release() {
3798
- rmSync(this.#lockFilePath, { force: true });
3799
- }
3800
- static async #sleep(time) {
3801
- 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);
3802
3819
  }
3803
3820
  }
3804
3821
 
3805
- class PackageInstaller {
3806
- #onDiagnostics;
3807
- #readyFileName = "__ready__";
3822
+ class PackageService {
3823
+ #fetcher;
3824
+ #lockService;
3808
3825
  #storePath;
3809
3826
  #timeout = Environment.timeout * 1000;
3810
- constructor(storePath, onDiagnostics) {
3827
+ constructor(storePath, fetcher, lockService) {
3811
3828
  this.#storePath = storePath;
3812
- this.#onDiagnostics = onDiagnostics;
3829
+ this.#fetcher = fetcher;
3830
+ this.#lockService = lockService;
3813
3831
  }
3814
- async ensure(compilerVersion, cancellationToken) {
3815
- const installationPath = Path.join(this.#storePath, compilerVersion);
3816
- const readyFilePath = Path.join(installationPath, this.#readyFileName);
3817
- 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");
3818
3836
  if (existsSync(readyFilePath)) {
3819
3837
  return modulePath;
3820
3838
  }
3821
- if (await Lock.isLocked(installationPath, {
3822
- cancellationToken,
3823
- onDiagnostics: (text) => {
3824
- this.#onDiagnostics(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
3825
- },
3826
- timeout: this.#timeout,
3827
- })) {
3839
+ const diagnostic = Diagnostic.error(StoreDiagnosticText.failedToInstalTypeScript(packageVersion));
3840
+ if (await this.#lockService.isLocked(packagePath, this.#timeout, diagnostic)) {
3828
3841
  return;
3829
3842
  }
3830
- const lock = new Lock(installationPath);
3831
- EventEmitter.dispatch(["store:info", { compilerVersion, installationPath }]);
3832
- try {
3833
- await fs.mkdir(installationPath, { recursive: true });
3834
- const packageJson = {
3835
- name: "tstyche-typescript",
3836
- version: compilerVersion,
3837
- description: "Do not change. This package was generated by TSTyche",
3838
- private: true,
3839
- license: "MIT",
3840
- dependencies: {
3841
- typescript: compilerVersion,
3842
- },
3843
- };
3844
- await fs.writeFile(Path.join(installationPath, "package.json"), JSON.stringify(packageJson, null, 2));
3845
- await this.#install(installationPath);
3846
- 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
+ }
3847
3854
  return modulePath;
3848
3855
  }
3849
- catch (error) {
3850
- this.#onDiagnostics(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3851
- }
3852
- finally {
3853
- lock.release();
3854
- }
3855
3856
  return;
3856
3857
  }
3857
- async #install(cwd) {
3858
- const args = ["install", "--ignore-scripts", "--no-bin-links", "--no-package-lock"];
3859
- return new Promise((resolve, reject) => {
3860
- const spawnedNpm = spawn("npm", args, {
3861
- cwd,
3862
- shell: true,
3863
- stdio: "ignore",
3864
- timeout: this.#timeout,
3865
- });
3866
- spawnedNpm.on("error", (error) => {
3867
- reject(error);
3868
- });
3869
- spawnedNpm.on("close", (code, signal) => {
3870
- if (code === 0) {
3871
- 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;
3872
3866
  }
3873
- if (signal != null) {
3874
- reject(new Error(`setup timeout of ${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 });
3875
3871
  }
3876
- reject(new Error(`process exited with code ${code}`));
3877
- });
3878
- });
3872
+ await fs.writeFile(filePath, file.contents);
3873
+ }
3874
+ }
3879
3875
  }
3880
3876
  }
3881
3877
 
3882
3878
  class StoreService {
3883
3879
  #compilerInstanceCache = new Map();
3880
+ #fetcher;
3881
+ #lockService;
3884
3882
  #manifest;
3885
3883
  #manifestWorker;
3886
- #packageInstaller;
3884
+ #packageService;
3885
+ #npmRegistry = Environment.npmRegistry;
3887
3886
  #storePath;
3888
3887
  constructor() {
3889
3888
  this.#storePath = Environment.storePath;
3890
- this.#packageInstaller = new PackageInstaller(this.#storePath, this.#onDiagnostics);
3891
- 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);
3892
3893
  }
3893
3894
  async getSupportedTags() {
3894
3895
  await this.open();
@@ -3897,18 +3898,22 @@ class StoreService {
3897
3898
  }
3898
3899
  return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
3899
3900
  }
3900
- async install(tag, cancellationToken) {
3901
+ async install(tag) {
3901
3902
  if (tag === "current") {
3902
3903
  return;
3903
3904
  }
3904
- 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);
3905
3910
  if (!version) {
3906
- this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3911
+ this.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
3907
3912
  return;
3908
3913
  }
3909
- return this.#packageInstaller.ensure(version, cancellationToken);
3914
+ return this.#packageService.ensure(version, this.#manifest);
3910
3915
  }
3911
- async load(tag, cancellationToken) {
3916
+ async load(tag) {
3912
3917
  let compilerInstance = this.#compilerInstanceCache.get(tag);
3913
3918
  if (compilerInstance != null) {
3914
3919
  return compilerInstance;
@@ -3918,16 +3923,20 @@ class StoreService {
3918
3923
  modulePath = Environment.typescriptPath;
3919
3924
  }
3920
3925
  else {
3921
- 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);
3922
3931
  if (!version) {
3923
- this.#onDiagnostics(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3932
+ this.#onDiagnostics(Diagnostic.error(StoreDiagnosticText.cannotAddTypeScriptPackage(tag)));
3924
3933
  return;
3925
3934
  }
3926
3935
  compilerInstance = this.#compilerInstanceCache.get(version);
3927
3936
  if (compilerInstance != null) {
3928
3937
  return compilerInstance;
3929
3938
  }
3930
- modulePath = await this.#packageInstaller.ensure(version, cancellationToken);
3939
+ modulePath = await this.#packageService.ensure(version, this.#manifest);
3931
3940
  }
3932
3941
  if (modulePath != null) {
3933
3942
  compilerInstance = await this.#loadModule(modulePath);
@@ -3966,15 +3975,11 @@ class StoreService {
3966
3975
  }
3967
3976
  this.#manifest = await this.#manifestWorker.open();
3968
3977
  }
3969
- async #resolveTag(tag) {
3970
- await this.open();
3971
- if (!this.#manifest) {
3972
- return;
3973
- }
3974
- if (this.#manifest.versions.includes(tag)) {
3978
+ #resolveTag(tag, manifest) {
3979
+ if (manifest.versions.includes(tag)) {
3975
3980
  return tag;
3976
3981
  }
3977
- return this.#manifest.resolutions[tag];
3982
+ return manifest.resolutions[tag];
3978
3983
  }
3979
3984
  async update() {
3980
3985
  await this.#manifestWorker.open({ refresh: true });
@@ -3992,8 +3997,8 @@ class StoreService {
3992
3997
  (this.#manifest.resolutions["latest"] != null &&
3993
3998
  Version.isGreaterThan(tag, this.#manifest.resolutions["latest"])))) {
3994
3999
  this.#onDiagnostics(Diagnostic.warning([
3995
- "Failed to update metadata of the 'typescript' package from the registry.",
3996
- `The resolution of the '${tag}' tag may be outdated.`,
4000
+ StoreDiagnosticText.failedToUpdateMetadata(this.#npmRegistry),
4001
+ StoreDiagnosticText.maybeOutdatedResolution(tag),
3997
4002
  ]));
3998
4003
  }
3999
4004
  return tag in this.#manifest.resolutions || this.#manifest.versions.includes(tag);
@@ -4057,6 +4062,7 @@ class Cli {
4057
4062
  this.#outputService.writeMessage(formattedText({
4058
4063
  noColor: Environment.noColor,
4059
4064
  noInteractive: Environment.noInteractive,
4065
+ npmRegistry: Environment.npmRegistry,
4060
4066
  storePath: Environment.storePath,
4061
4067
  timeout: Environment.timeout,
4062
4068
  typescriptPath: Environment.typescriptPath,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "2.1.1",
3
+ "version": "3.0.0-beta.0",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -47,14 +47,14 @@
47
47
  "generate:schema": "node ./scripts/generate-schema.js",
48
48
  "generate:types": "node ./scripts/generate-types.js",
49
49
  "lint": "biome lint --write",
50
- "prepublish": "yarn clean && yarn build && yarn test",
50
+ "prepublish": "yarn clean && yarn build",
51
51
  "test": "yarn test:unit && yarn test:e2e",
52
52
  "test:coverage": "yarn test:coverage:collect && yarn test:coverage:report",
53
53
  "test:coverage:collect": "yarn build --sourcemap && NODE_V8_COVERAGE='./coverage/v8-coverage' yarn test:e2e",
54
54
  "test:coverage:report": "node ./scripts/report-coverage.js",
55
55
  "test:e2e": "yarn test:e2e:parallel && yarn test:e2e:serial",
56
- "test:e2e:parallel": "yarn poku ./tests --exclude='store|target|update|versions|watch' --parallel",
57
- "test:e2e:serial": "yarn poku tests/*-{store,target,update,versions,watch}.test.*",
56
+ "test:e2e:parallel": "yarn poku tests/*.test.* --exclude='install|npmRegistry|store|target|typescript|update|watch' --parallel",
57
+ "test:e2e:serial": "yarn poku tests/*-{install,npmRegistry,store*,target,typescript*,update,watch}.test.*",
58
58
  "test:examples": "tstyche examples",
59
59
  "test:types": "tstyche typetests",
60
60
  "test:unit": "yarn poku **/__tests__/*.test.* --parallel"
@@ -62,15 +62,15 @@
62
62
  "devDependencies": {
63
63
  "@biomejs/biome": "1.8.3",
64
64
  "@rollup/plugin-typescript": "11.1.6",
65
- "@types/node": "20.14.12",
65
+ "@types/node": "20.14.15",
66
66
  "@types/react": "18.3.3",
67
67
  "ajv": "8.17.1",
68
- "cspell": "8.12.1",
69
- "magic-string": "0.30.10",
70
- "monocart-coverage-reports": "2.9.3",
71
- "poku": "2.2.3",
68
+ "cspell": "8.13.2",
69
+ "magic-string": "0.30.11",
70
+ "monocart-coverage-reports": "2.10.2",
71
+ "poku": "2.4.3",
72
72
  "pretty-ansi": "2.0.0",
73
- "rollup": "4.19.1",
73
+ "rollup": "4.20.0",
74
74
  "rollup-plugin-dts": "6.1.1",
75
75
  "tslib": "2.6.3",
76
76
  "typescript": "5.5.4"
@@ -83,8 +83,8 @@
83
83
  "optional": true
84
84
  }
85
85
  },
86
- "packageManager": "yarn@3.8.3",
86
+ "packageManager": "yarn@4.4.0",
87
87
  "engines": {
88
- "node": ">=16.14"
88
+ "node": ">=18.14"
89
89
  }
90
90
  }