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 +16 -0
- package/build/tstyche.js +105 -52
- package/package.json +5 -5
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
|
|
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
|
|
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) ||
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
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
|
-
|
|
2964
|
-
|
|
2965
|
-
|
|
2966
|
-
|
|
2967
|
-
|
|
2968
|
-
|
|
2969
|
-
|
|
2970
|
-
|
|
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
|
-
|
|
3047
|
-
|
|
3048
|
-
|
|
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.
|
|
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
|
|
3102
|
-
const distributionTagValue = packageMetadata["dist-tags"][
|
|
3153
|
+
for (const tagKey of ["beta", "latest", "next", "rc"]) {
|
|
3154
|
+
const distributionTagValue = packageMetadata["dist-tags"][tagKey];
|
|
3103
3155
|
if (distributionTagValue != null) {
|
|
3104
|
-
manifest.resolutions[
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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@
|
|
94
|
+
"packageManager": "yarn@3.7.0",
|
|
95
95
|
"engines": {
|
|
96
|
-
"node": "^18.
|
|
96
|
+
"node": "^16.14 || 18.x || >=20.x"
|
|
97
97
|
}
|
|
98
98
|
}
|