* feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI - 新增 @esengine/worker-generator 包,用于从 WorkerEntitySystem 生成 Worker 文件 - WorkerEntitySystem 添加 workerScriptPath 配置项,支持预编译 Worker 脚本 - CLI 工具支持 --wechat 模式,自动转换 ES6+ 为 ES5 语法 - 修复微信小游戏 Worker 消息格式差异(res 直接是数据,无需 .data) - 更新中英文文档,添加微信小游戏支持章节 * docs: 更新 changelog,添加 v2.3.1 说明并标注 v2.3.0 为废弃 * fix: 修复 CI 检查问题 - 移除 cli.ts 中未使用的 toKebabCase 函数 - 修复 generator.ts 中正则表达式的 ReDoS 风险(使用 [ \t] 替代 \s*) - 更新 changelog 版本号(2.3.1 -> 2.3.2) * docs: 移除未发布版本的 changelog 条目 * fix(worker-generator): 使用 TypeScript 编译器替代手写正则进行 ES5 转换 - 修复 CodeQL 检测的 ReDoS 安全问题 - 使用 ts.transpileModule 进行安全可靠的代码转换 - 移除所有可能导致回溯的正则表达式
173 lines
6.5 KiB
JavaScript
173 lines
6.5 KiB
JavaScript
#!/usr/bin/env node
|
||
|
||
/**
|
||
* Worker Generator CLI
|
||
* 从 WorkerEntitySystem 子类生成 Worker 文件
|
||
* Generate Worker files from WorkerEntitySystem subclasses
|
||
*/
|
||
|
||
import { Command } from 'commander';
|
||
import chalk from 'chalk';
|
||
import * as path from 'path';
|
||
import * as fs from 'fs';
|
||
import { parseWorkerSystems } from './parser';
|
||
import { generateWorkerFiles } from './generator';
|
||
import type { GeneratorConfig } from './types';
|
||
|
||
const packageJson = require('../package.json');
|
||
|
||
const program = new Command();
|
||
|
||
program
|
||
.name('esengine-worker-gen')
|
||
.description('Generate Worker files from WorkerEntitySystem classes for WeChat Mini Game and other platforms')
|
||
.version(packageJson.version);
|
||
|
||
program
|
||
.option('-s, --src <dir>', 'Source directory to scan', './src')
|
||
.option('-o, --out <dir>', 'Output directory for Worker files', './workers')
|
||
.option('-w, --wechat', 'Generate WeChat Mini Game compatible code', false)
|
||
.option('-m, --mapping', 'Generate worker-mapping.json file', true)
|
||
.option('-t, --tsconfig <path>', 'Path to tsconfig.json')
|
||
.option('-v, --verbose', 'Verbose output', false)
|
||
.action((options) => {
|
||
run(options);
|
||
});
|
||
|
||
function run(options: {
|
||
src: string;
|
||
out: string;
|
||
wechat: boolean;
|
||
mapping: boolean;
|
||
tsconfig?: string;
|
||
verbose: boolean;
|
||
}) {
|
||
console.log(chalk.cyan('\n🔧 ESEngine Worker Generator\n'));
|
||
|
||
// 解析路径
|
||
// Resolve paths
|
||
const srcDir = path.resolve(process.cwd(), options.src);
|
||
const outDir = path.resolve(process.cwd(), options.out);
|
||
|
||
// 检查源目录是否存在
|
||
// Check if source directory exists
|
||
if (!fs.existsSync(srcDir)) {
|
||
console.error(chalk.red(`Error: Source directory not found: ${srcDir}`));
|
||
process.exit(1);
|
||
}
|
||
|
||
// 查找 tsconfig.json
|
||
// Find tsconfig.json
|
||
let tsConfigPath = options.tsconfig;
|
||
if (!tsConfigPath) {
|
||
const defaultTsConfig = path.join(process.cwd(), 'tsconfig.json');
|
||
if (fs.existsSync(defaultTsConfig)) {
|
||
tsConfigPath = defaultTsConfig;
|
||
}
|
||
}
|
||
|
||
const config: GeneratorConfig = {
|
||
srcDir,
|
||
outDir,
|
||
wechat: options.wechat,
|
||
generateMapping: options.mapping,
|
||
tsConfigPath,
|
||
verbose: options.verbose,
|
||
};
|
||
|
||
console.log(chalk.gray(`Source directory: ${srcDir}`));
|
||
console.log(chalk.gray(`Output directory: ${outDir}`));
|
||
console.log(chalk.gray(`WeChat mode: ${options.wechat ? 'Yes' : 'No'}`));
|
||
if (tsConfigPath) {
|
||
console.log(chalk.gray(`TypeScript config: ${tsConfigPath}`));
|
||
}
|
||
console.log();
|
||
|
||
// 解析源文件
|
||
// Parse source files
|
||
console.log(chalk.yellow('Scanning for WorkerEntitySystem classes...'));
|
||
const systems = parseWorkerSystems(config);
|
||
|
||
if (systems.length === 0) {
|
||
console.log(chalk.yellow('\n⚠️ No WorkerEntitySystem subclasses found.'));
|
||
console.log(chalk.gray('Make sure your classes:'));
|
||
console.log(chalk.gray(' - Extend WorkerEntitySystem'));
|
||
console.log(chalk.gray(' - Have a workerProcess method'));
|
||
console.log(chalk.gray(' - Are in .ts files under the source directory'));
|
||
return;
|
||
}
|
||
|
||
console.log(chalk.green(`\n✓ Found ${systems.length} WorkerEntitySystem class(es):`));
|
||
for (const system of systems) {
|
||
const configStatus = system.workerScriptPath
|
||
? chalk.green(`✓ workerScriptPath: '${system.workerScriptPath}'`)
|
||
: chalk.yellow('⚠ No workerScriptPath configured');
|
||
console.log(chalk.gray(` - ${system.className}`));
|
||
console.log(chalk.gray(` ${path.relative(process.cwd(), system.filePath)}`));
|
||
console.log(` ${configStatus}`);
|
||
}
|
||
console.log();
|
||
|
||
// 生成 Worker 文件
|
||
// Generate Worker files
|
||
console.log(chalk.yellow('Generating Worker files...'));
|
||
const result = generateWorkerFiles(systems, config);
|
||
|
||
// 输出结果
|
||
// Output results
|
||
console.log();
|
||
if (result.success.length > 0) {
|
||
console.log(chalk.green(`✓ Successfully generated ${result.success.length} Worker file(s):`));
|
||
for (const item of result.success) {
|
||
const relativePath = path.relative(process.cwd(), item.outputPath).replace(/\\/g, '/');
|
||
if (item.configuredPath) {
|
||
console.log(chalk.green(` ✓ ${item.className} -> ${relativePath}`));
|
||
} else {
|
||
console.log(chalk.yellow(` ⚠ ${item.className} -> ${relativePath} (需要配置 workerScriptPath)`));
|
||
}
|
||
}
|
||
}
|
||
|
||
if (result.errors.length > 0) {
|
||
console.log(chalk.red(`\n✗ Failed to generate ${result.errors.length} Worker file(s):`));
|
||
for (const item of result.errors) {
|
||
console.log(chalk.red(` - ${item.className}: ${item.error}`));
|
||
}
|
||
}
|
||
|
||
// 提示未配置 workerScriptPath 的类
|
||
// Remind about classes without workerScriptPath
|
||
if (result.skipped.length > 0) {
|
||
console.log(chalk.yellow('\n⚠️ 以下类未配置 workerScriptPath,请在构造函数中添加配置:'));
|
||
console.log(chalk.yellow(' The following classes need workerScriptPath configuration:\n'));
|
||
for (const item of result.skipped) {
|
||
console.log(chalk.white(` // ${item.className}`));
|
||
console.log(chalk.cyan(` super(matcher, {`));
|
||
console.log(chalk.cyan(` workerScriptPath: '${item.suggestedPath}',`));
|
||
console.log(chalk.cyan(` // ... 其他配置`));
|
||
console.log(chalk.cyan(` });`));
|
||
console.log();
|
||
}
|
||
}
|
||
|
||
// 使用提示(只有当有已配置路径的成功项时)
|
||
// Usage tips (only when there are success items with configured path)
|
||
const configuredSuccess = result.success.filter(item => item.configuredPath);
|
||
if (configuredSuccess.length > 0) {
|
||
console.log(chalk.green('\n✅ 已按照代码中的 workerScriptPath 配置生成 Worker 文件!'));
|
||
console.log(chalk.gray(' Worker files generated according to workerScriptPath in your code!'));
|
||
console.log(chalk.gray('\n 下一步 | Next steps:'));
|
||
console.log(chalk.gray(' 1. 确保 game.json 配置了 workers 目录'));
|
||
console.log(chalk.gray(' Ensure game.json has workers directory configured'));
|
||
|
||
if (options.mapping) {
|
||
console.log(chalk.gray('\n 已生成映射文件 | Mapping file generated:'));
|
||
console.log(chalk.white(` import mapping from '${path.relative(process.cwd(), outDir)}/worker-mapping.json'`));
|
||
}
|
||
}
|
||
|
||
console.log();
|
||
}
|
||
|
||
program.parse();
|