feat: 新编译器

This commit is contained in:
Pan Qiancheng 2026-01-04 16:34:44 +08:00
parent 0f2e6171bd
commit a5d940c97b
4 changed files with 10 additions and 193 deletions

View File

@ -49,7 +49,7 @@ const checkers = [
const { orderId, price } = data;
data.refundable = false;
if (orderId) {
// 所有已经支付和正在支付的pay之和不能超过订单总和
// @oak-ignore 所有已经支付和正在支付的pay之和不能超过订单总和
const order = context.select('order', {
data: {
id: 1,

View File

@ -1,190 +1 @@
const ts = require('typescript');
const path = require('path');
const fs = require('fs');
const { cwd } = require('process');
// 解析命令行参数
function parseArgs() {
const args = process.argv.slice(2);
let configPath = 'tsconfig.json';
for (let i = 0; i < args.length; i++) {
if (args[i] === '-p' || args[i] === '--project') {
if (i + 1 < args.length) {
configPath = args[i + 1];
break;
} else {
console.error('error: option \'-p, --project\' argument missing');
process.exit(1);
}
}
}
return configPath;
}
// ANSI 颜色代码
const colors = {
reset: '\x1b[0m',
cyan: '\x1b[36m',
red: '\x1b[91m',
yellow: '\x1b[93m',
gray: '\x1b[90m'
};
function compile(configPath) {
// 读取 tsconfig.json
const configFile = ts.readConfigFile(configPath, ts.sys.readFile);
if (configFile.error) {
console.error(ts.formatDiagnostic(configFile.error, {
getCanonicalFileName: f => f,
getCurrentDirectory: process.cwd,
getNewLine: () => '\n'
}));
process.exit(1);
}
// 解析配置
const parsedConfig = ts.parseJsonConfigFileContent(
configFile.config,
ts.sys,
path.dirname(configPath)
);
if (parsedConfig.errors.length > 0) {
parsedConfig.errors.forEach(diagnostic => {
console.error(ts.formatDiagnostic(diagnostic, {
getCanonicalFileName: f => f,
getCurrentDirectory: process.cwd,
getNewLine: () => '\n'
}));
});
process.exit(1);
}
// 创建编译程序
// 根据配置决定是否使用增量编译
let program;
if (parsedConfig.options.incremental || parsedConfig.options.composite) {
// 对于增量编译,使用 createIncrementalProgram
const host = ts.createIncrementalCompilerHost(parsedConfig.options);
program = ts.createIncrementalProgram({
rootNames: parsedConfig.fileNames,
options: parsedConfig.options,
host: host,
configFileParsingDiagnostics: ts.getConfigFileParsingDiagnostics(parsedConfig),
});
} else {
// 普通编译
program = ts.createProgram({
rootNames: parsedConfig.fileNames,
options: parsedConfig.options,
});
}
// 执行编译
const emitResult = program.emit();
// 获取诊断信息
const allDiagnostics = ts
.getPreEmitDiagnostics(program)
.concat(emitResult.diagnostics);
// 输出诊断信息
allDiagnostics.forEach(diagnostic => {
if (diagnostic.file) {
const { line, character } = diagnostic.file.getLineAndCharacterOfPosition(
diagnostic.start
);
const message = ts.flattenDiagnosticMessageText(
diagnostic.messageText,
'\n'
);
const isError = diagnostic.category === ts.DiagnosticCategory.Error;
const category = isError ? 'error' : 'warning';
const categoryColor = isError ? colors.red : colors.yellow;
console.log(
`${colors.cyan}${diagnostic.file.fileName}${colors.reset}:${colors.yellow}${line + 1}${colors.reset}:${colors.yellow}${character + 1}${colors.reset} - ${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${message}`
);
} else {
const isError = diagnostic.category === ts.DiagnosticCategory.Error;
const category = isError ? 'error' : 'warning';
const categoryColor = isError ? colors.red : colors.yellow;
console.log(
`${categoryColor}${category}${colors.reset} ${colors.gray}TS${diagnostic.code}${colors.reset}: ${ts.flattenDiagnosticMessageText(diagnostic.messageText, '\n')}`
);
}
});
// 输出编译统计
const errorCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Error).length;
const warningCount = allDiagnostics.filter(d => d.category === ts.DiagnosticCategory.Warning).length;
if (errorCount > 0 || warningCount > 0) {
if (allDiagnostics.length > 0) {
console.log('');
}
const parts = [];
if (errorCount > 0) {
parts.push(`${errorCount} error${errorCount !== 1 ? 's' : ''}`);
}
if (warningCount > 0) {
parts.push(`${warningCount} warning${warningCount !== 1 ? 's' : ''}`);
}
console.log(`Found ${parts.join(' and ')}.`);
}
// tsc 的行为:
// 1. 默认情况下(noEmitOnError: false):
// - 即使有类型错误也会生成 .js 文件
// - 但如果有错误,退出码是 1
// 2. noEmitOnError: true 时:
// - 有错误时不生成文件(emitSkipped 为 true)
// - 退出码是 1
// 3. 没有错误时:
// - 生成文件,退出码 0
// 无论 emitSkipped 与否,只要有错误就应该退出 1
if (errorCount > 0) {
process.exit(1);
}
console.log('Compilation completed successfully.');
}
// 执行编译
const configPathArg = parseArgs();
let configPath;
// 判断参数是目录还是文件
if (fs.existsSync(configPathArg)) {
const stat = fs.statSync(configPathArg);
if (stat.isDirectory()) {
// 如果是目录,拼接 tsconfig.json
configPath = path.resolve(configPathArg, 'tsconfig.json');
} else {
// 如果是文件,直接使用
configPath = path.resolve(configPathArg);
}
} else {
// 尝试作为相对路径解析
configPath = path.resolve(cwd(), configPathArg);
if (!fs.existsSync(configPath)) {
// 如果还是不存在,尝试添加 tsconfig.json
const dirPath = path.resolve(cwd(), configPathArg);
if (fs.existsSync(dirPath) && fs.statSync(dirPath).isDirectory()) {
configPath = path.join(dirPath, 'tsconfig.json');
}
}
}
if (!fs.existsSync(configPath)) {
console.error(`error TS5058: The specified path does not exist: '${configPath}'.`);
process.exit(1);
}
compile(configPath);
require('oak-domain/lib/compiler/tscBuilder.js')

View File

@ -54,7 +54,7 @@ const checkers: Checker<EntityDict, 'pay', RuntimeCxt>[] = [
data.refundable = false;
if (orderId) {
// 所有已经支付和正在支付的pay之和不能超过订单总和
// @oak-ignore 所有已经支付和正在支付的pay之和不能超过订单总和
const order = context.select('order', {
data: {
id: 1,

View File

@ -91,5 +91,11 @@
"package-lock.json",
"test",
"scripts"
]
],
"oakBuildChecks": {
"context": {
"checkAsyncContext": true,
"targetModules": ["context/BackendRuntimeContext"]
}
}
}