diff --git a/.changeset/clever-dragons-smile.md b/.changeset/clever-dragons-smile.md new file mode 100644 index 00000000..b96c54eb --- /dev/null +++ b/.changeset/clever-dragons-smile.md @@ -0,0 +1,11 @@ +--- +"@esengine/cli": minor +--- + +feat(cli): 添加 CLI 工具用于将 ECS 框架集成到现有项目 + +- 支持 Cocos Creator 2.x/3.x、LayaAir 3.x、Node.js 平台 +- 自动检测项目类型 +- 生成完整配置的 ECSManager(调试模式、远程调试、WebSocket URL) +- 自动安装依赖(支持 npm/yarn/pnpm) +- 针对不同平台生成正确的装饰器和生命周期方法 diff --git a/.github/labeler.yml b/.github/labeler.yml deleted file mode 100644 index 54aaab71..00000000 --- a/.github/labeler.yml +++ /dev/null @@ -1,32 +0,0 @@ -# 自动标签配置 -# 根据 issue/PR 内容自动打标签 - -'bug': - - '/(bug|错误|崩溃|crash|error|exception|问题)/i' - -'enhancement': - - '/(feature|功能|enhancement|improve|优化|建议)/i' - -'documentation': - - '/(doc|文档|readme|guide|tutorial|教程)/i' - -'question': - - '/(question|疑问|how to|如何|怎么)/i' - -'performance': - - '/(performance|性能|slow|慢|lag|卡顿|optimize)/i' - -'core': - - '/(@esengine\/ecs-framework|packages\/core|core package)/i' - -'editor': - - '/(editor|编辑器|tauri)/i' - -'network': - - '/(network|网络|multiplayer|多人)/i' - -'help wanted': - - '/(help wanted|需要帮助|求助)/i' - -'good first issue': - - '/(good first issue|新手友好|beginner)/i' diff --git a/.github/workflows/ai-batch-analyze-issues.yml b/.github/workflows/ai-batch-analyze-issues.yml deleted file mode 100644 index c81c0c08..00000000 --- a/.github/workflows/ai-batch-analyze-issues.yml +++ /dev/null @@ -1,73 +0,0 @@ -name: AI Batch Analyze Issues - -on: - workflow_dispatch: - inputs: - mode: - description: '分析模式' - required: true - type: choice - options: - - 'recent' # 最近 10 个 issue - - 'open' # 所有打开的 issue - - 'all' # 所有 issue(慎用) - default: 'recent' - -permissions: - issues: write - contents: read - -jobs: - analyze: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Install GitHub CLI - run: | - gh --version || (curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg | sudo dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg - echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" | sudo tee /etc/apt/sources.list.d/github-cli.list > /dev/null - sudo apt update - sudo apt install gh) - - - name: Batch Analyze Issues - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - MODE="${{ github.event.inputs.mode }}" - - # 获取 issue 列表 - if [ "$MODE" = "recent" ]; then - echo "📊 分析最近 10 个 issue..." - ISSUES=$(gh issue list --limit 10 --json number --jq '.[].number') - elif [ "$MODE" = "open" ]; then - echo "📊 分析所有打开的 issue..." - ISSUES=$(gh issue list --state open --json number --jq '.[].number') - else - echo "📊 分析所有 issue(这可能需要很长时间)..." - ISSUES=$(gh issue list --state all --limit 100 --json number --jq '.[].number') - fi - - # 为每个 issue 添加 AI 分析评论 - for issue_num in $ISSUES; do - echo "🤖 分析 Issue #$issue_num..." - - # 获取 issue 内容 - ISSUE_BODY=$(gh issue view $issue_num --json body --jq '.body') - ISSUE_TITLE=$(gh issue view $issue_num --json title --jq '.title') - - # 添加触发评论 - gh issue comment $issue_num --body "@ai-helper 请帮我分析这个 issue" || true - - # 避免 API 限制 - sleep 2 - done - - echo "✅ 批量分析完成!" - echo "查看结果:https://github.com/${{ github.repository }}/issues" diff --git a/.github/workflows/ai-helper-tip.yml b/.github/workflows/ai-helper-tip.yml deleted file mode 100644 index b6915000..00000000 --- a/.github/workflows/ai-helper-tip.yml +++ /dev/null @@ -1,61 +0,0 @@ -name: AI Helper Tip - -# 对所有新创建的 issue 自动回复 AI 助手使用说明(新老用户都适用) -on: - issues: - types: [opened] - -permissions: - issues: write - -jobs: - tip: - runs-on: ubuntu-latest - steps: - - name: Post AI Helper Usage Tip - uses: actions/github-script@v7 - with: - script: | - const message = [ - "## 🤖 AI 助手可用 | AI Helper Available", - "", - "**中文说明:**", - "", - "本项目配备了 AI 智能助手,可以帮助你快速获得解答!", - "", - "**使用方法:** 在评论中提及 `@ai-helper`,AI 会自动搜索项目代码并提供解决方案。", - "", - "**示例:**", - "```", - "@ai-helper 如何创建一个新的 System?", - "@ai-helper 这个报错是什么原因?", - "```", - "", - "---", - "", - "**English:**", - "", - "This project has an AI assistant to help you get answers quickly!", - "", - "**How to use:** Mention `@ai-helper` in a comment, and AI will automatically search the codebase and provide solutions.", - "", - "**Examples:**", - "```", - "@ai-helper How do I create a new System?", - "@ai-helper What causes this error?", - "```", - "", - "---", - "", - "💡 *AI 助手基于代码库提供建议,复杂问题建议等待维护者回复*", - "💡 *AI suggestions are based on the codebase. For complex issues, please wait for maintainer responses*" - ].join('\n'); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: message - }); - - console.log('✅ AI helper tip posted successfully'); diff --git a/.github/workflows/ai-issue-helper.yml b/.github/workflows/ai-issue-helper.yml deleted file mode 100644 index bc628304..00000000 --- a/.github/workflows/ai-issue-helper.yml +++ /dev/null @@ -1,80 +0,0 @@ -name: AI Issue Helper - -on: - issue_comment: - types: [created] - -permissions: - issues: write - contents: read - models: read - -jobs: - ai-helper: - runs-on: ubuntu-latest - # 只在真实用户提到 @ai-helper 时触发,忽略机器人评? if: | - contains(github.event.comment.body, '@ai-helper') && - github.event.comment.user.type != 'Bot' - steps: - - name: Checkout Repository - uses: actions/checkout@v4 - - - name: Get Issue Details - id: issue - uses: actions/github-script@v7 - with: - script: | - const issue = await github.rest.issues.get({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number - }); - - // 限制长度,避免超?token 限制 - const maxLength = 1000; - const truncate = (str, max) => { - if (!str) return ''; - return str.length > max ? str.substring(0, max) + '...[内容过长已截断]' : str; - }; - - core.exportVariable('ISSUE_TITLE', truncate(issue.data.title || '', 200)); - core.exportVariable('ISSUE_BODY', truncate(issue.data.body || '', maxLength)); - core.exportVariable('COMMENT_BODY', truncate(context.payload.comment.body || '', 500)); - core.exportVariable('ISSUE_NUMBER', context.issue.number); - - - name: Create Prompt - id: prompt - run: | - cat > prompt.txt << 'PROMPT_EOF' - Issue #${{ env.ISSUE_NUMBER }} - - 标题: ${{ env.ISSUE_TITLE }} - - 内容: ${{ env.ISSUE_BODY }} - - 评论: ${{ env.COMMENT_BODY }} - - 请搜索项目代码并提供解决方案? PROMPT_EOF - - - name: AI Analysis - uses: actions/ai-inference@v1 - id: ai - with: - model: 'gpt-4o' - enable-github-mcp: true - max-tokens: 1500 - system-prompt: | - 你是 ECS Framework (TypeScript ECS 框架) ?AI 助手? 主要代码?packages/framework/core/src? 搜索相关代码后,用中文简洁回答问题,包含问题分析、解决方案和代码引用? prompt-file: prompt.txt - - - name: Post AI Response - env: - AI_RESPONSE: ${{ steps.ai.outputs.response }} - uses: actions/github-script@v7 - with: - script: | - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: process.env.AI_RESPONSE - }); diff --git a/.github/workflows/ai-issue-moderator.yml b/.github/workflows/ai-issue-moderator.yml deleted file mode 100644 index a50bf6f5..00000000 --- a/.github/workflows/ai-issue-moderator.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: AI Issue Moderator - -on: - issues: - types: [opened] - issue_comment: - types: [created] - -permissions: - issues: write - contents: read - models: read - -jobs: - moderate: - runs-on: ubuntu-latest - steps: - - name: Check Content - uses: actions/ai-inference@v1 - id: check - with: - model: 'gpt-4o-mini' - system-prompt: | - 你是一个内容审查助手。 - 检查内容是否包含: - 1. 垃圾信息或广告 - 2. 恶意或攻击性内容 - 3. 与项目完全无关的内容 - - 只返回 "SPAM" 或 "OK",不要其他内容。 - prompt: | - 标题:${{ github.event.issue.title || github.event.comment.body }} - - 内容: - ${{ github.event.issue.body || github.event.comment.body }} - - - name: Mark as Spam - if: contains(steps.check.outputs.response, 'SPAM') - uses: actions/github-script@v7 - with: - script: | - // 添加 spam 标签 - github.rest.issues.addLabels({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - labels: ['spam'] - }); - - // 添加评论 - github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: context.issue.number, - body: '🤖 这个内容被 AI 检测为可能的垃圾内容。如果这是误判,请联系维护者。\n\n🤖 This content was detected as potential spam by AI. If this is a false positive, please contact the maintainers.' - }); diff --git a/.github/workflows/batch-label-issues.yml b/.github/workflows/batch-label-issues.yml deleted file mode 100644 index 0593ad24..00000000 --- a/.github/workflows/batch-label-issues.yml +++ /dev/null @@ -1,160 +0,0 @@ -name: Batch Label Issues - -on: - workflow_dispatch: - inputs: - mode: - description: '标签模式' - required: true - type: choice - options: - - 'recent' # 最?20 ?issue - - 'open' # 所有打开?issue - - 'unlabeled' # 只处理没有标签的 issue - - 'all' # 所?issue(慎用) - default: 'recent' - - skip_labeled: - description: '跳过已有标签?issue' - required: false - type: boolean - default: true - -permissions: - issues: write - contents: read - -jobs: - batch-label: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - - - name: Batch Label Issues - env: - GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: | - MODE="${{ github.event.inputs.mode }}" - SKIP_LABELED="${{ github.event.inputs.skip_labeled }}" - - echo "📊 开始批量打标签..." - echo "模式: $MODE" - echo "跳过已标? $SKIP_LABELED" - - # 获取 issue 列表 - if [ "$MODE" = "recent" ]; then - echo "📋 获取最?20 ?issue..." - ISSUES=$(gh issue list --limit 20 --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}') - elif [ "$MODE" = "open" ]; then - echo "📋 获取所有打开?issue..." - ISSUES=$(gh issue list --state open --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}') - elif [ "$MODE" = "unlabeled" ]; then - echo "📋 获取没有标签?issue..." - ISSUES=$(gh issue list --state all --json number,labels,title,body --jq '.[] | select(.labels | length == 0) | {number, labels: [.labels[].name], title, body}') - else - echo "📋 获取所?issue(限?100 个)..." - ISSUES=$(gh issue list --state all --limit 100 --json number,labels,title,body --jq '.[] | {number, labels: [.labels[].name], title, body}') - fi - - # 临时文件 - echo "$ISSUES" > /tmp/issues.json - - # 处理每个 issue - cat /tmp/issues.json | jq -c '.' | while read -r issue; do - ISSUE_NUM=$(echo "$issue" | jq -r '.number') - EXISTING_LABELS=$(echo "$issue" | jq -r '.labels | join(",")') - TITLE=$(echo "$issue" | jq -r '.title') - BODY=$(echo "$issue" | jq -r '.body') - - echo "" - echo "🔍 处理 Issue #$ISSUE_NUM: $TITLE" - echo " 现有标签: $EXISTING_LABELS" - - # 跳过已有标签?issue - if [ "$SKIP_LABELED" = "true" ] && [ ! -z "$EXISTING_LABELS" ]; then - echo " ⏭️ 跳过(已有标签)" - continue - fi - - # 分析内容并打标签 - LABELS_TO_ADD="" - - # 检?bug - if echo "$TITLE $BODY" | grep -iE "(bug|错误|崩溃|crash|error|exception|问题|fix)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD bug" - echo " 🐛 检测到: bug" - fi - - # 检?feature request - if echo "$TITLE $BODY" | grep -iE "(feature|功能|enhancement|improve|优化|建议|新增|添加|add)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD enhancement" - echo " ?检测到: enhancement" - fi - - # 检?question - if echo "$TITLE $BODY" | grep -iE "(question|疑问|how to|如何|怎么|为什么|why|咨询|\?|?" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD question" - echo " ?检测到: question" - fi - - # 检?documentation - if echo "$TITLE $BODY" | grep -iE "(doc|文档|readme|guide|tutorial|教程|说明)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD documentation" - echo " 📖 检测到: documentation" - fi - - # 检?performance - if echo "$TITLE $BODY" | grep -iE "(performance|性能|slow|慢|lag|卡顿|optimize|优化)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD performance" - echo " ?检测到: performance" - fi - - # 检?core - if echo "$TITLE $BODY" | grep -iE "(@esengine/ecs-framework|packages/framework/core|core package|核心?" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD core" - echo " 🎯 检测到: core" - fi - - # 检?editor - if echo "$TITLE $BODY" | grep -iE "(editor|编辑器|tauri)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD editor" - echo " 🎨 检测到: editor" - fi - - # 检?network - if echo "$TITLE $BODY" | grep -iE "(network|网络|multiplayer|多人|同步)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD network" - echo " 🌐 检测到: network" - fi - - # 检?help wanted - if echo "$TITLE $BODY" | grep -iE "(help wanted|需要帮助|求助)" > /dev/null; then - LABELS_TO_ADD="$LABELS_TO_ADD help wanted" - echo " 🆘 检测到: help wanted" - fi - - # 添加标签 - if [ ! -z "$LABELS_TO_ADD" ]; then - echo " ?添加标签: $LABELS_TO_ADD" - for label in $LABELS_TO_ADD; do - gh issue edit $ISSUE_NUM --add-label "$label" 2>&1 | grep -v "already exists" || true - done - echo " 💬 添加说明评论..." - gh issue comment $ISSUE_NUM --body $'🤖 自动标签系统检测到?issue 并添加了相关标签。如有误判,请告知维护者。\n\n🤖 Auto-labeling system detected and labeled this issue. Please let maintainers know if this is incorrect.' || true - else - echo " ℹ️ 未检测到明确类型" - fi - - # 避免 API 限制 - sleep 1 - done - - echo "" - echo "?批量标签完成? - echo "查看结果: https://github.com/${{ github.repository }}/issues" diff --git a/.github/workflows/cleanup-dependabot.yml b/.github/workflows/cleanup-dependabot.yml deleted file mode 100644 index 1010e9e4..00000000 --- a/.github/workflows/cleanup-dependabot.yml +++ /dev/null @@ -1,146 +0,0 @@ -name: Cleanup Old Dependabot PRs - -# 手动触发的 workflow,用于清理堆积的 Dependabot PR -on: - workflow_dispatch: - inputs: - days_old: - description: '关闭多少天前创建的 PR(默认 7 天)' - required: false - default: '7' - dry_run: - description: '试运行模式(true=仅显示,不关闭)' - required: false - default: 'true' - type: choice - options: - - 'true' - - 'false' - -jobs: - cleanup: - runs-on: ubuntu-latest - permissions: - pull-requests: write - issues: write - - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: List and Close Old Dependabot PRs - uses: actions/github-script@v7 - with: - script: | - const daysOld = parseInt('${{ github.event.inputs.days_old }}') || 7; - const dryRun = '${{ github.event.inputs.dry_run }}' === 'true'; - const cutoffDate = new Date(); - cutoffDate.setDate(cutoffDate.getDate() - daysOld); - - console.log(`🔍 查找超过 ${daysOld} 天的 Dependabot PR...`); - console.log(`📅 截止日期: ${cutoffDate.toISOString()}`); - console.log(`🏃 模式: ${dryRun ? '试运行(不会实际关闭)' : '实际执行'}`); - console.log('---'); - - // 获取所有 Dependabot PR - const { data: pulls } = await github.rest.pulls.list({ - owner: context.repo.owner, - repo: context.repo.repo, - state: 'open', - per_page: 100 - }); - - const dependabotPRs = pulls.filter(pr => - pr.user.login === 'dependabot[bot]' && - new Date(pr.created_at) < cutoffDate - ); - - console.log(`📊 找到 ${dependabotPRs.length} 个符合条件的 Dependabot PR`); - console.log(''); - - if (dependabotPRs.length === 0) { - console.log('✅ 没有需要清理的 PR'); - return; - } - - // 按类型分组 - const byType = { - dev: [], - prod: [], - actions: [], - other: [] - }; - - for (const pr of dependabotPRs) { - const title = pr.title.toLowerCase(); - const labels = pr.labels.map(l => l.name); - - let type = 'other'; - if (title.includes('dev-dependencies') || title.includes('development')) { - type = 'dev'; - } else if (title.includes('production-dependencies')) { - type = 'prod'; - } else if (labels.includes('github-actions')) { - type = 'actions'; - } - - byType[type].push(pr); - } - - console.log('📋 PR 分类统计:'); - console.log(` 🔧 开发依赖: ${byType.dev.length} 个`); - console.log(` 📦 生产依赖: ${byType.prod.length} 个`); - console.log(` ⚙️ GitHub Actions: ${byType.actions.length} 个`); - console.log(` ❓ 其他: ${byType.other.length} 个`); - console.log(''); - - // 处理每个 PR - for (const pr of dependabotPRs) { - const age = Math.floor((Date.now() - new Date(pr.created_at)) / (1000 * 60 * 60 * 24)); - - console.log(`${dryRun ? '🔍' : '🗑️ '} #${pr.number}: ${pr.title}`); - console.log(` 创建时间: ${pr.created_at} (${age} 天前)`); - console.log(` 链接: ${pr.html_url}`); - - if (!dryRun) { - await github.rest.pulls.update({ - owner: context.repo.owner, - repo: context.repo.repo, - pull_number: pr.number, - state: 'closed' - }); - - await github.rest.issues.createComment({ - owner: context.repo.owner, - repo: context.repo.repo, - issue_number: pr.number, - body: `🤖 **自动关闭旧的 Dependabot PR** - -此 PR 已超过 ${daysOld} 天未合并,已被自动关闭以清理积压。 - -📌 **下一步:** -- Dependabot 已配置为月度运行,届时会创建新的分组更新 -- 新的 Mergify 规则会智能处理不同类型的依赖更新 -- 开发依赖和 GitHub Actions 会自动合并(即使 CI 失败) -- 生产依赖需要 CI 通过才会自动合并 - -如果需要立即应用此更新,请手动更新依赖。 - ---- -*此操作由仓库维护者手动触发的清理工作流执行*` - }); - - console.log(' ✅ 已关闭并添加说明'); - } else { - console.log(' ℹ️ 试运行模式 - 未执行操作'); - } - console.log(''); - } - - console.log('---'); - if (dryRun) { - console.log(`✨ 试运行完成!共发现 ${dependabotPRs.length} 个待清理的 PR`); - console.log('💡 要实际执行清理,请将 dry_run 参数设为 false 重新运行'); - } else { - console.log(`✅ 清理完成!已关闭 ${dependabotPRs.length} 个 Dependabot PR`); - } diff --git a/.github/workflows/issue-labeler.yml b/.github/workflows/issue-labeler.yml deleted file mode 100644 index 1db79962..00000000 --- a/.github/workflows/issue-labeler.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: Issue Labeler - -on: - issues: - types: [opened, edited] - -permissions: - issues: write - contents: read - -jobs: - label: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Label Issues - uses: github/issue-labeler@v3.4 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - configuration-path: .github/labeler.yml - enable-versioned-regex: 1 diff --git a/.github/workflows/issue-translator.yml b/.github/workflows/issue-translator.yml deleted file mode 100644 index 3b783e93..00000000 --- a/.github/workflows/issue-translator.yml +++ /dev/null @@ -1,28 +0,0 @@ -name: Issue Translator - -on: - issue_comment: - types: [created] - issues: - types: [opened] - -permissions: - issues: write - -jobs: - translate: - runs-on: ubuntu-latest - steps: - - name: Translate Issues - uses: tomsun28/issues-translate-action@v2.7 - with: - IS_MODIFY_TITLE: false - # 设置为 true 会修改标题,false 只在评论中添加翻译 - CUSTOM_BOT_NOTE: | -
- 🌏 Translation / 翻译 - - Bot detected the issue body's language is not English, translate it automatically. - 机器人检测到 issue 内容非英文,自动翻译。 - -
diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e144780d..7719f05a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,6 @@ name: Release NPM Packages on: - # 标签触发:支?v* ?{package}-v* 格式 # Tag trigger: supports v* and {package}-v* formats push: tags: @@ -15,12 +14,11 @@ on: - 'physics-rapier2d-v*' - 'worker-generator-v*' - # 保留手动触发选项 - # Keep manual trigger option + # Manual trigger option workflow_dispatch: inputs: package: - description: '选择要发布的?| Select package to publish' + description: 'Select package to publish' required: true type: choice options: @@ -33,7 +31,7 @@ on: - physics-rapier2d - worker-generator version_type: - description: '版本更新类型 | Version bump type' + description: 'Version bump type' required: true type: choice options: @@ -61,11 +59,10 @@ jobs: id: parse run: | if [ "${{ github.event_name }}" = "push" ]; then - # 从标签解析包名和版本 | Parse package and version from tag + # Parse package and version from tag TAG="${GITHUB_REF#refs/tags/}" echo "tag=$TAG" >> $GITHUB_OUTPUT - # 解析格式:v1.0.0 ?package-v1.0.0 # Parse format: v1.0.0 or package-v1.0.0 if [[ "$TAG" =~ ^v([0-9]+\.[0-9]+\.[0-9]+.*)$ ]]; then PACKAGE="core" @@ -82,10 +79,9 @@ jobs: echo "package=$PACKAGE" >> $GITHUB_OUTPUT echo "version=$VERSION" >> $GITHUB_OUTPUT echo "mode=tag" >> $GITHUB_OUTPUT - echo "📦 Package: $PACKAGE" - echo "📌 Version: $VERSION" + echo "Package: $PACKAGE" + echo "Version: $VERSION" else - # 手动触发:从 package.json 读取?bump 版本 # Manual trigger: read from package.json and bump version PACKAGE="${{ github.event.inputs.package }}" echo "package=$PACKAGE" >> $GITHUB_OUTPUT @@ -112,7 +108,6 @@ jobs: PACKAGE="${{ steps.parse.outputs.package }}" EXPECTED_VERSION="${{ steps.parse.outputs.version }}" - # 获取 package.json 中的版本 # Get version from package.json ACTUAL_VERSION=$(node -p "require('./packages/$PACKAGE/package.json').version") @@ -125,7 +120,7 @@ jobs: exit 1 fi - echo "?Version verified: $EXPECTED_VERSION" + echo "Version verified: $EXPECTED_VERSION" - name: Bump version (manual mode) if: steps.parse.outputs.mode == 'manual' @@ -147,7 +142,7 @@ jobs: node -e "const fs=require('fs'); const pkg=JSON.parse(fs.readFileSync('package.json')); pkg.version='$NEW_VERSION'; fs.writeFileSync('package.json', JSON.stringify(pkg, null, 2)+'\n')" echo "version=$NEW_VERSION" >> $GITHUB_OUTPUT - echo "📌 Bumped version: $CURRENT ?$NEW_VERSION" + echo "Bumped version: $CURRENT -> $NEW_VERSION" - name: Set final version id: version @@ -188,19 +183,18 @@ jobs: with: tag_name: ${{ steps.parse.outputs.tag }} name: "${{ steps.parse.outputs.package }} v${{ steps.version.outputs.value }}" - # 不设置为 latest,latest 保留给编辑器热更新使? # Don't mark as latest, reserve latest for editor auto-update make_latest: false body: | - ## 🚀 @esengine/${{ steps.parse.outputs.package }} v${{ steps.version.outputs.value }} + ## @esengine/${{ steps.parse.outputs.package }} v${{ steps.version.outputs.value }} - 📦 **NPM**: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }}) + **NPM**: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }}) ```bash npm install @esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }} ``` --- - *自动发布 | Auto-released by GitHub Actions* + *Auto-released by GitHub Actions* generate_release_notes: true env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} @@ -215,15 +209,16 @@ jobs: delete-branch: true title: "chore(${{ steps.parse.outputs.package }}): Release v${{ steps.version.outputs.value }}" body: | - ## 🚀 Release v${{ steps.version.outputs.value }} + ## Release v${{ steps.version.outputs.value }} - ?PR 更新 `@esengine/${{ steps.parse.outputs.package }}` 包的版本? - ### 变更 - - ?已发布到 npm: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }}) - - ?更新 `packages/${{ steps.parse.outputs.package }}/package.json` ?`${{ steps.version.outputs.value }}` + This PR updates `@esengine/${{ steps.parse.outputs.package }}` package version. + + ### Changes + - Published to npm: [@esengine/${{ steps.parse.outputs.package }}@${{ steps.version.outputs.value }}](https://www.npmjs.com/package/@esengine/${{ steps.parse.outputs.package }}/v/${{ steps.version.outputs.value }}) + - Updated `packages/${{ steps.parse.outputs.package }}/package.json` to `${{ steps.version.outputs.value }}` --- - *?PR 由发布工作流自动创建* + *This PR was automatically created by the release workflow* labels: | release ${{ steps.parse.outputs.package }} diff --git a/.github/workflows/size-limit.yml b/.github/workflows/size-limit.yml deleted file mode 100644 index f8a1c7b1..00000000 --- a/.github/workflows/size-limit.yml +++ /dev/null @@ -1,46 +0,0 @@ -name: Size Limit - -on: - pull_request: - branches: - - master - - main - paths: - - 'packages/framework/core/src/**' - - 'packages/framework/core/package.json' - - '.size-limit.json' - -permissions: - contents: read - pull-requests: write - issues: write - -jobs: - size: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Install pnpm - uses: pnpm/action-setup@v4 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - - name: Build core package - run: | - cd packages/framework/core - pnpm run build:npm - - - name: Check bundle size - uses: andresz1/size-limit-action@v1 - with: - github_token: ${{ secrets.GITHUB_TOKEN }} - skip_step: install diff --git a/.github/workflows/welcome.yml b/.github/workflows/welcome.yml deleted file mode 100644 index 2079048e..00000000 --- a/.github/workflows/welcome.yml +++ /dev/null @@ -1,58 +0,0 @@ -name: Welcome - -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - -permissions: - issues: write - pull-requests: write - -jobs: - welcome: - runs-on: ubuntu-latest - steps: - - name: Welcome new contributors - uses: actions/first-interaction@v1.3.0 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - issue-message: | - 👋 你好!感谢你提交第一个 issue! - - 我们会尽快查看并回复。同时,建议你: - - 📚 查看[文档](https://esengine.github.io/ecs-framework/) - - 🤖 使用 [AI 文档助手](https://deepwiki.com/esengine/esengine) - - 💬 加入 [QQ 交流群](https://jq.qq.com/?_wv=1027&k=29w1Nud6) - - --- - - 👋 Hello! Thanks for opening your first issue! - - We'll review it as soon as possible. Meanwhile, you might want to: - - 📚 Check the [documentation](https://esengine.github.io/ecs-framework/) - - 🤖 Use [AI documentation assistant](https://deepwiki.com/esengine/esengine) - - pr-message: | - 👋 你好!感谢你提交第一个 Pull Request! - - 在我们 Review 之前,请确保: - - ✅ 代码遵循项目规范 - - ✅ 通过所有测试 - - ✅ 更新了相关文档 - - ✅ Commit 遵循 [Conventional Commits](https://www.conventionalcommits.org/) 规范 - - 查看完整的[贡献指南](https://github.com/esengine/esengine/blob/master/CONTRIBUTING.md)。 - - --- - - 👋 Hello! Thanks for your first Pull Request! - - Before we review, please ensure: - - ✅ Code follows project conventions - - ✅ All tests pass - - ✅ Documentation is updated - - ✅ Commits follow [Conventional Commits](https://www.conventionalcommits.org/) - - See the full [Contributing Guide](https://github.com/esengine/esengine/blob/master/CONTRIBUTING.md). diff --git a/packages/tools/cli/package.json b/packages/tools/cli/package.json new file mode 100644 index 00000000..710748e8 --- /dev/null +++ b/packages/tools/cli/package.json @@ -0,0 +1,51 @@ +{ + "name": "@esengine/cli", + "version": "1.0.0", + "description": "CLI tool for adding ESEngine ECS framework to existing projects", + "type": "module", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "bin": { + "esengine": "./dist/cli.js" + }, + "exports": { + ".": { + "import": "./dist/index.js", + "types": "./dist/index.d.ts" + } + }, + "files": [ + "dist" + ], + "scripts": { + "build": "tsc", + "build:watch": "tsc --watch", + "dev": "ts-node src/cli.ts", + "clean": "rimraf dist", + "prepublishOnly": "pnpm run build" + }, + "keywords": [ + "esengine", + "ecs", + "cli", + "cocos", + "laya", + "game-framework" + ], + "author": "yhh", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.2", + "commander": "^12.1.0", + "prompts": "^2.4.2" + }, + "devDependencies": { + "@types/node": "^20.19.0", + "@types/prompts": "^2.4.9", + "rimraf": "^5.0.0", + "typescript": "^5.8.3" + }, + "publishConfig": { + "access": "public" + } +} diff --git a/packages/tools/cli/src/adapters/cocos.ts b/packages/tools/cli/src/adapters/cocos.ts new file mode 100644 index 00000000..cfbe9369 --- /dev/null +++ b/packages/tools/cli/src/adapters/cocos.ts @@ -0,0 +1,254 @@ +import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js'; + +/** + * @zh Cocos Creator 3.x 平台适配器 + * @en Cocos Creator 3.x platform adapter + */ +export const cocosAdapter: PlatformAdapter = { + id: 'cocos', + name: 'Cocos Creator 3.x', + description: 'Generate ECS integration for Cocos Creator 3.x projects', + + getDependencies() { + return { + '@esengine/ecs-framework': 'latest' + }; + }, + + getDevDependencies() { + return {}; + }, + + getScripts() { + return {}; + }, + + generateFiles(config: ProjectConfig): FileEntry[] { + const files: FileEntry[] = []; + + files.push({ + path: 'assets/scripts/ecs/ECSManager.ts', + content: generateECSManager(config) + }); + + files.push({ + path: 'assets/scripts/ecs/components/PositionComponent.ts', + content: generatePositionComponent() + }); + + files.push({ + path: 'assets/scripts/ecs/systems/MovementSystem.ts', + content: generateMovementSystem() + }); + + files.push({ + path: 'assets/scripts/ecs/README.md', + content: generateReadme(config) + }); + + return files; + } +}; + +function generateECSManager(config: ProjectConfig): string { + return `import { _decorator, Component, director } from 'cc'; +import { Core, Scene, type ICoreConfig } from '@esengine/ecs-framework'; +import { MovementSystem } from './systems/MovementSystem'; + +const { ccclass, property } = _decorator; + +/** + * Game Scene - Define your game systems here + */ +class GameScene extends Scene { + initialize(): void { + this.name = '${config.name}'; + this.addSystem(new MovementSystem()); + // Add more systems here... + } + + onStart(): void { + // Create your initial entities here + } +} + +/** + * ECS Manager - Bridge between Cocos Creator and ESEngine ECS + * + * Attach this component to a node in your scene. + * All game logic should be implemented in ECS Systems. + */ +@ccclass('ECSManager') +export class ECSManager extends Component { + /** @zh 调试模式 @en Debug mode */ + @property({ tooltip: 'Enable debug mode for ECS framework' }) + debug = false; + + /** @zh 跨场景保持 @en Keep across scenes */ + @property({ tooltip: 'Keep this node alive across scenes' }) + persistent = true; + + /** @zh 启用远程调试 @en Enable remote debugging */ + @property({ tooltip: 'Connect to ECS debugger via WebSocket' }) + remoteDebug = false; + + /** @zh WebSocket调试地址 @en WebSocket debug URL */ + @property({ tooltip: 'WebSocket URL for remote debugging' }) + debugUrl = 'ws://localhost:9229'; + + /** @zh 自动重连 @en Auto reconnect */ + @property({ tooltip: 'Auto reconnect when connection lost' }) + autoReconnect = true; + + private static _instance: ECSManager | null = null; + private _scene!: GameScene; + + static get instance() { return ECSManager._instance; } + get scene() { return this._scene; } + + onLoad() { + if (ECSManager._instance) { + this.destroy(); + return; + } + ECSManager._instance = this; + + if (this.persistent) { + director.addPersistRootNode(this.node); + } + + const config: ICoreConfig = { + debug: this.debug + }; + + // 配置远程调试 + if (this.remoteDebug && this.debugUrl) { + config.debugConfig = { + enabled: true, + websocketUrl: this.debugUrl, + autoReconnect: this.autoReconnect, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + } + + Core.create(config); + this._scene = new GameScene(); + Core.setScene(this._scene); + } + + update(dt: number) { + Core.update(dt); + } + + onDestroy() { + if (ECSManager._instance === this) { + ECSManager._instance = null; + Core.destroy(); + } + } +} +`; +} + +function generatePositionComponent(): string { + return `import { Component, ECSComponent } from '@esengine/ecs-framework'; + +/** + * Position component - stores entity position + */ +@ECSComponent('Position') +export class PositionComponent extends Component { + x = 0; + y = 0; + + constructor(x = 0, y = 0) { + super(); + this.x = x; + this.y = y; + } +} +`; +} + +function generateMovementSystem(): string { + return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework'; +import { PositionComponent } from '../components/PositionComponent'; + +/** + * Movement system - processes entities with PositionComponent + * + * Customize this system for your game logic. + */ +@ECSSystem('MovementSystem') +export class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(PositionComponent)!; + // Update position using Time.deltaTime + // position.x += velocity.dx * Time.deltaTime; + } + } +} +`; +} + +function generateReadme(config: ProjectConfig): string { + return `# ${config.name} - ECS Module + +This module integrates ESEngine ECS framework with Cocos Creator. + +## Quick Start + +1. Attach \`ECSManager\` component to a node in your scene +2. Create your own components in \`components/\` folder +3. Create your systems in \`systems/\` folder +4. Register systems in \`ECSManager.start()\` + +## Creating Components + +\`\`\`typescript +import { Component } from '@esengine/ecs-framework'; + +export class MyComponent extends Component { + // Your data here + health: number = 100; + + reset() { + this.health = 100; + } +} +\`\`\` + +## Creating Systems + +\`\`\`typescript +import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework'; +import { MyComponent } from '../components/MyComponent'; + +export class MySystem extends EntitySystem { + constructor() { + super(Matcher.all(MyComponent)); + } + + protected processEntity(entity: Entity, dt: number): void { + const comp = entity.getComponent(MyComponent)!; + // Process entity + } +} +\`\`\` + +## Documentation + +- [ESEngine ECS Framework](https://github.com/esengine/esengine) +`; +} diff --git a/packages/tools/cli/src/adapters/cocos2.ts b/packages/tools/cli/src/adapters/cocos2.ts new file mode 100644 index 00000000..842ea24d --- /dev/null +++ b/packages/tools/cli/src/adapters/cocos2.ts @@ -0,0 +1,259 @@ +import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js'; + +/** + * @zh Cocos Creator 2.x 平台适配器 + * @en Cocos Creator 2.x platform adapter + */ +export const cocos2Adapter: PlatformAdapter = { + id: 'cocos2', + name: 'Cocos Creator 2.x', + description: 'Generate ECS integration for Cocos Creator 2.x projects', + + getDependencies() { + return { + '@esengine/ecs-framework': 'latest' + }; + }, + + getDevDependencies() { + return {}; + }, + + getScripts() { + return {}; + }, + + generateFiles(config: ProjectConfig): FileEntry[] { + const files: FileEntry[] = []; + + files.push({ + path: 'assets/scripts/ecs/ECSManager.ts', + content: generateECSManager(config) + }); + + files.push({ + path: 'assets/scripts/ecs/components/PositionComponent.ts', + content: generatePositionComponent() + }); + + files.push({ + path: 'assets/scripts/ecs/systems/MovementSystem.ts', + content: generateMovementSystem() + }); + + files.push({ + path: 'assets/scripts/ecs/README.md', + content: generateReadme(config) + }); + + return files; + } +}; + +function generateECSManager(config: ProjectConfig): string { + return `import { Core, Scene, ICoreConfig } from '@esengine/ecs-framework'; +import { MovementSystem } from './systems/MovementSystem'; + +const { ccclass, property } = cc._decorator; + +/** + * Game Scene - Define your game systems here + */ +class GameScene extends Scene { + initialize(): void { + this.name = '${config.name}'; + this.addSystem(new MovementSystem()); + // Add more systems here... + } + + onStart(): void { + // Create your initial entities here + } +} + +/** + * ECS Manager - Bridge between Cocos Creator 2.x and ESEngine ECS + * + * Attach this component to a node in your scene. + * All game logic should be implemented in ECS Systems. + */ +@ccclass +export default class ECSManager extends cc.Component { + /** @zh 调试模式 @en Debug mode */ + @property({ tooltip: 'Enable debug mode for ECS framework' }) + debug: boolean = false; + + /** @zh 跨场景保持 @en Keep across scenes */ + @property({ tooltip: 'Keep this node alive across scenes' }) + persistent: boolean = true; + + /** @zh 启用远程调试 @en Enable remote debugging */ + @property({ tooltip: 'Connect to ECS debugger via WebSocket' }) + remoteDebug: boolean = false; + + /** @zh WebSocket调试地址 @en WebSocket debug URL */ + @property({ tooltip: 'WebSocket URL for remote debugging' }) + debugUrl: string = 'ws://localhost:9229'; + + /** @zh 自动重连 @en Auto reconnect */ + @property({ tooltip: 'Auto reconnect when connection lost' }) + autoReconnect: boolean = true; + + private static _instance: ECSManager | null = null; + private _scene!: GameScene; + + static get instance() { return ECSManager._instance; } + get scene() { return this._scene; } + + onLoad() { + if (ECSManager._instance) { + this.node.destroy(); + return; + } + ECSManager._instance = this; + + if (this.persistent) { + cc.game.addPersistRootNode(this.node); + } + + const config: ICoreConfig = { + debug: this.debug + }; + + // 配置远程调试 + if (this.remoteDebug && this.debugUrl) { + config.debugConfig = { + enabled: true, + websocketUrl: this.debugUrl, + autoReconnect: this.autoReconnect, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + } + + Core.create(config); + this._scene = new GameScene(); + Core.setScene(this._scene); + } + + update(dt: number) { + Core.update(dt); + } + + onDestroy() { + if (ECSManager._instance === this) { + ECSManager._instance = null; + Core.destroy(); + } + } +} +`; +} + +function generatePositionComponent(): string { + return `import { Component, ECSComponent } from '@esengine/ecs-framework'; + +/** + * Position component - stores entity position + */ +@ECSComponent('Position') +export class PositionComponent extends Component { + x = 0; + y = 0; + + constructor(x = 0, y = 0) { + super(); + this.x = x; + this.y = y; + } +} +`; +} + +function generateMovementSystem(): string { + return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework'; +import { PositionComponent } from '../components/PositionComponent'; + +/** + * Movement system - processes entities with PositionComponent + * + * Customize this system for your game logic. + */ +@ECSSystem('MovementSystem') +export class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(PositionComponent)!; + // Update position using Time.deltaTime + // position.x += velocity.dx * Time.deltaTime; + } + } +} +`; +} + +function generateReadme(config: ProjectConfig): string { + return `# ${config.name} - ECS Module + +This module integrates ESEngine ECS framework with Cocos Creator 2.x. + +## Quick Start + +1. Attach \`ECSManager\` component to a node in your scene +2. Create your own components in \`components/\` folder +3. Create your systems in \`systems/\` folder +4. Register systems in \`ECSManager.onLoad()\` + +## Cocos Creator 2.x Notes + +- Use \`cc._decorator\` for decorators +- Use \`cc.Component\` as base class +- Use \`cc.game.addPersistRootNode()\` for persistent nodes + +## Creating Components + +\`\`\`typescript +import { Component } from '@esengine/ecs-framework'; + +export class MyComponent extends Component { + health: number = 100; + + reset() { + this.health = 100; + } +} +\`\`\` + +## Creating Systems + +\`\`\`typescript +import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework'; +import { MyComponent } from '../components/MyComponent'; + +export class MySystem extends EntitySystem { + constructor() { + super(Matcher.all(MyComponent)); + } + + protected processEntity(entity: Entity, dt: number): void { + const comp = entity.getComponent(MyComponent)!; + // Process entity + } +} +\`\`\` + +## Documentation + +- [ESEngine ECS Framework](https://github.com/esengine/esengine) +- [Cocos Creator 2.x Docs](https://docs.cocos.com/creator/2.4/manual/) +`; +} diff --git a/packages/tools/cli/src/adapters/index.ts b/packages/tools/cli/src/adapters/index.ts new file mode 100644 index 00000000..4833376a --- /dev/null +++ b/packages/tools/cli/src/adapters/index.ts @@ -0,0 +1,54 @@ +import { cocosAdapter } from './cocos.js'; +import { cocos2Adapter } from './cocos2.js'; +import { layaAdapter } from './laya.js'; +import { nodejsAdapter } from './nodejs.js'; +import type { AdapterRegistry, PlatformAdapter, PlatformType } from './types.js'; + +export * from './types.js'; +export { cocosAdapter } from './cocos.js'; +export { cocos2Adapter } from './cocos2.js'; +export { layaAdapter } from './laya.js'; +export { nodejsAdapter } from './nodejs.js'; + +/** + * @zh 平台适配器注册表 + * @en Platform adapter registry + */ +export const adapters: AdapterRegistry = { + cocos: cocosAdapter, + cocos2: cocos2Adapter, + laya: layaAdapter, + nodejs: nodejsAdapter +}; + +/** + * @zh 获取平台适配器 + * @en Get platform adapter + */ +export function getAdapter(platform: PlatformType): PlatformAdapter { + const adapter = adapters[platform]; + if (!adapter) { + throw new Error(`Unknown platform: ${platform}`); + } + return adapter; +} + +/** + * @zh 获取所有可用平台 + * @en Get all available platforms + */ +export function getPlatforms(): PlatformType[] { + return Object.keys(adapters) as PlatformType[]; +} + +/** + * @zh 获取平台选项(用于交互式提示) + * @en Get platform choices (for interactive prompts) + */ +export function getPlatformChoices(): Array<{ title: string; value: PlatformType; description: string }> { + return Object.values(adapters).map((adapter) => ({ + title: adapter.name, + value: adapter.id, + description: adapter.description + })); +} diff --git a/packages/tools/cli/src/adapters/laya.ts b/packages/tools/cli/src/adapters/laya.ts new file mode 100644 index 00000000..b58d0ca4 --- /dev/null +++ b/packages/tools/cli/src/adapters/laya.ts @@ -0,0 +1,245 @@ +import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js'; + +/** + * @zh Laya 3.x 平台适配器 + * @en Laya 3.x platform adapter + */ +export const layaAdapter: PlatformAdapter = { + id: 'laya', + name: 'Laya 3.x', + description: 'Generate ECS integration for LayaAir 3.x projects', + + getDependencies() { + return { + '@esengine/ecs-framework': 'latest' + }; + }, + + getDevDependencies() { + return {}; + }, + + getScripts() { + return {}; + }, + + generateFiles(config: ProjectConfig): FileEntry[] { + const files: FileEntry[] = []; + + files.push({ + path: 'src/ecs/ECSManager.ts', + content: generateECSManager(config) + }); + + files.push({ + path: 'src/ecs/components/PositionComponent.ts', + content: generatePositionComponent() + }); + + files.push({ + path: 'src/ecs/systems/MovementSystem.ts', + content: generateMovementSystem() + }); + + files.push({ + path: 'src/ecs/README.md', + content: generateReadme(config) + }); + + return files; + } +}; + +function generateECSManager(config: ProjectConfig): string { + return `import { Core, Scene, type ICoreConfig } from '@esengine/ecs-framework'; +import { MovementSystem } from './systems/MovementSystem'; + +const { regClass, property } = Laya; + +/** + * Game Scene - Define your game systems here + */ +class GameScene extends Scene { + initialize(): void { + this.name = '${config.name}'; + this.addSystem(new MovementSystem()); + // Add more systems here... + } + + onStart(): void { + // Create your initial entities here + } +} + +/** + * ECS Manager - Bridge between LayaAir and ESEngine ECS + * + * Attach this script to a node in your scene via Laya IDE. + * All game logic should be implemented in ECS Systems. + */ +@regClass() +export class ECSManager extends Laya.Script { + /** @zh 调试模式 @en Debug mode */ + @property({ type: Boolean, caption: 'Debug', tips: 'Enable debug mode for ECS framework' }) + debug = false; + + /** @zh 启用远程调试 @en Enable remote debugging */ + @property({ type: Boolean, caption: 'Remote Debug', tips: 'Connect to ECS debugger via WebSocket' }) + remoteDebug = false; + + /** @zh WebSocket调试地址 @en WebSocket debug URL */ + @property({ type: String, caption: 'Debug URL', tips: 'WebSocket URL for remote debugging (e.g., ws://localhost:9229)' }) + debugUrl = 'ws://localhost:9229'; + + /** @zh 自动重连 @en Auto reconnect */ + @property({ type: Boolean, caption: 'Auto Reconnect', tips: 'Auto reconnect when connection lost' }) + autoReconnect = true; + + private static _instance: ECSManager | null = null; + private _scene!: GameScene; + + static get instance() { return ECSManager._instance; } + get scene() { return this._scene; } + + onAwake(): void { + if (ECSManager._instance) { + this.destroy(); + return; + } + ECSManager._instance = this; + + const config: ICoreConfig = { + debug: this.debug + }; + + // 配置远程调试 + if (this.remoteDebug && this.debugUrl) { + config.debugConfig = { + enabled: true, + websocketUrl: this.debugUrl, + autoReconnect: this.autoReconnect, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + } + + Core.create(config); + this._scene = new GameScene(); + Core.setScene(this._scene); + } + + onUpdate(): void { + Core.update(Laya.timer.delta / 1000); + } + + onDestroy(): void { + if (ECSManager._instance === this) { + ECSManager._instance = null; + Core.destroy(); + } + } +} +`; +} + +function generatePositionComponent(): string { + return `import { Component, ECSComponent } from '@esengine/ecs-framework'; + +/** + * Position component - stores entity position + */ +@ECSComponent('Position') +export class PositionComponent extends Component { + x = 0; + y = 0; + + constructor(x = 0, y = 0) { + super(); + this.x = x; + this.y = y; + } +} +`; +} + +function generateMovementSystem(): string { + return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework'; +import { PositionComponent } from '../components/PositionComponent'; + +/** + * Movement system - processes entities with PositionComponent + * + * Customize this system for your game logic. + */ +@ECSSystem('MovementSystem') +export class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(PositionComponent)!; + // Update position using Time.deltaTime + // position.x += velocity.dx * Time.deltaTime; + } + } +} +`; +} + +function generateReadme(config: ProjectConfig): string { + return `# ${config.name} - ECS Module + +This module integrates ESEngine ECS framework with LayaAir 3.x. + +## Quick Start + +1. In Laya IDE, attach \`ECSManager\` script to a node in your scene +2. Create your own components in \`components/\` folder +3. Create your systems in \`systems/\` folder +4. Register systems in \`ECSManager.onAwake()\` + +## Creating Components + +\`\`\`typescript +import { Component } from '@esengine/ecs-framework'; + +export class MyComponent extends Component { + health: number = 100; + + reset() { + this.health = 100; + } +} +\`\`\` + +## Creating Systems + +\`\`\`typescript +import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework'; +import { MyComponent } from '../components/MyComponent'; + +export class MySystem extends EntitySystem { + constructor() { + super(Matcher.all(MyComponent)); + } + + protected processEntity(entity: Entity, dt: number): void { + const comp = entity.getComponent(MyComponent)!; + // Process entity + } +} +\`\`\` + +## Documentation + +- [ESEngine ECS Framework](https://github.com/esengine/esengine) +- [LayaAir Documentation](https://layaair.com/) +`; +} diff --git a/packages/tools/cli/src/adapters/nodejs.ts b/packages/tools/cli/src/adapters/nodejs.ts new file mode 100644 index 00000000..8537e491 --- /dev/null +++ b/packages/tools/cli/src/adapters/nodejs.ts @@ -0,0 +1,348 @@ +import type { FileEntry, PlatformAdapter, ProjectConfig } from './types.js'; + +/** + * @zh Node.js 平台适配器 + * @en Node.js platform adapter + */ +export const nodejsAdapter: PlatformAdapter = { + id: 'nodejs', + name: 'Node.js', + description: 'Generate standalone Node.js project with ECS (for servers, CLI tools, simulations)', + + getDependencies() { + return { + '@esengine/ecs-framework': 'latest' + }; + }, + + getDevDependencies() { + return { + '@types/node': '^20.0.0', + 'tsx': '^4.0.0', + 'typescript': '^5.0.0' + }; + }, + + getScripts() { + return { + 'dev': 'tsx watch src/index.ts', + 'start': 'tsx src/index.ts', + 'build': 'tsc', + 'build:start': 'tsc && node dist/index.js' + }; + }, + + generateFiles(config: ProjectConfig): FileEntry[] { + const files: FileEntry[] = []; + + files.push({ + path: 'src/index.ts', + content: generateIndex(config) + }); + + files.push({ + path: 'src/Game.ts', + content: generateGame(config) + }); + + files.push({ + path: 'src/components/PositionComponent.ts', + content: generatePositionComponent() + }); + + files.push({ + path: 'src/systems/MovementSystem.ts', + content: generateMovementSystem() + }); + + files.push({ + path: 'tsconfig.json', + content: generateTsConfig() + }); + + files.push({ + path: 'README.md', + content: generateReadme(config) + }); + + return files; + } +}; + +function generateIndex(config: ProjectConfig): string { + return `import { Game } from './Game.js'; + +const game = new Game(); + +// Handle graceful shutdown +process.on('SIGINT', () => { + console.log('\\nShutting down...'); + game.stop(); + process.exit(0); +}); + +process.on('SIGTERM', () => { + game.stop(); + process.exit(0); +}); + +// Start the game +game.start(); + +console.log('[${config.name}] Game started. Press Ctrl+C to stop.'); +`; +} + +function generateGame(config: ProjectConfig): string { + return `import { Core, Scene, type ICoreConfig } from '@esengine/ecs-framework'; +import { MovementSystem } from './systems/MovementSystem.js'; + +/** + * Game configuration options + */ +export interface GameOptions { + /** @zh 调试模式 @en Debug mode */ + debug?: boolean; + /** @zh 目标帧率 @en Target FPS */ + targetFPS?: number; + /** @zh 远程调试配置 @en Remote debug configuration */ + remoteDebug?: { + /** @zh 启用远程调试 @en Enable remote debugging */ + enabled: boolean; + /** @zh WebSocket地址 @en WebSocket URL */ + url: string; + /** @zh 自动重连 @en Auto reconnect */ + autoReconnect?: boolean; + }; +} + +/** + * Game Scene - Define your game systems here + */ +class GameScene extends Scene { + initialize(): void { + this.name = '${config.name}'; + this.addSystem(new MovementSystem()); + // Add more systems here... + } + + onStart(): void { + // Create your initial entities here + } +} + +/** + * Main game class with ECS game loop + * + * Features: + * - Configurable debug mode and FPS + * - Remote debugging via WebSocket + * - Fixed timestep game loop + * - Graceful start/stop + */ +export class Game { + private readonly _scene: GameScene; + private readonly _targetFPS: number; + private _running = false; + private _tickInterval: ReturnType | null = null; + private _lastTime = 0; + + get scene() { return this._scene; } + get running() { return this._running; } + + constructor(options: GameOptions = {}) { + const { debug = false, targetFPS = 60, remoteDebug } = options; + this._targetFPS = targetFPS; + + const config: ICoreConfig = { debug }; + + // 配置远程调试 + if (remoteDebug?.enabled && remoteDebug.url) { + config.debugConfig = { + enabled: true, + websocketUrl: remoteDebug.url, + autoReconnect: remoteDebug.autoReconnect ?? true, + channels: { + entities: true, + systems: true, + performance: true, + components: true, + scenes: true + } + }; + } + + Core.create(config); + this._scene = new GameScene(); + Core.setScene(this._scene); + } + + start(): void { + if (this._running) return; + this._running = true; + this._lastTime = performance.now(); + + this._tickInterval = setInterval(() => { + const now = performance.now(); + Core.update((now - this._lastTime) / 1000); + this._lastTime = now; + }, 1000 / this._targetFPS); + } + + stop(): void { + if (!this._running) return; + this._running = false; + + if (this._tickInterval) { + clearInterval(this._tickInterval); + this._tickInterval = null; + } + Core.destroy(); + } +} +`; +} + +function generatePositionComponent(): string { + return `import { Component, ECSComponent } from '@esengine/ecs-framework'; + +/** + * Position component - stores entity position + */ +@ECSComponent('Position') +export class PositionComponent extends Component { + x = 0; + y = 0; + + constructor(x = 0, y = 0) { + super(); + this.x = x; + this.y = y; + } +} +`; +} + +function generateMovementSystem(): string { + return `import { EntitySystem, Matcher, Entity, Time, ECSSystem } from '@esengine/ecs-framework'; +import { PositionComponent } from '../components/PositionComponent.js'; + +/** + * Movement system - processes entities with PositionComponent + * + * Customize this system for your game logic. + */ +@ECSSystem('MovementSystem') +export class MovementSystem extends EntitySystem { + constructor() { + super(Matcher.empty().all(PositionComponent)); + } + + protected process(entities: readonly Entity[]): void { + for (const entity of entities) { + const position = entity.getComponent(PositionComponent)!; + // Update position using Time.deltaTime + // position.x += velocity.dx * Time.deltaTime; + } + } +} +`; +} + +function generateTsConfig(): string { + return `{ + "compilerOptions": { + "target": "ES2022", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2022"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} +`; +} + +function generateReadme(config: ProjectConfig): string { + return `# ${config.name} + +A Node.js project using ESEngine ECS framework. + +## Quick Start + +\`\`\`bash +# Install dependencies +npm install + +# Run in development mode (with hot reload) +npm run dev + +# Build and run +npm run build:start +\`\`\` + +## Project Structure + +\`\`\` +src/ +├── index.ts # Entry point +├── Game.ts # Game loop and ECS setup +├── components/ # ECS components (data) +│ └── PositionComponent.ts +└── systems/ # ECS systems (logic) + └── MovementSystem.ts +\`\`\` + +## Creating Components + +\`\`\`typescript +import { Component } from '@esengine/ecs-framework'; + +export class HealthComponent extends Component { + current = 100; + max = 100; + + reset(): void { + this.current = this.max; + } +} +\`\`\` + +## Creating Systems + +\`\`\`typescript +import { EntitySystem, Matcher, Entity } from '@esengine/ecs-framework'; +import { HealthComponent } from '../components/HealthComponent.js'; + +export class HealthSystem extends EntitySystem { + constructor() { + super(Matcher.all(HealthComponent)); + } + + protected processEntity(entity: Entity, dt: number): void { + const health = entity.getComponent(HealthComponent)!; + // Your logic here + } +} +\`\`\` + +## Use Cases + +- Game servers +- CLI tools with complex logic +- Simulations +- Automated testing + +## Documentation + +- [ESEngine ECS Framework](https://github.com/esengine/esengine) +`; +} diff --git a/packages/tools/cli/src/adapters/types.ts b/packages/tools/cli/src/adapters/types.ts new file mode 100644 index 00000000..6dea7ac1 --- /dev/null +++ b/packages/tools/cli/src/adapters/types.ts @@ -0,0 +1,77 @@ +/** + * @zh 项目配置 + * @en Project configuration + */ +export interface ProjectConfig { + /** @zh 项目名称 @en Project name */ + name: string; + /** @zh 目标平台 @en Target platform */ + platform: PlatformType; + /** @zh 项目路径 @en Project path */ + path: string; +} + +/** + * @zh 支持的平台类型 + * @en Supported platform types + */ +export type PlatformType = 'cocos' | 'cocos2' | 'laya' | 'nodejs'; + +/** + * @zh 文件入口 + * @en File entry + */ +export interface FileEntry { + /** @zh 相对路径 @en Relative path */ + path: string; + /** @zh 文件内容 @en File content */ + content: string; +} + +/** + * @zh 平台适配器接口 + * @en Platform adapter interface + * + * @zh 每个平台只需实现这个接口,即可支持项目生成 + * @en Each platform only needs to implement this interface to support project generation + */ +export interface PlatformAdapter { + /** @zh 平台标识 @en Platform identifier */ + readonly id: PlatformType; + + /** @zh 平台显示名称 @en Platform display name */ + readonly name: string; + + /** @zh 平台描述 @en Platform description */ + readonly description: string; + + /** + * @zh 获取平台特定的依赖 + * @en Get platform-specific dependencies + */ + getDependencies(): Record; + + /** + * @zh 获取平台特定的开发依赖 + * @en Get platform-specific dev dependencies + */ + getDevDependencies(): Record; + + /** + * @zh 生成平台特定的文件 + * @en Generate platform-specific files + */ + generateFiles(config: ProjectConfig): FileEntry[]; + + /** + * @zh 获取 package.json 的 scripts + * @en Get package.json scripts + */ + getScripts(): Record; +} + +/** + * @zh 平台适配器注册表类型 + * @en Platform adapter registry type + */ +export type AdapterRegistry = Record; diff --git a/packages/tools/cli/src/cli.ts b/packages/tools/cli/src/cli.ts new file mode 100644 index 00000000..10ef5ac7 --- /dev/null +++ b/packages/tools/cli/src/cli.ts @@ -0,0 +1,320 @@ +#!/usr/bin/env node + +import { Command } from 'commander'; +import prompts from 'prompts'; +import chalk from 'chalk'; +import * as fs from 'node:fs'; +import * as path from 'node:path'; +import { execSync } from 'node:child_process'; +import { getPlatformChoices, getPlatforms, getAdapter } from './adapters/index.js'; +import type { PlatformType, ProjectConfig } from './adapters/types.js'; + +const VERSION = '1.0.0'; + +/** + * @zh 打印 Logo + * @en Print logo + */ +function printLogo(): void { + console.log(); + console.log(chalk.cyan(' ╭──────────────────────────────────────╮')); + console.log(chalk.cyan(' │ │')); + console.log(chalk.cyan(' │ ') + chalk.bold.white('ESEngine CLI') + chalk.gray(` v${VERSION}`) + chalk.cyan(' │')); + console.log(chalk.cyan(' │ │')); + console.log(chalk.cyan(' ╰──────────────────────────────────────╯')); + console.log(); +} + +/** + * @zh 检测是否存在 *.laya 文件 + * @en Check if *.laya file exists + */ +function hasLayaProjectFile(cwd: string): boolean { + try { + const files = fs.readdirSync(cwd); + return files.some(f => f.endsWith('.laya')); + } catch { + return false; + } +} + +/** + * @zh 检测 Cocos Creator 版本 + * @en Detect Cocos Creator version + */ +function detectCocosVersion(cwd: string): 'cocos' | 'cocos2' | null { + // Cocos 3.x: 检查 cc.config.json 或 extensions 目录 + if (fs.existsSync(path.join(cwd, 'cc.config.json')) || + fs.existsSync(path.join(cwd, 'extensions'))) { + return 'cocos'; + } + + // 检查 project.json 中的版本号 + const projectJsonPath = path.join(cwd, 'project.json'); + if (fs.existsSync(projectJsonPath)) { + try { + const project = JSON.parse(fs.readFileSync(projectJsonPath, 'utf-8')); + // Cocos 2.x project.json 有 engine-version 字段 + if (project['engine-version'] || project.engine) { + const version = project['engine-version'] || project.engine || ''; + // 2.x 版本格式: "cocos-creator-js-2.4.x" 或 "2.4.x" + if (version.includes('2.') || version.startsWith('2')) { + return 'cocos2'; + } + } + // 有 project.json 但没有版本信息,假设是 3.x + return 'cocos'; + } catch { + // 解析失败,假设是 3.x + return 'cocos'; + } + } + + return null; +} + +/** + * @zh 检测项目类型 + * @en Detect project type + */ +function detectProjectType(cwd: string): PlatformType | null { + // Laya: 检查 *.laya 文件 或 .laya 目录 或 laya.json + if (hasLayaProjectFile(cwd) || + fs.existsSync(path.join(cwd, '.laya')) || + fs.existsSync(path.join(cwd, 'laya.json'))) { + return 'laya'; + } + + // Cocos Creator: 检查 assets 目录 + if (fs.existsSync(path.join(cwd, 'assets'))) { + const cocosVersion = detectCocosVersion(cwd); + if (cocosVersion) { + return cocosVersion; + } + } + + // Node.js: 检查 package.json + if (fs.existsSync(path.join(cwd, 'package.json'))) { + return 'nodejs'; + } + + return null; +} + +/** + * @zh 检测包管理器 + * @en Detect package manager + */ +function detectPackageManager(cwd: string): 'pnpm' | 'yarn' | 'npm' { + if (fs.existsSync(path.join(cwd, 'pnpm-lock.yaml'))) return 'pnpm'; + if (fs.existsSync(path.join(cwd, 'yarn.lock'))) return 'yarn'; + return 'npm'; +} + +/** + * @zh 读取或创建 package.json + * @en Read or create package.json + */ +function readOrCreatePackageJson(packageJsonPath: string, projectName: string): Record { + try { + return JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'ENOENT') { + const pkg = { + name: projectName, + version: '1.0.0', + private: true, + dependencies: {} + }; + fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf-8'); + console.log(chalk.green(' ✓ Created package.json')); + return pkg; + } + throw err; + } +} + +/** + * @zh 安装依赖 + * @en Install dependencies + */ +function installDependencies(cwd: string, deps: Record): boolean { + const pm = detectPackageManager(cwd); + const packageJsonPath = path.join(cwd, 'package.json'); + + // 读取或创建 package.json(原子操作,避免竞态条件) + const pkg = readOrCreatePackageJson(packageJsonPath, path.basename(cwd)); + const pkgDeps = (pkg.dependencies || {}) as Record; + + let needsInstall = false; + for (const [name, version] of Object.entries(deps)) { + if (!pkgDeps[name]) { + pkgDeps[name] = version; + needsInstall = true; + } + } + + if (!needsInstall) { + console.log(chalk.gray(' Dependencies already configured.')); + return true; + } + + pkg.dependencies = pkgDeps; + fs.writeFileSync(packageJsonPath, JSON.stringify(pkg, null, 2), 'utf-8'); + + // 运行安装命令 + const installCmd = pm === 'pnpm' ? 'pnpm install' : pm === 'yarn' ? 'yarn' : 'npm install'; + console.log(chalk.gray(` Running ${installCmd}...`)); + + try { + execSync(installCmd, { cwd, stdio: 'inherit' }); + return true; + } catch { + console.log(chalk.yellow(` ⚠ Failed to run ${installCmd}. Please run it manually.`)); + return false; + } +} + +/** + * @zh 获取项目名称 + * @en Get project name + */ +function getProjectName(cwd: string): string { + const packageJsonPath = path.join(cwd, 'package.json'); + if (fs.existsSync(packageJsonPath)) { + try { + const pkg = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8')); + return pkg.name || path.basename(cwd); + } catch { + // ignore + } + } + return path.basename(cwd); +} + +/** + * @zh 初始化 ECS 到现有项目 + * @en Initialize ECS into existing project + */ +async function initCommand(options: { platform?: string }): Promise { + printLogo(); + + const cwd = process.cwd(); + let platform = options.platform as PlatformType | undefined; + + // 尝试自动检测项目类型 + const detected = detectProjectType(cwd); + + if (!platform) { + if (detected) { + console.log(chalk.gray(` Detected: ${detected} project`)); + platform = detected; + } else { + // 交互式选择 + const response = await prompts({ + type: 'select', + name: 'platform', + message: 'Select platform:', + choices: getPlatformChoices(), + initial: 0 + }, { + onCancel: () => { + console.log(chalk.yellow('\n Cancelled.')); + process.exit(0); + } + }); + platform = response.platform; + } + } + + // 验证平台 + const validPlatforms = getPlatforms(); + if (!platform || !validPlatforms.includes(platform)) { + console.log(chalk.red(`\n ✗ Invalid platform. Choose from: ${validPlatforms.join(', ')}`)); + process.exit(1); + } + + const projectName = getProjectName(cwd); + const adapter = getAdapter(platform); + + const config: ProjectConfig = { + name: projectName, + platform, + path: cwd + }; + + console.log(); + console.log(chalk.bold('Adding ECS to your project...')); + + // 生成文件 + const files = adapter.generateFiles(config); + const createdFiles: string[] = []; + + for (const file of files) { + const filePath = path.join(cwd, file.path); + const dir = path.dirname(filePath); + + // 创建目录(recursive: true 不会因目录存在而失败) + fs.mkdirSync(dir, { recursive: true }); + + // 尝试写入文件(wx 模式:如果文件存在则失败,避免竞态条件) + try { + fs.writeFileSync(filePath, file.content, { encoding: 'utf-8', flag: 'wx' }); + createdFiles.push(file.path); + console.log(chalk.green(` ✓ Created ${file.path}`)); + } catch (err) { + if ((err as NodeJS.ErrnoException).code === 'EEXIST') { + console.log(chalk.yellow(` ⚠ Skipped ${file.path} (already exists)`)); + } else { + throw err; + } + } + } + + if (createdFiles.length === 0) { + console.log(chalk.yellow('\n No files created. ECS may already be set up.')); + return; + } + + // 安装依赖 + console.log(); + console.log(chalk.bold('Installing dependencies...')); + const deps = adapter.getDependencies(); + installDependencies(cwd, deps); + + // 打印下一步 + console.log(); + console.log(chalk.bold('Done!')); + console.log(); + + if (platform === 'cocos' || platform === 'cocos2') { + console.log(chalk.gray(' Attach ECSManager to a node in your scene to start.')); + } else if (platform === 'laya') { + console.log(chalk.gray(' Attach ECSManager script to a node in Laya IDE to start.')); + } else { + console.log(chalk.gray(' Run `npm run dev` to start your game.')); + } + console.log(); +} + +// Setup CLI +const program = new Command(); + +program + .name('esengine') + .description('CLI tool for adding ESEngine ECS to your project') + .version(VERSION); + +program + .command('init') + .description('Add ECS framework to your existing project') + .option('-p, --platform ', 'Target platform (cocos, cocos2, laya, nodejs)') + .action(initCommand); + +// Default command: run init +program + .action(() => { + initCommand({}); + }); + +program.parse(); diff --git a/packages/tools/cli/src/index.ts b/packages/tools/cli/src/index.ts new file mode 100644 index 00000000..eed42e03 --- /dev/null +++ b/packages/tools/cli/src/index.ts @@ -0,0 +1,8 @@ +/** + * @zh ESEngine CLI - 为现有项目添加 ECS 框架 + * @en ESEngine CLI - Add ECS framework to existing projects + * + * @packageDocumentation + */ + +export * from './adapters/index.js'; diff --git a/packages/tools/cli/tsconfig.json b/packages/tools/cli/tsconfig.json new file mode 100644 index 00000000..1721d73b --- /dev/null +++ b/packages/tools/cli/tsconfig.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "NodeNext", + "moduleResolution": "NodeNext", + "lib": ["ES2020"], + "outDir": "./dist", + "rootDir": "./src", + "strict": true, + "esModuleInterop": true, + "skipLibCheck": true, + "forceConsistentCasingInFileNames": true, + "declaration": true, + "declarationMap": true, + "sourceMap": true + }, + "include": ["src/**/*"], + "exclude": ["node_modules", "dist"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 350cd40f..f728c061 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -2032,6 +2032,31 @@ importers: specifier: ^4.5.4 version: 4.5.4(@types/node@20.19.27)(rollup@4.54.0)(typescript@5.9.3)(vite@6.4.1(@types/node@20.19.27)(jiti@2.6.1)(terser@5.44.1)(tsx@4.21.0)(yaml@2.8.2)) + packages/tools/cli: + dependencies: + chalk: + specifier: ^4.1.2 + version: 4.1.2 + commander: + specifier: ^12.1.0 + version: 12.1.0 + prompts: + specifier: ^2.4.2 + version: 2.4.2 + devDependencies: + '@types/node': + specifier: ^20.19.0 + version: 20.19.27 + '@types/prompts': + specifier: ^2.4.9 + version: 2.4.9 + rimraf: + specifier: ^5.0.0 + version: 5.0.10 + typescript: + specifier: ^5.8.3 + version: 5.9.3 + packages/tools/sdk: dependencies: '@esengine/asset-system': @@ -4793,6 +4818,9 @@ packages: '@types/pako@2.0.4': resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==} + '@types/prompts@2.4.9': + resolution: {integrity: sha512-qTxFi6Buiu8+50/+3DGIWLHM6QuWsEKugJnnP6iv2Mc4ncxE4A/OJkjuVOA+5X0X1S/nq5VJRa8Lu+nwcvbrKA==} + '@types/prop-types@15.7.15': resolution: {integrity: sha512-F6bEyamV9jKGAFBEmlQnesRPGOQqS2+Uwi0Em15xenOxHaf2hv6L8YCVn3rPdPJOiJfPiCnLIRyvwVaqMY3MIw==} @@ -5557,6 +5585,10 @@ packages: resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==} engines: {node: '>=16'} + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@2.20.3: resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==} @@ -13067,6 +13099,11 @@ snapshots: '@types/pako@2.0.4': {} + '@types/prompts@2.4.9': + dependencies: + '@types/node': 20.19.27 + kleur: 3.0.3 + '@types/prop-types@15.7.15': {} '@types/qs@6.14.0': {} @@ -13936,6 +13973,8 @@ snapshots: commander@11.1.0: {} + commander@12.1.0: {} + commander@2.20.3: {} commander@4.1.1: {}