Skip to content

Commit e666b77

Browse files
authored
feat(typescript): support TypeScript 7 (#110)
1 parent 5b8d6ec commit e666b77

9 files changed

Lines changed: 580 additions & 62 deletions

README.md

Lines changed: 33 additions & 16 deletions
Large diffs are not rendered by default.
Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,13 @@
1-
const TYPESCRIPT_GO_PACKAGE = '@typescript/native-preview';
2-
const TYPESCRIPT_GO_PACKAGE_JSON = `${TYPESCRIPT_GO_PACKAGE}/package.json`;
1+
const TYPESCRIPT_PACKAGE = 'typescript';
2+
const TYPESCRIPT_PACKAGE_JSON = `${TYPESCRIPT_PACKAGE}/package.json`;
3+
const TYPESCRIPT_PREVIEW_PACKAGE = '@typescript/native-preview';
4+
const TYPESCRIPT_PREVIEW_PACKAGE_JSON = `${TYPESCRIPT_PREVIEW_PACKAGE}/package.json`;
35
const TYPESCRIPT_GO_ISSUE_CODE = 'TSGO';
46

5-
export { TYPESCRIPT_GO_PACKAGE, TYPESCRIPT_GO_PACKAGE_JSON, TYPESCRIPT_GO_ISSUE_CODE };
7+
export {
8+
TYPESCRIPT_GO_ISSUE_CODE,
9+
TYPESCRIPT_PACKAGE,
10+
TYPESCRIPT_PACKAGE_JSON,
11+
TYPESCRIPT_PREVIEW_PACKAGE,
12+
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
13+
};
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
import path from 'node:path';
2+
3+
import {
4+
TYPESCRIPT_PACKAGE,
5+
TYPESCRIPT_PREVIEW_PACKAGE,
6+
} from './type-script-go-constants';
7+
8+
type TypeScriptGoPackage = 'typescript' | 'preview';
9+
10+
type TypeScriptGoPackageJson = {
11+
name?: string;
12+
version?: string;
13+
bin?: string | Record<string, string>;
14+
};
15+
16+
type ResolvedTypeScriptGoPackage = {
17+
packageJsonPath: string;
18+
tsgoPackage: TypeScriptGoPackage;
19+
};
20+
21+
function readTsgoPackageJson(packageJsonPath: string): TypeScriptGoPackageJson {
22+
return require(packageJsonPath) as TypeScriptGoPackageJson;
23+
}
24+
25+
function getTsgoPackage(packageJson: TypeScriptGoPackageJson): TypeScriptGoPackage | undefined {
26+
if (packageJson.name === TYPESCRIPT_PACKAGE) {
27+
const versionMatch = packageJson.version?.match(/^(\d+)\.(\d+)(?:\.|$|-)/);
28+
29+
if (versionMatch && Number(versionMatch[1]) >= 7) {
30+
return 'typescript';
31+
}
32+
}
33+
34+
if (packageJson.name === TYPESCRIPT_PREVIEW_PACKAGE) {
35+
return 'preview';
36+
}
37+
38+
return undefined;
39+
}
40+
41+
function resolveTypeScriptGoPackage(
42+
packageJsonPath: string,
43+
): ResolvedTypeScriptGoPackage | undefined {
44+
if (
45+
!path.isAbsolute(packageJsonPath) ||
46+
path.basename(packageJsonPath) !== 'package.json'
47+
) {
48+
return undefined;
49+
}
50+
51+
try {
52+
const tsgoPackage = getTsgoPackage(readTsgoPackageJson(packageJsonPath));
53+
54+
return tsgoPackage ? { packageJsonPath, tsgoPackage } : undefined;
55+
} catch {
56+
return undefined;
57+
}
58+
}
59+
60+
export {
61+
getTsgoPackage,
62+
readTsgoPackageJson,
63+
resolveTypeScriptGoPackage,
64+
};
65+
66+
export type {
67+
ResolvedTypeScriptGoPackage,
68+
TypeScriptGoPackage,
69+
TypeScriptGoPackageJson,
70+
};

src/typescript/type-script-go-runner.ts

Lines changed: 55 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import { spawn } from 'node:child_process';
2+
import fs from 'node:fs';
23
import path from 'node:path';
34
import { pathToFileURL } from 'node:url';
45

@@ -9,35 +10,78 @@ import { AbortError } from '../utils/async/abort-error';
910
import type { TypeScriptWorkerConfig } from './type-script-worker-config';
1011
import {
1112
TYPESCRIPT_GO_ISSUE_CODE,
12-
TYPESCRIPT_GO_PACKAGE,
13-
TYPESCRIPT_GO_PACKAGE_JSON,
13+
TYPESCRIPT_PACKAGE,
14+
TYPESCRIPT_PREVIEW_PACKAGE,
15+
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
1416
} from './type-script-go-constants';
1517

18+
type TypeScriptGoExecutable = {
19+
command: string;
20+
args: string[];
21+
};
22+
1623
function resolveTypeScriptGoPackageJsonPath(config: TypeScriptWorkerConfig): string {
1724
if (
1825
!path.isAbsolute(config.typescriptPath) ||
1926
path.basename(config.typescriptPath) !== 'package.json'
2027
) {
2128
throw new Error(
22-
`The typescriptPath option must be an absolute path to "${TYPESCRIPT_GO_PACKAGE_JSON}" when tsgo is enabled.`,
29+
`The typescriptPath option must be an absolute path to a package.json file when tsgo is enabled.`,
30+
);
31+
}
32+
33+
if (!config.tsgoPackage) {
34+
throw new Error(
35+
`The typescriptPath option must point to "${TYPESCRIPT_PREVIEW_PACKAGE_JSON}" or "${TYPESCRIPT_PACKAGE}@rc".`,
2336
);
2437
}
2538

2639
return config.typescriptPath;
2740
}
2841

29-
async function resolveTypeScriptGoBinPath(config: TypeScriptWorkerConfig): Promise<string> {
30-
const tsgoPkgPath = resolveTypeScriptGoPackageJsonPath(config);
31-
const getExePathPath = path.resolve(path.dirname(tsgoPkgPath), './lib/getExePath.js');
42+
async function resolveTypeScriptGoNativeExecutablePath(
43+
packageJsonPath: string,
44+
packageName: string,
45+
): Promise<string> {
46+
const getExePathPath = path.resolve(path.dirname(packageJsonPath), './lib/getExePath.js');
3247
const getExePathUrl = pathToFileURL(getExePathPath).href;
3348
const getExePathModule = await import(getExePathUrl);
3449
const getExePath = getExePathModule.default || getExePathModule.getExePath;
3550

3651
if (typeof getExePath !== 'function') {
37-
throw new Error(`Cannot resolve the typescript-go executable from "${TYPESCRIPT_GO_PACKAGE}".`);
52+
throw new Error(
53+
`Cannot resolve the typescript-go executable from "${packageName}".`,
54+
);
55+
}
56+
57+
const executablePath = getExePath();
58+
59+
if (typeof executablePath !== 'string' || !fs.existsSync(executablePath)) {
60+
throw new Error('Executable not found');
61+
}
62+
63+
return executablePath;
64+
}
65+
66+
async function resolveTypeScriptGoBinPath(config: TypeScriptWorkerConfig): Promise<string> {
67+
const packageJsonPath = resolveTypeScriptGoPackageJsonPath(config);
68+
69+
if (config.tsgoPackage === 'typescript') {
70+
return resolveTypeScriptGoNativeExecutablePath(packageJsonPath, TYPESCRIPT_PACKAGE);
3871
}
3972

40-
return getExePath();
73+
return resolveTypeScriptGoNativeExecutablePath(packageJsonPath, TYPESCRIPT_PREVIEW_PACKAGE);
74+
}
75+
76+
async function resolveTypeScriptGoExecutable(
77+
config: TypeScriptWorkerConfig,
78+
): Promise<TypeScriptGoExecutable> {
79+
const binPath = await resolveTypeScriptGoBinPath(config);
80+
81+
return {
82+
command: binPath,
83+
args: [],
84+
};
4185
}
4286

4387
function createTypeScriptGoArgs(config: TypeScriptWorkerConfig) {
@@ -218,13 +262,13 @@ async function runTypeScriptGo(
218262
): Promise<Issue[]> {
219263
AbortError.throwIfAborted(signal);
220264

221-
const binPath = await resolveTypeScriptGoBinPath(config);
265+
const executable = await resolveTypeScriptGoExecutable(config);
222266
const args = createTypeScriptGoArgs(config);
223267

224268
return new Promise((resolve, reject) => {
225269
let settled = false;
226270
let output = '';
227-
const childProcess = spawn(binPath, args, {
271+
const childProcess = spawn(executable.command, [...executable.args, ...args], {
228272
cwd: config.context,
229273
stdio: ['inherit', 'pipe', 'pipe'],
230274
});
@@ -312,6 +356,7 @@ export {
312356
isTypeScriptGoIssue,
313357
isTypeScriptGoStatsError,
314358
parseTypeScriptGoIssues,
359+
resolveTypeScriptGoExecutable,
315360
resolveTypeScriptGoBinPath,
316361
resolveTypeScriptGoPackageJsonPath,
317362
runTypeScriptGo,

src/typescript/type-script-support.ts

Lines changed: 42 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,48 +3,77 @@ import fs from 'node:fs';
33
import path from 'node:path';
44

55
import type { TypeScriptWorkerConfig } from './type-script-worker-config';
6-
import { TYPESCRIPT_GO_PACKAGE, TYPESCRIPT_GO_PACKAGE_JSON } from './type-script-go-constants';
6+
import {
7+
getTsgoPackage,
8+
readTsgoPackageJson,
9+
} from './type-script-go-package';
10+
import {
11+
TYPESCRIPT_PACKAGE,
12+
TYPESCRIPT_PACKAGE_JSON,
13+
TYPESCRIPT_PREVIEW_PACKAGE,
14+
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
15+
} from './type-script-go-constants';
716
import {
817
resolveTypeScriptGoBinPath,
918
resolveTypeScriptGoPackageJsonPath,
1019
} from './type-script-go-runner';
1120

1221
function isDefaultTypeScriptGoPath(typescriptPath: string): boolean {
13-
if (typescriptPath === TYPESCRIPT_GO_PACKAGE_JSON) {
22+
if (typescriptPath === TYPESCRIPT_PREVIEW_PACKAGE_JSON) {
1423
return true;
1524
}
1625

1726
try {
18-
return typescriptPath === require.resolve(TYPESCRIPT_GO_PACKAGE_JSON);
27+
if (typescriptPath === require.resolve(TYPESCRIPT_PREVIEW_PACKAGE_JSON)) {
28+
return true;
29+
}
30+
} catch {
31+
// silent catch
32+
}
33+
34+
if (
35+
!path.isAbsolute(typescriptPath) ||
36+
path.basename(typescriptPath) !== 'package.json'
37+
) {
38+
return false;
39+
}
40+
41+
try {
42+
return Boolean(getTsgoPackage(readTsgoPackageJson(typescriptPath)));
1943
} catch {
2044
return false;
2145
}
2246
}
2347

2448
function createTypeScriptGoSupportError(config: TypeScriptWorkerConfig, error?: unknown) {
2549
const message = [
26-
`When you enable TsCheckerRspackPlugin with \`typescript.tsgo\`, you must install \`${TYPESCRIPT_GO_PACKAGE}\` package.`,
50+
`When you enable TsCheckerRspackPlugin with \`typescript.tsgo\`, you must install \`${TYPESCRIPT_PACKAGE}@rc\` or \`${TYPESCRIPT_PREVIEW_PACKAGE}\` package.`,
2751
];
2852

2953
if (!isDefaultTypeScriptGoPath(config.typescriptPath)) {
3054
message.push(
31-
`If you set \`typescript.typescriptPath\`, it must be an absolute path to \`${TYPESCRIPT_GO_PACKAGE_JSON}\`.`,
55+
`If you set \`typescript.typescriptPath\`, it must be an absolute path to \`${TYPESCRIPT_PACKAGE_JSON}\` from \`${TYPESCRIPT_PACKAGE}@rc\` or \`${TYPESCRIPT_PREVIEW_PACKAGE_JSON}\`.`,
3256
);
3357
}
3458

3559
if (error instanceof Error && error.message) {
3660
message.push(`Failed to resolve the tsgo executable: ${error.message}`);
3761
}
3862

39-
message.push(`You can install it with \`npm add ${TYPESCRIPT_GO_PACKAGE} -D\`.`);
63+
message.push(
64+
`You can install it with \`npm add ${TYPESCRIPT_PACKAGE}@rc -D\` or \`npm add ${TYPESCRIPT_PREVIEW_PACKAGE} -D\`.`,
65+
);
4066

4167
return new Error(message.join(os.EOL));
4268
}
4369

4470
function assertTypeScriptGoSupport(config: TypeScriptWorkerConfig) {
4571
try {
4672
const tsgoPackageJsonPath = resolveTypeScriptGoPackageJsonPath(config);
47-
const getExePathPath = path.resolve(path.dirname(tsgoPackageJsonPath), './lib/getExePath.js');
73+
const getExePathPath = path.resolve(
74+
path.dirname(tsgoPackageJsonPath),
75+
'./lib/getExePath.js',
76+
);
4877

4978
if (!fs.existsSync(getExePathPath)) {
5079
throw new Error();
@@ -66,6 +95,12 @@ function assertTypeScriptSupport(config: TypeScriptWorkerConfig) {
6695
if (config.tsgo) {
6796
assertTypeScriptGoSupport(config);
6897
} else {
98+
if (path.basename(config.typescriptPath) === 'package.json') {
99+
throw new Error(
100+
"When you use TsCheckerRspackPlugin without `typescript.tsgo`, `typescript.typescriptPath` should point to a path like `require.resolve('typescript')`.",
101+
);
102+
}
103+
69104
let typescriptVersion: string | undefined;
70105

71106
try {

0 commit comments

Comments
 (0)