Files
esengine/packages/worker-generator/src/cli.ts
YHH dfd0dfc7f9 feat(worker): 添加微信小游戏 Worker 支持和 Worker Generator CLI (#297)
* 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 进行安全可靠的代码转换
- 移除所有可能导致回溯的正则表达式
2025-12-08 17:02:11 +08:00

173 lines
6.5 KiB
JavaScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/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();