npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

ccgf-game-tool

v1.0.25

Published

i18n:ccgf-game-tool.description

Downloads

1,349

Readme

ccgf-game-tool

cocos_gameframework 的编辑器扩展:把框架的 MVC 约定自动化,并在约定被破坏时提前报出来。

背景:框架的约定

约定写在 docs/create-view-design.md:

  • Module = Controller:@RegisterModule({ order, model }),model 必填;一个模块目录 = 一对 XxxModule.ts + XxxModel.ts
  • View = 脚本 + 预制体:@RegisterView({ name, path, layer });脚本放在模块的 views/ 下,预制体放在 resources/views/<模块>/ 下,脚本挂在预制体根节点
  • .meta(含 UUID)由编辑器自动生成,不手写

这套约定原本只在「创建那一刻」靠工具帮忙,之后就全靠自觉。而约定被破坏后通常是运行期静默出错(加载错预制体、启动顺序不对、脚本丢失),定位成本很高。所以本扩展分两边:生成器(从零到有)+ 体检(从有到对)。

面板

菜单 开发者 → 工作台(也可通过 面板 → ccgf-game-tool)。面板分两个切页。

模块切页

| 区域 | 内容 | | --- | --- | | 左栏 | 模块列表(order 徽标、缺 Model 时显示告警点)、新建模块表单 | | 右栏 | 选中模块的 order 编辑、模块的三个脚本(点击打开)、View 列表(层级标签、预制体缺失告警) | | 顶部 | 「概览」启动顺序 + 层级占用;「体检」一键检查 |

创建 View 时可选择层级(框架 ELayer)与预制体模板。

创建模块会生成三个脚本:

| 文件 | 用途 | | --- | --- | | <前缀>Module.ts | Module 子类,用 @RegisterModule 注册 | | <前缀>Model.ts | Model 子类 | | <Prefix>Events.ts | 事件声明 —— 一段 declare global { interface IGameEvents { ... } } 增强 |

事件声明文件是纯声明:它让本模块能派发事件、别的模块能监听,而两边不需要互相 import。里面的说明注释是刻意保留的 —— 尤其末尾的 export { }; 不能删(没有 import/export 的文件会被当成全局脚本,declare global 会直接编译报错)。有一条测试专门编译这个模板,并断言删掉那一行会被编译器拒绝。设计与规则见 docs/event.md。

目标包(Asset Bundle)

面板顶部有「目标包」下拉:主包 = assets/scripts + assets/resources/views;选中子包后,create-module / create-view 落到 assets/bundles/<包名>/scripts/ 与 assets/bundles/<包名>/views/,生成的 @RegisterView 会带上 bundle: '<包名>'。

「新建 Bundle」会建好目录、把目录 .meta 的 userData.isBundle 写上(字段照真实 bundle 的 meta 格式),并把目标包切过去。.meta 没写成时会如实提示去资源管理器手动勾选「配置为 Bundle」,不会假装成功。scripts/ 与 views/ 里各有一个 README.md 占位——空目录不会被资源库导入,没有文件的话资源管理器里看不到这两个目录(别删)。

右侧的「删除 Bundle」删除当前子包(连同 scripts/、views/ 下的全部内容,删除前会确认);处于主包时按钮为禁用状态,不会误删主包。删完自动切回主包。

模块列表、View 列表与体检都按当前目标包扫描:选中子包就只看子包里的模块 / View,切回主包就回到主包的数据。

子包模式生成的代码会多两处标记,配合框架的脚本分包:

  • 模块:@RegisterModule({ order, model, bundle: '<包名>' }) —— 子包脚本 loadBundle 时才执行,框架靠这个字段在 SubGame.enter('<包名>') 时按包启动
  • View:@ccclass('<包名>_<类名>') —— Cocos 的 ccclass 名全局唯一,不加包名前缀两个子游戏生成同名 View 会冲突;注册名与类名不变,大厅用 ViewManager.showInBundle('<包名>', 'XxxView') 打开

完整约定见框架文档 docs/subgame.md。

数据表工具(独立面板)

菜单「DevTools → 数据表工具」打开,和本工作台完全分开:

  • 扫描工程根 tables/**/*.csv,每张表显示 CSV 物理路径
  • 筛选:全部表 / 主包 / 指定 Bundle 下的表;全选和导出只作用于当前筛选结果
  • 新建表:只生成 ID 一列的三行骨架(类型/字段名/注释),不覆盖、不自动导出;编辑交给 Excel/WPS
  • 全部导出 / 指定导出:CSV → .bin,按 tables/ 镜像到 assets/,并自动 refresh asset-db
  • 导出成功后同时生成 TableTypes.ts(主包 scripts/table/,子包 bundles/<bundle>/scripts/table/)
  • 刷新:外部改名/新增 CSV 后重新扫描;新建/删除 Bundle 会自动通知刷新(手动按钮兜底)
  • 不做单元格编辑、不做预览;表名全局唯一,失败会显示表名/行号/字段

格式与 TableManager 用法见框架文档 docs/table-format.md。

Manager 切页

列出 assets/scripts/manager/ 下已有的全局单例,并可直接新建 / 删除。

约定的理由:manager 是全局服务,不属于某个业务模块,所以放在与 framework/、gameplay/ 平级的 assets/scripts/manager/,并且继承框架的 ASingleton,用 XxxManager.getInstance() 取实例。生成的模板:

import { ASingleton } from '../framework/common/Singleton';

export class ConfigManager extends ASingleton {

    public init() {
    }

    public update(dt: number) {
    }
}

列表里没继承 ASingleton 的 manager 会带告警点,对应体检的 manager-not-singleton。

公共模块切页

列出框架的公共模块(可选、可独立发布的包),分成已安装与未安装两组,并直接代跑 npm 完成安装 / 卸载。

  • 安装在工程根目录执行 npm install <说明符>…;卸载执行 npm uninstall <包名>。两者都带 --no-audit --no-fund,让输出聚焦在真正的问题上。
  • 模块的本地依赖会一起装。只装它一个的话,npm 会去 registry 找依赖 —— 而未发布的本地包在 registry 上当然不存在,实测会直接 E404 失败。面板上显示的命令与实际执行的是同一个函数产出的,所以你复制去手动跑,行为完全一致。
  • 失败会把原始信息整个给你。 不是一句"安装失败",而是弹窗展示退出码、完整命令(方便你自己重跑)、以及 npm 的原始 stdout/stderr;认出故障特征时再补一句提示(找不到 npm / 网络不可达 / 超时 / 证书 / 权限 / ERESOLVE / 404 / EBADENGINE ……)。实现见 source/shared.ts。
  • npm 命令常驻在界面上(详情面板里),显示的是与按钮即将执行的动作一致的那条(未安装显示 install,已安装显示 uninstall),并提示自动执行失败时可以把命令复制到工程根目录手动跑。命令由主进程拼好下发,界面上是原样展示 —— 你复制到的就是按钮本来会执行的那条。
  • 区分三种状态:installed(依赖里声明了且 node_modules 里存在)、declared-not-installed(声明了但磁盘上没有 —— 上次安装大概失败了)、not-installed。
  • 依赖关系双向显示:它依赖谁(每个依赖都带实时安装状态,所以缺哪个在真正用到之前就能看见),以及谁依赖它。后一个方向也进了卸载确认 —— 只检查脚本引用的话,"另一个公共模块依赖它"会被静默漏掉。
  • 可更新状态分五种,而不是"能更新 / 不能更新"两种。远程最新版本号直接取自列表用的那次 registry 搜索(实测搜索结果的 version 就等于 registry 的 dist-tags.latest,抽样 6/6 一致),所以不增加任何网络开销。关键是 "无法确认"和"已是最新"是两回事 —— 离线绝不会被显示成"已是最新"。本地 file: 来源显示为不适用,因为对它"更新"只会把同一个本地来源再装一遍。有新版时安装按钮直接变成「更新到 x.y.z」。
  • 卸载前会把仍在 import 它的脚本列出来让你确认,因为卸载后这些 import 会失败。
  • Windows 注意:npm 是通过 cmd.exe /d /s /c 启动并且自己加引号的 —— Node 在修了 CVE-2024-27980 之后不允许直接 spawn 一个 .cmd 文件;而且 spawn 必须带上 windowsVerbatimArguments: true,否则 Node 会把引号再转义一遍,cmd 把整行当成一个命令名,npm 根本不会被执行(这个标志由启动说明符一起带出,忘不掉;tests/npm-spawn.test.js 会真的起一个进程来守它)。子进程输出不能一律按 UTF-8 解:先严解码 UTF-8,失败再回退到控制台代码页(中文机器上是 GBK) —— cmd.exe 自己的消息在中文 Windows 上不是 UTF-8,按 UTF-8 解正好就是乱码的来源。同理,故障特征中英文签名都匹配。实现见 source/shared.ts 的 spawnSpecFor / decodeProcessOutput。

「未安装」列表的来源:

| 来源 | 状态 | | --- | --- | | 框架自己的工作区(packages/shared/*) | 已实现 —— 用 file: 说明符安装,装的是工作区里那份源码。只在开发时成立:只有外层仓库确实是本框架的(按根 package.json 的 name 判定)才认,所以这个概念永远不会出现在用户工程里 —— 即使那个工程恰好有个 packages/shared 目录 | | 远程 npm registry | 已实现 —— 用 npm search keywords:ccgf-shared --json 发现,因此与包 scope 无关(搜索 API 的 scope: 限定符实测无效,keywords: 才是精确匹配)。结果缓存 5 分钟,「刷新」按钮强制重拉;拉取失败只显示提示,不影响面板其余功能。走 npm 而不是自己发 HTTPS,是为了自动带上工程的 .npmrc(registry / 代理 / 认证 / cafile)—— 私有 registry 靠的正是这些 |

公共模块是靠 package.json 里的标记(cocosGameFramework.sharedModule,由 tools/build-shared.js 写入)认出来的,不是靠包名匹配 —— 那样 scope 一改就认不出来了。

装 / 卸之后脚本编译器可能需要一点时间;如果 import 仍提示找不到模块,让编辑器重新编译一次或重启。

预制体模板

生成器的默认产物是一个 100×100 的空根节点。模板只改根节点的尺寸与 cc.Widget 对齐 —— 这正是每个 View 都要手设一遍的重复劳动:

| 模板 | 尺寸 | 对齐(_alignFlags) | | --- | --- | --- | | empty | 100×100 | 不加 Widget | | fullscreen | 1280×720 | 45 = TOP|BOT|LEFT|RIGHT(四边拉伸) | | popup | 600×400 | 192 = HORIZONTAL|VERTICAL(居中) | | listitem | 600×80 | 41 = TOP|LEFT|RIGHT(顶部横向拉伸) |

体检的检查项

| 规则 | 级别 | 挡住的问题 | | --- | --- | --- | | layers-drift | error | 框架 ELayer 与扩展 LAYERS 不同步(加层漏改一处) | | view-duplicate-name | error | 跨模块同名 View —— ViewManager 按类名全局查找,会加载错预制体 | | view-layer-invalid | error | layer 不在框架层级里 | | view-path-not-found | error | @RegisterView.path 对不上真实预制体 | | view-script-not-mounted | error | 预制体里没挂该 View 脚本 | | import-broken | error | 相对导入上溯层数不对 / 目标文件不存在 | | module-order-collision | warn | order 撞号(实际启动顺序取决于脚本加载顺序,不确定) | | module-order-missing | warn | 解析不到 order | | module-model-missing | warn | 有 Module 没 Model | | view-name-mismatch | warn | 文件名 / @RegisterView.name / @ccclass / 类名 四者不一致 | | view-without-module | warn | views/ 目录存在但模块不存在 | | manager-not-singleton | warn | assets/scripts/manager 下的 manager 没继承 ASingleton | | dangling-script-ref | warn | 场景/预制体引用了已删除的脚本(悬空组件,控制台会持续报 missing) |

体检结果里带文件路径的条目可以点击,直接在编辑器里打开对应资源。

生成器的其它保障

  • 失败回滚:建模块是 Module/Model/Events 三个文件的原子操作 —— 任一步失败都会删掉已建的部分;预制体建失败会回滚脚本,不留半成品
  • 同名提示明确:先查存在性再动手,报「模块已存在:BagModule.ts(如需重建,请先删除该模块)」,而不是含糊的"可能已存在"
  • 跨模块重名拦截:创建 View 前扫全工程,重名直接拒绝并指出被哪个模块占用
  • 删除前查引用:删除模块时列出引用了该模块脚本的资源,避免留下悬空引用
  • 空目录清理:删除 View/模块后收掉 resources/views/<模块> 这类空壳

命令

npm install
npm run build                      # npx tsc -> dist/
npm test                           # node --test(94 项)

# 不开编辑器,直接对工程跑一遍体检(开发/排查用)
node tests/manual-doctor.js [工程根目录]

.ts 改动后必须重新编译并重载扩展才会生效;只改 panel.html 的话重开面板即可,但 index.css 是在模块加载时读取的,同样需要重载扩展。

源码结构

source/
  main.ts        主进程:方法注册 + 编辑器 API 调用(唯一的副作用层)
  panels/workbench/index.ts   Vue3 面板
  constants.ts   层级/目录/模板/正则的唯一定义
  types.ts       快照与发现的类型
  naming.ts      命名归一化(纯函数)
  project.ts     工程快照读取(唯一的 I/O 层)
  doctor.ts      体检的 12 项检查(纯函数)
  templates.ts   脚本与预制体模板(纯函数)
tests/           node --test:naming / templates / doctor / 契约 / i18n
i18n/            zh.js + en.js,key 集合由测试保证一致

doctor / naming / templates 都是不碰 fs、不碰 Editor 的纯函数,因此可以被单测完全覆盖 —— 这也是 project.ts 之所以要把 I/O 单独拆出来的原因。

已知限制

  • 主进程抛出的错误文案目前只有中文(面板 UI 已完整支持中英切换)
  • 预制体模板只生成根节点,不生成子节点骨架(标题/关闭按钮等)
  • queryModuleRefs 的引用检查靠扫描序列化资源文本,没有用 asset-db 的 query-asset-users(后者参数与返回结构未核实)