tstyche 1.0.0-beta.4 → 1.0.0-beta.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,20 @@
1
1
  # Changelog
2
2
 
3
+ ## [1.0.0-beta.5] - 2023-11-27
4
+
5
+ ### Changed
6
+
7
+ - **Breaking:** Move retry logic to the `Lock` class ([#31](https://github.com/tstyche/tstyche/pull/31))
8
+ - Bring back support for Node.js 16 ([#28](https://github.com/tstyche/tstyche/pull/28), [#27](https://github.com/tstyche/tstyche/pull/27))
9
+
10
+ ### Added
11
+
12
+ - Add support for the `current` target tag ([#33](https://github.com/tstyche/tstyche/pull/33))
13
+
14
+ ### Fixed
15
+
16
+ - Allow `.raiseError()` to take template literals as arguments. ([#35](https://github.com/tstyche/tstyche/pull/35))
17
+
3
18
  ## [1.0.0-beta.4] - 2023-11-24
4
19
 
5
20
  ### Added
@@ -40,6 +55,7 @@
40
55
 
41
56
  _First pre-release._
42
57
 
58
+ [1.0.0-beta.5]: https://github.com/tstyche/tstyche/releases/tag/v1.0.0-beta.5
43
59
  [1.0.0-beta.4]: https://github.com/tstyche/tstyche/releases/tag/v1.0.0-beta.4
44
60
  [1.0.0-beta.3]: https://github.com/tstyche/tstyche/releases/tag/v1.0.0-beta.3
45
61
  [1.0.0-beta.2]: https://github.com/tstyche/tstyche/releases/tag/v1.0.0-beta.2
package/build/tstyche.js CHANGED
@@ -5,7 +5,7 @@ import path from 'node:path';
5
5
  import fs from 'node:fs/promises';
6
6
  import { createRequire } from 'node:module';
7
7
  import { spawn } from 'node:child_process';
8
- import { setInterval } from 'node:timers/promises';
8
+ import https from 'node:https';
9
9
 
10
10
  class EventEmitter {
11
11
  static #handlers = new Set();
@@ -1396,7 +1396,10 @@ class IdentifierLookup {
1396
1396
  };
1397
1397
  }
1398
1398
  clone() {
1399
- return structuredClone(this.#identifiers);
1399
+ return {
1400
+ namedImports: { ...this.#identifiers.namedImports },
1401
+ namespace: this.#identifiers.namespace,
1402
+ };
1400
1403
  }
1401
1404
  handleImportDeclaration(node) {
1402
1405
  if (this.#moduleSpecifiers.includes(node.moduleSpecifier.getText()) &&
@@ -1680,7 +1683,9 @@ class Checker {
1680
1683
  this.#assertNonNullish(assertion.typeChecker, "The 'typeChecker' was not provided.");
1681
1684
  }
1682
1685
  #assertStringsOrNumbers(nodes) {
1683
- return nodes.every((expression) => this.compiler.isStringLiteral(expression) || this.compiler.isNumericLiteral(expression));
1686
+ return nodes.every((expression) => this.compiler.isStringLiteral(expression) ||
1687
+ this.compiler.isNumericLiteral(expression) ||
1688
+ this.compiler.isNoSubstitutionTemplateLiteral(expression));
1684
1689
  }
1685
1690
  explain(assertion) {
1686
1691
  this.#assertNonNullishTypeChecker(assertion);
@@ -2330,7 +2335,7 @@ class OptionDefinitionsMap {
2330
2335
  items: {
2331
2336
  brand: "string",
2332
2337
  name: "target",
2333
- pattern: "^([45]\\.[0-9](\\.[0-9])?)|beta|latest|next|rc$",
2338
+ pattern: "^([45]\\.[0-9](\\.[0-9])?)|beta|current|latest|next|rc$",
2334
2339
  },
2335
2340
  name: "target",
2336
2341
  },
@@ -2426,7 +2431,7 @@ class OptionUsageText {
2426
2431
  const supportedTagsText = `Supported tags: ${["'", supportedTags.join("', '"), "'"].join("")}.`;
2427
2432
  switch (this.#optionGroup) {
2428
2433
  case 2:
2429
- usageText.push("Argument for the '--target' option must be a single tag or a comma separated list of versions.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,4.8,latest'.", supportedTagsText);
2434
+ usageText.push("Argument for the '--target' option must be a single tag or a comma separated list.", "Usage examples: '--target 4.9', '--target 5.0.4', '--target 4.7,4.8,latest'.", supportedTagsText);
2430
2435
  break;
2431
2436
  case 4:
2432
2437
  usageText.push("Item of the 'target' list must be a supported version tag.", supportedTagsText);
@@ -2916,58 +2921,85 @@ class Lock {
2916
2921
  #lockFilePath;
2917
2922
  static #lockSuffix = "__lock__";
2918
2923
  constructor(targetPath) {
2919
- this.#lockFilePath = Lock.getLockFilePath(targetPath);
2924
+ this.#lockFilePath = Lock.#getLockFilePath(targetPath);
2920
2925
  writeFileSync(this.#lockFilePath, "");
2921
2926
  process.on("exit", () => {
2922
2927
  this.release();
2923
2928
  });
2924
2929
  }
2925
- static getLockFilePath(targetPath) {
2930
+ static #getLockFilePath(targetPath) {
2926
2931
  return `${targetPath}${Lock.#lockSuffix}`;
2927
2932
  }
2928
- static isLocked(targetPath) {
2929
- return existsSync(Lock.getLockFilePath(targetPath));
2933
+ static async isLocked(targetPath, options) {
2934
+ let isLocked = existsSync(Lock.#getLockFilePath(targetPath));
2935
+ if (!isLocked) {
2936
+ return isLocked;
2937
+ }
2938
+ if (options?.timeout == null) {
2939
+ return isLocked;
2940
+ }
2941
+ const waitStartTime = Date.now();
2942
+ while (isLocked) {
2943
+ if (options.signal?.aborted === true) {
2944
+ break;
2945
+ }
2946
+ if (Date.now() - waitStartTime > options.timeout) {
2947
+ options.onDiagnostic?.(`Lock wait timeout of ${options.timeout / 1000}s was exceeded.`);
2948
+ break;
2949
+ }
2950
+ await Lock.#sleep(1000);
2951
+ isLocked = existsSync(Lock.#getLockFilePath(targetPath));
2952
+ }
2953
+ return isLocked;
2930
2954
  }
2931
2955
  release() {
2932
2956
  rmSync(this.#lockFilePath, { force: true });
2933
2957
  }
2958
+ static async #sleep(time) {
2959
+ return new Promise((resolve) => setTimeout(resolve, time));
2960
+ }
2934
2961
  }
2935
2962
 
2936
2963
  class CompilerModuleWorker {
2937
2964
  #cachePath;
2965
+ #onDiagnostic;
2938
2966
  #readyFileName = "__ready__";
2939
2967
  #timeout = Environment.timeout * 1000;
2940
- constructor(cachePath) {
2968
+ constructor(cachePath, onDiagnostic) {
2941
2969
  this.#cachePath = cachePath;
2970
+ this.#onDiagnostic = onDiagnostic;
2942
2971
  }
2943
2972
  async ensure(compilerVersion, signal) {
2944
2973
  const installationPath = path.join(this.#cachePath, compilerVersion);
2945
2974
  const readyFilePath = path.join(installationPath, this.#readyFileName);
2946
2975
  const tsserverFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "tsserverlibrary.js");
2947
2976
  const typescriptFilePath = path.join(installationPath, "node_modules", "typescript", "lib", "typescript.js");
2948
- if (Lock.isLocked(installationPath)) {
2949
- for await (const now of setInterval(1000, Date.now(), { signal })) {
2950
- const startTime = Date.now();
2951
- if (!Lock.isLocked(installationPath)) {
2952
- break;
2953
- }
2954
- if (startTime - now > this.#timeout) {
2955
- throw new Error(`Lock wait timeout of ${this.#timeout / 1000}s was exceeded.`);
2956
- }
2957
- }
2977
+ if (await Lock.isLocked(installationPath, {
2978
+ onDiagnostic: (text) => {
2979
+ this.#onDiagnostic(Diagnostic.error([`Failed to install 'typescript@${compilerVersion}'.`, text]));
2980
+ },
2981
+ signal,
2982
+ timeout: this.#timeout,
2983
+ })) {
2984
+ return;
2958
2985
  }
2959
2986
  if (existsSync(readyFilePath)) {
2960
2987
  return tsserverFilePath;
2961
2988
  }
2962
2989
  EventEmitter.dispatch(["store:info", { compilerVersion, installationPath: this.#normalizePath(installationPath) }]);
2963
- await fs.mkdir(installationPath, { recursive: true });
2964
- const lock = new Lock(installationPath);
2965
- await fs.writeFile(path.join(installationPath, "package.json"), this.#getPackageJson(compilerVersion));
2966
- await this.#installPackage(installationPath, signal);
2967
- await fs.writeFile(tsserverFilePath, await this.#getPatched(compilerVersion, tsserverFilePath));
2968
- await fs.writeFile(typescriptFilePath, await this.#getPatched(compilerVersion, typescriptFilePath));
2969
- await fs.writeFile(readyFilePath, "");
2970
- lock.release();
2990
+ try {
2991
+ await fs.mkdir(installationPath, { recursive: true });
2992
+ const lock = new Lock(installationPath);
2993
+ await fs.writeFile(path.join(installationPath, "package.json"), this.#getPackageJson(compilerVersion));
2994
+ await this.#installPackage(installationPath, signal);
2995
+ await fs.writeFile(tsserverFilePath, await this.#getPatched(compilerVersion, tsserverFilePath));
2996
+ await fs.writeFile(typescriptFilePath, await this.#getPatched(compilerVersion, typescriptFilePath));
2997
+ await fs.writeFile(readyFilePath, "");
2998
+ lock.release();
2999
+ }
3000
+ catch (error) {
3001
+ this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${compilerVersion}'.`, error));
3002
+ }
2971
3003
  return tsserverFilePath;
2972
3004
  }
2973
3005
  #getPackageJson(version) {
@@ -3043,14 +3075,34 @@ class ManifestWorker {
3043
3075
  this.#prune = prune;
3044
3076
  }
3045
3077
  async #fetch(signal) {
3046
- const result = await fetch(new URL("typescript", this.#registryUrl), {
3047
- headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3048
- signal,
3078
+ return new Promise((resolve, reject) => {
3079
+ const request = https.get(new URL("typescript", this.#registryUrl), {
3080
+ headers: { accept: "application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*" },
3081
+ signal,
3082
+ }, (result) => {
3083
+ if (result.statusCode !== 200) {
3084
+ reject(new Error(`Request failed with status code ${String(result.statusCode)}.`));
3085
+ return;
3086
+ }
3087
+ result.setEncoding("utf8");
3088
+ let rawData = "";
3089
+ result.on("data", (chunk) => {
3090
+ rawData += chunk;
3091
+ });
3092
+ result.on("end", () => {
3093
+ try {
3094
+ const packageMetadata = JSON.parse(rawData);
3095
+ resolve(packageMetadata);
3096
+ }
3097
+ catch (error) {
3098
+ reject(error);
3099
+ }
3100
+ });
3101
+ });
3102
+ request.on("error", (error) => {
3103
+ reject(error);
3104
+ });
3049
3105
  });
3050
- if (!result.ok) {
3051
- throw new Error(`Request failed with status code ${String(result.status)}.`);
3052
- }
3053
- return result.json();
3054
3106
  }
3055
3107
  isOutdated(manifest, ageTolerance = 0) {
3056
3108
  if (Date.now() - manifest.lastUpdated > 2 * 60 * 60 * 1000 + ageTolerance * 1000) {
@@ -3093,15 +3145,15 @@ class ManifestWorker {
3093
3145
  .sort();
3094
3146
  const minorVersions = [...new Set(manifest.versions.map((version) => version.slice(0, -2)))];
3095
3147
  for (const tag of minorVersions) {
3096
- const resolvedVersion = manifest.versions.findLast((version) => version.startsWith(tag));
3148
+ const resolvedVersion = manifest.versions.filter((version) => version.startsWith(tag)).pop();
3097
3149
  if (resolvedVersion != null) {
3098
3150
  manifest.resolutions[tag] = resolvedVersion;
3099
3151
  }
3100
3152
  }
3101
- for (const distributionTagKey of ["beta", "latest", "next", "rc"]) {
3102
- const distributionTagValue = packageMetadata["dist-tags"][distributionTagKey];
3153
+ for (const tagKey of ["beta", "latest", "next", "rc"]) {
3154
+ const distributionTagValue = packageMetadata["dist-tags"][tagKey];
3103
3155
  if (distributionTagValue != null) {
3104
- manifest.resolutions[distributionTagKey] = distributionTagValue;
3156
+ manifest.resolutions[tagKey] = distributionTagValue;
3105
3157
  }
3106
3158
  }
3107
3159
  return manifest;
@@ -3171,7 +3223,7 @@ class StoreService {
3171
3223
  #nodeRequire = createRequire(import.meta.url);
3172
3224
  constructor() {
3173
3225
  this.#cachePath = Environment.storePath;
3174
- this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath);
3226
+ this.#compilerModuleWorker = new CompilerModuleWorker(this.#cachePath, this.#onDiagnostic);
3175
3227
  this.#manifestWorker = new ManifestWorker(this.#cachePath, async () => this.prune());
3176
3228
  }
3177
3229
  get supportedTags() {
@@ -3179,7 +3231,7 @@ class StoreService {
3179
3231
  this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3180
3232
  return [];
3181
3233
  }
3182
- return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions].sort();
3234
+ return [...Object.keys(this.#manifest.resolutions), ...this.#manifest.versions, "current"].sort();
3183
3235
  }
3184
3236
  async install(tag, signal) {
3185
3237
  if (!this.#manifest) {
@@ -3191,14 +3243,7 @@ class StoreService {
3191
3243
  this.#onDiagnostic(Diagnostic.error(`Cannot add the 'typescript' package for the '${tag}' tag.`));
3192
3244
  return;
3193
3245
  }
3194
- let modulePath;
3195
- try {
3196
- modulePath = await this.#compilerModuleWorker.ensure(version, signal);
3197
- }
3198
- catch (error) {
3199
- this.#onDiagnostic(Diagnostic.fromError(`Failed to install 'typescript@${version}'.`, error));
3200
- }
3201
- return modulePath;
3246
+ return this.#compilerModuleWorker.ensure(version, signal);
3202
3247
  }
3203
3248
  async load(tag, signal) {
3204
3249
  let modulePath;
@@ -3218,9 +3263,9 @@ class StoreService {
3218
3263
  }
3219
3264
  return;
3220
3265
  }
3221
- #onDiagnostic(diagnostic) {
3266
+ #onDiagnostic = (diagnostic) => {
3222
3267
  EventEmitter.dispatch(["store:error", { diagnostics: [diagnostic] }]);
3223
- }
3268
+ };
3224
3269
  async open(signal) {
3225
3270
  if (this.#manifest) {
3226
3271
  return;
@@ -3235,6 +3280,14 @@ class StoreService {
3235
3280
  this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3236
3281
  return;
3237
3282
  }
3283
+ if (tag === "current") {
3284
+ try {
3285
+ tag = this.#nodeRequire("typescript").version;
3286
+ }
3287
+ catch (error) {
3288
+ this.#onDiagnostic(Diagnostic.fromError("Failed to resolve tag 'current'. The 'typescript' package might be not installed.", error));
3289
+ }
3290
+ }
3238
3291
  if (this.#manifest.versions.includes(tag)) {
3239
3292
  return tag;
3240
3293
  }
@@ -3260,7 +3313,7 @@ class StoreService {
3260
3313
  this.#onDiagnostic(Diagnostic.error("Store manifest is not open. Call 'StoreService.open()' first."));
3261
3314
  return false;
3262
3315
  }
3263
- if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions) {
3316
+ if (this.#manifest.versions.includes(tag) || tag in this.#manifest.resolutions || tag === "current") {
3264
3317
  return true;
3265
3318
  }
3266
3319
  if (this.#manifest.resolutions["latest"] != null &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tstyche",
3
- "version": "1.0.0-beta.4",
3
+ "version": "1.0.0-beta.5",
4
4
  "description": "The Essential Type Testing Tool.",
5
5
  "keywords": [
6
6
  "typescript",
@@ -57,7 +57,7 @@
57
57
  "devDependencies": {
58
58
  "@jest/globals": "29.7.0",
59
59
  "@rollup/plugin-typescript": "11.1.5",
60
- "@types/node": "20.9.5",
60
+ "@types/node": "20.10.0",
61
61
  "@typescript-eslint/eslint-plugin": "6.12.0",
62
62
  "@typescript-eslint/parser": "6.12.0",
63
63
  "ajv": "8.12.0",
@@ -76,7 +76,7 @@
76
76
  "jest-serializer-ansi-escapes": "2.0.1",
77
77
  "magic-string": "0.30.5",
78
78
  "prettier": "3.1.0",
79
- "rollup": "4.5.2",
79
+ "rollup": "4.6.0",
80
80
  "rollup-plugin-dts": "6.1.0",
81
81
  "rollup-plugin-tsconfig-paths": "1.5.2",
82
82
  "ts-node": "10.9.1",
@@ -91,8 +91,8 @@
91
91
  "optional": true
92
92
  }
93
93
  },
94
- "packageManager": "yarn@4.0.2",
94
+ "packageManager": "yarn@3.7.0",
95
95
  "engines": {
96
- "node": "^18.12 || >=20.x"
96
+ "node": "^16.14 || 18.x || >=20.x"
97
97
  }
98
98
  }