Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 33 additions & 16 deletions README.md

Large diffs are not rendered by default.

14 changes: 11 additions & 3 deletions src/typescript/type-script-go-constants.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
const TYPESCRIPT_GO_PACKAGE = '@typescript/native-preview';
const TYPESCRIPT_GO_PACKAGE_JSON = `${TYPESCRIPT_GO_PACKAGE}/package.json`;
const TYPESCRIPT_PACKAGE = 'typescript';
const TYPESCRIPT_PACKAGE_JSON = `${TYPESCRIPT_PACKAGE}/package.json`;
const TYPESCRIPT_PREVIEW_PACKAGE = '@typescript/native-preview';
const TYPESCRIPT_PREVIEW_PACKAGE_JSON = `${TYPESCRIPT_PREVIEW_PACKAGE}/package.json`;
const TYPESCRIPT_GO_ISSUE_CODE = 'TSGO';

export { TYPESCRIPT_GO_PACKAGE, TYPESCRIPT_GO_PACKAGE_JSON, TYPESCRIPT_GO_ISSUE_CODE };
export {
TYPESCRIPT_GO_ISSUE_CODE,
TYPESCRIPT_PACKAGE,
TYPESCRIPT_PACKAGE_JSON,
TYPESCRIPT_PREVIEW_PACKAGE,
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
};
70 changes: 70 additions & 0 deletions src/typescript/type-script-go-package.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import path from 'node:path';

import {
TYPESCRIPT_PACKAGE,
TYPESCRIPT_PREVIEW_PACKAGE,
} from './type-script-go-constants';

type TypeScriptGoPackage = 'typescript' | 'preview';

type TypeScriptGoPackageJson = {
name?: string;
version?: string;
bin?: string | Record<string, string>;
};

type ResolvedTypeScriptGoPackage = {
packageJsonPath: string;
tsgoPackage: TypeScriptGoPackage;
};

function readTsgoPackageJson(packageJsonPath: string): TypeScriptGoPackageJson {
return require(packageJsonPath) as TypeScriptGoPackageJson;
}

function getTsgoPackage(packageJson: TypeScriptGoPackageJson): TypeScriptGoPackage | undefined {
if (packageJson.name === TYPESCRIPT_PACKAGE) {
const versionMatch = packageJson.version?.match(/^(\d+)\.(\d+)(?:\.|$|-)/);

if (versionMatch && Number(versionMatch[1]) >= 7) {
return 'typescript';
}
}

if (packageJson.name === TYPESCRIPT_PREVIEW_PACKAGE) {
return 'preview';
}

return undefined;
}

function resolveTypeScriptGoPackage(
packageJsonPath: string,
): ResolvedTypeScriptGoPackage | undefined {
if (
!path.isAbsolute(packageJsonPath) ||
path.basename(packageJsonPath) !== 'package.json'
) {
return undefined;
}

try {
const tsgoPackage = getTsgoPackage(readTsgoPackageJson(packageJsonPath));

return tsgoPackage ? { packageJsonPath, tsgoPackage } : undefined;
} catch {
return undefined;
}
}

export {
getTsgoPackage,
readTsgoPackageJson,
resolveTypeScriptGoPackage,
};

export type {
ResolvedTypeScriptGoPackage,
TypeScriptGoPackage,
TypeScriptGoPackageJson,
};
65 changes: 55 additions & 10 deletions src/typescript/type-script-go-runner.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { spawn } from 'node:child_process';
import fs from 'node:fs';
import path from 'node:path';
import { pathToFileURL } from 'node:url';

Expand All @@ -9,35 +10,78 @@ import { AbortError } from '../utils/async/abort-error';
import type { TypeScriptWorkerConfig } from './type-script-worker-config';
import {
TYPESCRIPT_GO_ISSUE_CODE,
TYPESCRIPT_GO_PACKAGE,
TYPESCRIPT_GO_PACKAGE_JSON,
TYPESCRIPT_PACKAGE,
TYPESCRIPT_PREVIEW_PACKAGE,
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
} from './type-script-go-constants';

type TypeScriptGoExecutable = {
command: string;
args: string[];
};

function resolveTypeScriptGoPackageJsonPath(config: TypeScriptWorkerConfig): string {
if (
!path.isAbsolute(config.typescriptPath) ||
path.basename(config.typescriptPath) !== 'package.json'
) {
throw new Error(
`The typescriptPath option must be an absolute path to "${TYPESCRIPT_GO_PACKAGE_JSON}" when tsgo is enabled.`,
`The typescriptPath option must be an absolute path to a package.json file when tsgo is enabled.`,
);
}

if (!config.tsgoPackage) {
throw new Error(
`The typescriptPath option must point to "${TYPESCRIPT_PREVIEW_PACKAGE_JSON}" or "${TYPESCRIPT_PACKAGE}@rc".`,
);
}

return config.typescriptPath;
}

async function resolveTypeScriptGoBinPath(config: TypeScriptWorkerConfig): Promise<string> {
const tsgoPkgPath = resolveTypeScriptGoPackageJsonPath(config);
const getExePathPath = path.resolve(path.dirname(tsgoPkgPath), './lib/getExePath.js');
async function resolveTypeScriptGoNativeExecutablePath(
packageJsonPath: string,
packageName: string,
): Promise<string> {
const getExePathPath = path.resolve(path.dirname(packageJsonPath), './lib/getExePath.js');
const getExePathUrl = pathToFileURL(getExePathPath).href;
const getExePathModule = await import(getExePathUrl);
const getExePath = getExePathModule.default || getExePathModule.getExePath;

if (typeof getExePath !== 'function') {
throw new Error(`Cannot resolve the typescript-go executable from "${TYPESCRIPT_GO_PACKAGE}".`);
throw new Error(
`Cannot resolve the typescript-go executable from "${packageName}".`,
);
}

const executablePath = getExePath();

if (typeof executablePath !== 'string' || !fs.existsSync(executablePath)) {
throw new Error('Executable not found');
}

return executablePath;
}

async function resolveTypeScriptGoBinPath(config: TypeScriptWorkerConfig): Promise<string> {
const packageJsonPath = resolveTypeScriptGoPackageJsonPath(config);

if (config.tsgoPackage === 'typescript') {
return resolveTypeScriptGoNativeExecutablePath(packageJsonPath, TYPESCRIPT_PACKAGE);
}

return getExePath();
return resolveTypeScriptGoNativeExecutablePath(packageJsonPath, TYPESCRIPT_PREVIEW_PACKAGE);
}

async function resolveTypeScriptGoExecutable(
config: TypeScriptWorkerConfig,
): Promise<TypeScriptGoExecutable> {
const binPath = await resolveTypeScriptGoBinPath(config);

return {
command: binPath,
args: [],
};
}

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

const binPath = await resolveTypeScriptGoBinPath(config);
const executable = await resolveTypeScriptGoExecutable(config);
const args = createTypeScriptGoArgs(config);

return new Promise((resolve, reject) => {
let settled = false;
let output = '';
const childProcess = spawn(binPath, args, {
const childProcess = spawn(executable.command, [...executable.args, ...args], {
cwd: config.context,
stdio: ['inherit', 'pipe', 'pipe'],
});
Expand Down Expand Up @@ -312,6 +356,7 @@ export {
isTypeScriptGoIssue,
isTypeScriptGoStatsError,
parseTypeScriptGoIssues,
resolveTypeScriptGoExecutable,
resolveTypeScriptGoBinPath,
resolveTypeScriptGoPackageJsonPath,
runTypeScriptGo,
Expand Down
49 changes: 42 additions & 7 deletions src/typescript/type-script-support.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,48 +3,77 @@ import fs from 'node:fs';
import path from 'node:path';

import type { TypeScriptWorkerConfig } from './type-script-worker-config';
import { TYPESCRIPT_GO_PACKAGE, TYPESCRIPT_GO_PACKAGE_JSON } from './type-script-go-constants';
import {
getTsgoPackage,
readTsgoPackageJson,
} from './type-script-go-package';
import {
TYPESCRIPT_PACKAGE,
TYPESCRIPT_PACKAGE_JSON,
TYPESCRIPT_PREVIEW_PACKAGE,
TYPESCRIPT_PREVIEW_PACKAGE_JSON,
} from './type-script-go-constants';
import {
resolveTypeScriptGoBinPath,
resolveTypeScriptGoPackageJsonPath,
} from './type-script-go-runner';

function isDefaultTypeScriptGoPath(typescriptPath: string): boolean {
if (typescriptPath === TYPESCRIPT_GO_PACKAGE_JSON) {
if (typescriptPath === TYPESCRIPT_PREVIEW_PACKAGE_JSON) {
return true;
}

try {
return typescriptPath === require.resolve(TYPESCRIPT_GO_PACKAGE_JSON);
if (typescriptPath === require.resolve(TYPESCRIPT_PREVIEW_PACKAGE_JSON)) {
return true;
}
} catch {
// silent catch
}

if (
!path.isAbsolute(typescriptPath) ||
path.basename(typescriptPath) !== 'package.json'
) {
return false;
}

try {
return Boolean(getTsgoPackage(readTsgoPackageJson(typescriptPath)));
} catch {
return false;
}
}

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

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

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

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

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

function assertTypeScriptGoSupport(config: TypeScriptWorkerConfig) {
try {
const tsgoPackageJsonPath = resolveTypeScriptGoPackageJsonPath(config);
const getExePathPath = path.resolve(path.dirname(tsgoPackageJsonPath), './lib/getExePath.js');
const getExePathPath = path.resolve(
path.dirname(tsgoPackageJsonPath),
'./lib/getExePath.js',
);

if (!fs.existsSync(getExePathPath)) {
throw new Error();
Expand All @@ -66,6 +95,12 @@ function assertTypeScriptSupport(config: TypeScriptWorkerConfig) {
if (config.tsgo) {
assertTypeScriptGoSupport(config);
} else {
if (path.basename(config.typescriptPath) === 'package.json') {
throw new Error(
"When you use TsCheckerRspackPlugin without `typescript.tsgo`, `typescript.typescriptPath` should point to a path like `require.resolve('typescript')`.",
);
}

let typescriptVersion: string | undefined;

try {
Expand Down
Loading
Loading