From 0e85e5e421cc3726a403d43b646c4756435daefd Mon Sep 17 00:00:00 2001 From: Shiroha7z Date: Sun, 27 Sep 2026 13:14:35 +0800 Subject: [PATCH] chore: prepare pnpm stable release workflow --- .changeset/pnpm-stable-release-prep.md | 7 + .github/workflows/module-publish.yml | 148 +++++++++++ CONTRIBUTING.md | 1 - README.en.md | 6 +- README.md | 9 +- docs/en/guide/backup-upgrade.mdx | 3 +- docs/en/guide/index.mdx | 7 +- docs/superpowers/specs/modules/AGENT_BRIEF.md | 3 +- docs/zh/CONTRIBUTING-DOCS.mdx | 8 +- docs/zh/api/sdk/index.md | 2 +- docs/zh/dev/architecture.md | 2 +- docs/zh/dev/contributing.md | 40 ++- docs/zh/dev/eslint.md | 6 +- docs/zh/dev/index.mdx | 4 +- docs/zh/dev/module-author.mdx | 10 +- docs/zh/dev/publish.md | 8 +- docs/zh/dev/stable-release-plan.md | 86 ++++++ docs/zh/dev/style-sample.mdx | 9 +- docs/zh/dev/testing.md | 2 +- docs/zh/dev/tools.md | 6 +- docs/zh/guide/backup-upgrade.mdx | 4 +- docs/zh/guide/index.mdx | 17 +- docs/zh/reference/index.md | 2 +- modules/sdk/@sfmc-eslint-plugin/README.md | 4 +- modules/sdk/@sfmc-sdk/README.md | 2 +- modules/sdk/@sfmc-sdk/build.mjs | 2 +- packages/bds-tools/README.md | 2 +- packages/bds-tools/recovery.js | 2 +- packages/cli/README.md | 9 +- packages/cli/package.json | 1 + .../scripts/module-install/fetch-module.mjs | 246 +++++++++++------- packages/cli/src/i18n/locales/en.ts | 2 +- packages/cli/src/i18n/locales/zh-CN.ts | 2 +- packages/cli/src/module-commands.ts | 4 +- packages/cli/src/module-pack-build.ts | 2 +- packages/cli/src/wizard.ts | 4 +- packages/create-module/README.md | 3 +- packages/create-module/package.json | 7 +- packages/create-module/src/cli.ts | 15 +- .../templates/base/.prettierignore | 2 +- .../create-module/templates/base/README.md | 10 +- .../create-module/templates/base/package.json | 8 +- .../templates/base/pnpm-workspace.yaml | 5 + packages/db-server/README.md | 2 +- packages/devkit/README.md | 2 +- packages/devkit/package.json | 5 + packages/devkit/src/index.ts | 2 +- packages/meta/README.md | 10 +- packages/qq-bridge/README.md | 2 +- packages/tools/README.md | 11 +- packages/tools/changeset-assert-mode.mjs | 6 +- packages/tools/changeset-ensure.mjs | 2 +- packages/tools/changeset-publish.mjs | 8 +- packages/tools/lib/npm-publish-packages.mjs | 1 + packages/tools/lib/proc.mjs | 15 -- packages/tools/lib/verify/db-api.mjs | 1 - packages/tools/lib/verify/db-harness.mjs | 66 ++++- packages/tools/lib/verify/offline.mjs | 2 +- packages/tools/pack-verify.mjs | 24 +- packages/tools/run-release.mjs | 12 +- packages/tools/tsc7.mjs | 2 +- packages/tools/verify.mjs | 2 +- pnpm-lock.yaml | 3 + website/AGENTS.md | 4 +- website/data/troubleshoot-tree.en.json | 4 +- website/data/troubleshoot-tree.zh.json | 4 +- 66 files changed, 616 insertions(+), 296 deletions(-) create mode 100644 .changeset/pnpm-stable-release-prep.md create mode 100644 .github/workflows/module-publish.yml create mode 100644 docs/zh/dev/stable-release-plan.md create mode 100644 packages/create-module/templates/base/pnpm-workspace.yaml diff --git a/.changeset/pnpm-stable-release-prep.md b/.changeset/pnpm-stable-release-prep.md new file mode 100644 index 00000000..41ea897c --- /dev/null +++ b/.changeset/pnpm-stable-release-prep.md @@ -0,0 +1,7 @@ +--- +"@sfmc-bds/cli": patch +"@sfmc-bds/create-module": patch +"@sfmc-bds/devkit": patch +--- + +统一模块模板和公开文档的 pnpm 工作流,并将 registry 模块下载改为 pnpm,补齐公开包仓库元数据。 diff --git a/.github/workflows/module-publish.yml b/.github/workflows/module-publish.yml new file mode 100644 index 00000000..40e6e272 --- /dev/null +++ b/.github/workflows/module-publish.yml @@ -0,0 +1,148 @@ +name: module-publish + +# 从主仓发布独立模块。NPM_TOKEN 只配置在主仓;源代码必须是模块仓 main 上的指定提交。 +on: + workflow_dispatch: + inputs: + module: + description: 模块仓后缀 + required: true + type: choice + options: + - activity-log + - afk + - area + - chat + - chat-sounds + - clean + - coop + - data-backup + - economy + - fly-area + - gamemode-area + - inventory-switcher + - land + - monitor + - online-time + - peace-area + - qa + - qq-link + - spawn-protect + commit: + description: 模块仓 main 上的完整提交 SHA + required: true + type: string + version: + description: 与 package.json 一致的正式版本 + required: true + type: string + dry_run: + description: 仅检查并上传打包产物 + required: true + default: true + type: boolean + +permissions: + contents: read + +concurrency: + group: module-publish-${{ inputs.module }} + cancel-in-progress: false + +jobs: + publish: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + repository: Tanya7z/sfmc-module-${{ inputs.module }} + ref: ${{ inputs.commit }} + fetch-depth: 0 + persist-credentials: false + + - uses: pnpm/action-setup@v4 + with: + version: 10.8.0 + run_install: false + + - uses: actions/setup-node@v4 + with: + node-version: "22.14" + cache: pnpm + registry-url: https://registry.npmjs.org + + - name: 校验来源与版本 + env: + MODULE: ${{ inputs.module }} + COMMIT: ${{ inputs.commit }} + VERSION: ${{ inputs.version }} + run: | + git fetch origin main + node -e 'const fs=require("node:fs");const p=JSON.parse(fs.readFileSync("package.json","utf8"));const m=process.env.MODULE;const v=process.env.VERSION;const c=process.env.COMMIT;if(!/^[0-9a-f]{40}$/.test(c)||!/^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/.test(v)||p.name!==`@sfmc-bds/module-${m}`||p.version!==v||p.private===true){throw new Error("模块名称、提交或正式版本与输入不符")}'; + test "$(git rev-parse HEAD)" = "${COMMIT}" + git merge-base --is-ancestor HEAD origin/main + + - name: 校验主仓发布凭据 + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + GH_TOKEN: ${{ secrets.SFMC_GITHUB_TOKEN }} + MODULE: ${{ inputs.module }} + run: | + if [ -z "${NODE_AUTH_TOKEN}" ] || [ -z "${GH_TOKEN}" ]; then + echo "::error::主仓发布凭据未配置" + exit 1 + fi + pnpm whoami >/dev/null + can_push="$(gh api "repos/Tanya7z/sfmc-module-${MODULE}" --jq '.permissions.push')" + if [ "${can_push}" != "true" ]; then + echo "::error::SFMC_GITHUB_TOKEN 无法写入模块仓" + exit 1 + fi + + - run: pnpm install --frozen-lockfile + - run: pnpm run typecheck + - run: pnpm run lint + - run: pnpm run test + - name: 打包候选产物 + run: mkdir -p artifacts && pnpm pack --pack-destination artifacts + - uses: actions/upload-artifact@v4 + with: + name: module-${{ inputs.module }}-${{ inputs.version }} + path: artifacts/*.tgz + retention-days: 14 + + - name: 发布到 npm + if: inputs.dry_run == false + env: + NODE_AUTH_TOKEN: ${{ secrets.NPM_TOKEN }} + run: | + if [ -z "${NODE_AUTH_TOKEN}" ]; then + echo "::error::主仓 NPM_TOKEN 未配置" + exit 1 + fi + pnpm publish --access public --tag latest --no-git-checks + + - name: 创建模块版本标签与 GitHub Release + if: inputs.dry_run == false + env: + GH_TOKEN: ${{ secrets.SFMC_GITHUB_TOKEN }} + MODULE: ${{ inputs.module }} + VERSION: ${{ inputs.version }} + COMMIT: ${{ inputs.commit }} + run: | + repo="Tanya7z/sfmc-module-${MODULE}" + tag="v${VERSION}" + if [ -z "${GH_TOKEN}" ]; then + echo "::error::主仓 SFMC_GITHUB_TOKEN 未配置" + exit 1 + fi + existing="$(gh api "repos/${repo}/git/ref/tags/${tag}" --jq .object.sha 2>/dev/null || true)" + if [ -n "${existing}" ] && [ "${existing}" != "${COMMIT}" ]; then + echo "::error::${tag} 已指向其他提交" + exit 1 + fi + if [ -z "${existing}" ]; then + gh api -X POST "repos/${repo}/git/refs" -f ref="refs/tags/${tag}" -f sha="${COMMIT}" + fi + gh release view "${tag}" --repo "${repo}" >/dev/null 2>&1 || \ + gh release create "${tag}" --repo "${repo}" --target "${COMMIT}" --title "${tag}" --generate-notes diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a42e0a68..19922a42 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -10,7 +10,6 @@ ```bash pnpm install && pnpm run build && pnpm run verify -npm install && npm run build --workspaces --if-present && npm run verify ``` 文档站维护见 [docs/CONTRIBUTING-DOCS.md](./docs/CONTRIBUTING-DOCS.md)。 diff --git a/README.en.md b/README.en.md index b146cfc2..5f6c6ea2 100644 --- a/README.en.md +++ b/README.en.md @@ -84,7 +84,7 @@ Ensure **Node.js ≥ 22.13.0** is installed (required for native `node:sqlite` s ```bash # 1. Install SFMC CLI globally -npm install -g @sfmc-bds/sfmc +pnpm add -g @sfmc-bds/sfmc@beta # 2. Create and enter your server workspace mkdir my-bedrock-server && cd my-bedrock-server @@ -105,7 +105,7 @@ You don't need to clone this monorepo! Scaffold your standalone module repositor ```bash # 1. Interactively scaffold your author repository -npm create @sfmc-bds/module@latest +pnpm dlx @sfmc-bds/create-module@latest # 2. Open the created project in VS Code or Cursor # 3. Install the official "SFMC Module" extension @@ -127,7 +127,7 @@ This repository houses the SFMC core platform and tooling packages, organized wi | `packages/db-server` | `@sfmc-bds/db-server` | High-performance SQLite HTTP data hub, transaction engine, and token sandbox | | `packages/qq-bridge` | `@sfmc-bds/qq-bridge` | Tencent QQ Official Bot and OneBot 11 two-way communication gateway | | `packages/bds-tools` | `@sfmc-bds/bds-tools` | BDS version updater and add-on inbox deployment pipeline | -| `packages/create-module` | `@sfmc-bds/create-module` | Official module scaffolding engine (`npm create @sfmc-bds/module`) | +| `packages/create-module` | `@sfmc-bds/create-module` | Official module scaffolding engine (`pnpm dlx @sfmc-bds/create-module`) | | `packages/devkit` | `@sfmc-bds/devkit` | Incremental transpiler (esbuild), file watcher, and reload core | | `packages/sfmc-extension`| `@sfmc-bds/sfmc-extension` | Official VS Code / Cursor IDE extension ("SFMC Module") | | `packages/meta` | `@sfmc-bds/sfmc` | Meta-package aggregator distribution | diff --git a/README.md b/README.md index 33802772..bc78698d 100644 --- a/README.md +++ b/README.md @@ -34,17 +34,14 @@ SFMC 希望通过模块化架构补充基岩版的原生开发体验。 ## 快速开始 -### 使用 npm 安装 +### 使用 pnpm 安装 ```bash # 检查 Node.js 版本(需要 v22.13+) node -v -# 安装 SFMC CLI -npm install -g @sfmc-bds/sfmc - -# Beta 版本 -npm install -g @sfmc-bds/sfmc@beta +# 安装当前公测版 SFMC CLI +pnpm add -g @sfmc-bds/sfmc@beta # 创建服务器目录 mkdir my-server && cd my-server diff --git a/docs/en/guide/backup-upgrade.mdx b/docs/en/guide/backup-upgrade.mdx index c065746e..f89d120d 100644 --- a/docs/en/guide/backup-upgrade.mdx +++ b/docs/en/guide/backup-upgrade.mdx @@ -31,7 +31,7 @@ Do not copy `.db` files while db-server is running. ```bash -npm i -g @sfmc-bds/sfmc@beta +pnpm add -g @sfmc-bds/sfmc@beta ``` @@ -40,7 +40,6 @@ npm i -g @sfmc-bds/sfmc@beta ```bash git pull pnpm install && pnpm run build -npm install && npm run build --workspaces --if-present ``` diff --git a/docs/en/guide/index.mdx b/docs/en/guide/index.mdx index 2d779922..7d46d218 100644 --- a/docs/en/guide/index.mdx +++ b/docs/en/guide/index.mdx @@ -35,7 +35,7 @@ flowchart LR ```bash -npm i -g @sfmc-bds/sfmc@beta +pnpm add -g @sfmc-bds/sfmc@beta mkdir my-server && cd my-server sfmc ``` @@ -47,7 +47,6 @@ sfmc git clone https://github.com/DogeLakeDev/ScriptsForMinecraftServer.git cd ScriptsForMinecraftServer pnpm install && pnpm run build -npm install && npm run build --workspaces --if-present sfmc ``` @@ -117,12 +116,10 @@ sfmc> mod reload -Create with `npm create @sfmc-bds/module@latest`: +Create with `pnpm dlx @sfmc-bds/create-module@latest`: ```bash -npm create @sfmc-bds/module@latest cd my-feature && pnpm install && pnpm run typecheck -cd my-feature && npm install && npm run typecheck ``` Link into your SFMC workspace (module short id required). Authors use extension Link / Watch; on the server: diff --git a/docs/superpowers/specs/modules/AGENT_BRIEF.md b/docs/superpowers/specs/modules/AGENT_BRIEF.md index b5d914f5..f045e718 100644 --- a/docs/superpowers/specs/modules/AGENT_BRIEF.md +++ b/docs/superpowers/specs/modules/AGENT_BRIEF.md @@ -5,8 +5,7 @@ ## 1. 脚手架 ```bash -npm create @sfmc-bds/module@latest -- --official -# 或: npx @sfmc-bds/create-module@latest --official +pnpm dlx @sfmc-bds/create-module@latest --official ``` - install id = 规格中的短名(kebab-case) diff --git a/docs/zh/CONTRIBUTING-DOCS.mdx b/docs/zh/CONTRIBUTING-DOCS.mdx index 9c322791..68f4572c 100644 --- a/docs/zh/CONTRIBUTING-DOCS.mdx +++ b/docs/zh/CONTRIBUTING-DOCS.mdx @@ -29,11 +29,8 @@ i18n.json ```bash pnpm install -npm install -pnpm run docs -- serve -npm run docs -- serve -pnpm run docs -- build -npm run docs -- build +pnpm run docs serve +pnpm run docs build ``` ## 配置入口 @@ -52,4 +49,3 @@ npm run docs -- build ## 扩展 TypeDoc 入口 在 `website/rspress.config.ts` 的 `pluginTypeDoc({ entryPoints })` 与根目录 `typedoc.json` 同步追加 SDK 公开入口。 - diff --git a/docs/zh/api/sdk/index.md b/docs/zh/api/sdk/index.md index 723207ee..67a396c5 100644 --- a/docs/zh/api/sdk/index.md +++ b/docs/zh/api/sdk/index.md @@ -1,6 +1,6 @@ # SDK 导读 -包名 `@sfmc-bds/sdk`。本文是**用法导读**;完整签名见 [SDK 类型参考](../reference/index.md)(`pnpm run docs -- api` / `npm run docs -- api`)。 +包名 `@sfmc-bds/sdk`。本文是**用法导读**;完整签名见 [SDK 类型参考](../reference/index.md)(`pnpm run docs api`)。 ## 四抽屉 diff --git a/docs/zh/dev/architecture.md b/docs/zh/dev/architecture.md index be5d1914..ceff7972 100644 --- a/docs/zh/dev/architecture.md +++ b/docs/zh/dev/architecture.md @@ -168,7 +168,7 @@ ScriptsForMinecraftServer/ │ ├── devkit/ # 模块文件监听(Watch)与脚手架转译引擎 │ ├── cli/ # @sfmc-bds/cli 终端交互与进程 Supervisor │ ├── meta/ # @sfmc-bds/sfmc 全局命令行入口包装 -│ ├── create-module/ # npm create @sfmc-bds/module 脚手架生成器 +│ ├── create-module/ # pnpm dlx @sfmc-bds/create-module 脚手架生成器 │ ├── sfmc-extension/ # VS Code / Cursor 官方开发专属扩展 │ └── tools/ # 仓内专用测试、验证与文档生成工具集 └── docs/ # 基于 Rspress 2 的多语言文档站源码 diff --git a/docs/zh/dev/contributing.md b/docs/zh/dev/contributing.md index 1f678969..4e9c3ab7 100644 --- a/docs/zh/dev/contributing.md +++ b/docs/zh/dev/contributing.md @@ -7,57 +7,55 @@ ## 环境 - **Node.js ≥ 22.13** -- **pnpm workspace**(`pnpm-workspace.yaml`,主仓推荐)或 **npm workspaces**;本地 `@sfmc-bds/*` 用 `^`,禁止 `workspace:*` +- **pnpm workspace**(`pnpm-workspace.yaml`);本地 `@sfmc-bds/*` 用 `^`,禁止 `workspace:*` - 可选:Python 3.12 + `pip install -r docs/requirements.txt` ```bash pnpm install && pnpm run build && pnpm run verify -npm install && npm run build --workspaces --if-present && npm run verify ``` ## 根脚本 -下文 **pnpm / npm 等价**(单包 filter 写法见第二列)。 +以下命令统一使用 pnpm。 ### 日常 -| pnpm | npm | 说明 | -| ------ | ------ | ------ | -| `pnpm run build` | `npm run build --workspaces --if-present` | 各 workspace build | -| `pnpm run typecheck` | `npm run typecheck` | 各 workspace typecheck | -| `pnpm run lint` | `npm run lint` | build eslint-plugin 后 `eslint .` | -| `pnpm start` | `npm start` | CLI | +| 命令 | 说明 | +| ------ | ------ | +| `pnpm run build` | 各 workspace build | +| `pnpm run typecheck` | 各 workspace typecheck | +| `pnpm run lint` | build eslint-plugin 后 `eslint .` | +| `pnpm start` | CLI | -单包:`pnpm --filter @sfmc-bds/sdk run build` / `npm run build -w @sfmc-bds/sdk`。 +单包:`pnpm --filter @sfmc-bds/sdk run build`。 ### 自检 -| pnpm | npm | 说明 | -| ------ | ------ | ------ | -| `pnpm run verify` | `npm run verify` | 平台集成自检 | +| 命令 | 说明 | +| ------ | ------ | +| `pnpm run verify` | 平台集成自检 | ### 文档 ```bash -pnpm run docs -- api|serve|build -npm run docs -- api|serve|build +pnpm run docs api +pnpm run docs serve +pnpm run docs build ``` ### 依赖对齐 ```bash pnpm run syncpack:fix -npm run syncpack:fix pnpm exec syncpack format --check -npx syncpack format --check ``` ### 平台发包 -| pnpm | npm | 说明 | -| ------ | ------ | ------ | -| `pnpm run changeset` | `npm run changeset` | 添加 changeset;push `main` 后 CI 开 Version PR | -| `node packages/tools/pack-verify.mjs` | 同上 | 可选:`pnpm pack` / `npm pack` 冒烟 | +| 命令 | 说明 | +| ------ | ------ | +| `pnpm run changeset` | 添加 changeset;push `main` 后 CI 开 Version PR | +| `node packages/tools/pack-verify.mjs` | 检查发布包内容 | 日常发版:**合并 Version PR 即可**,无需本地 `prerelease` / `release`。当前 **beta-only**。模块作者发包见 [发布你的模块](./publish.md)。 diff --git a/docs/zh/dev/eslint.md b/docs/zh/dev/eslint.md index cbf42928..16d8f1d2 100644 --- a/docs/zh/dev/eslint.md +++ b/docs/zh/dev/eslint.md @@ -7,7 +7,7 @@ 主仓 workspace 已包含。作者仓: ```bash -npm i -D @sfmc-bds/eslint-plugin eslint @typescript-eslint/parser +pnpm add -D @sfmc-bds/eslint-plugin eslint @typescript-eslint/parser ``` ## Flat config @@ -27,7 +27,7 @@ export default [ 更严:改用 `sfmc.configs.all`(部分 warn 升为 error)。 -主仓根 `eslint.config.js` 已接入;`pnpm run lint` / `npm run lint` 会先 build 插件再跑。 +主仓根 `eslint.config.js` 已接入;`pnpm run lint` 会先 build 插件再跑。 ## 规则(recommended) @@ -63,6 +63,4 @@ SFMC Module 扩展在 `package.json` 中通过 `extensionRecommendations` 推荐 ```bash pnpm --filter @sfmc-bds/eslint-plugin run build pnpm --filter @sfmc-bds/eslint-plugin typecheck -npm run build -w @sfmc-bds/eslint-plugin -npm run typecheck -w @sfmc-bds/eslint-plugin ``` diff --git a/docs/zh/dev/index.mdx b/docs/zh/dev/index.mdx index 459cf7a2..2651a21d 100644 --- a/docs/zh/dev/index.mdx +++ b/docs/zh/dev/index.mdx @@ -29,7 +29,7 @@ flowchart LR | :--- | :--- | :--- | | **操作系统** | Windows 10 / 11 或 Linux (Ubuntu 22.04+) | 建议开发机配置好本地 Node.js 与 Git 环境。 | | **Node.js** | **`≥ 22.13.0`** (LTS) | 必须满足该版本以获得 Node 原生内置的 `node:sqlite` 支持。 | -| **包管理** | **`pnpm`** (推荐) 或 `npm` | 主仓采用 pnpm workspaces;本地开发包严禁使用 `workspace:*` 外部依赖。 | +| **包管理** | **`pnpm`** | 主仓采用 pnpm workspaces;本地开发包严禁使用 `workspace:*` 外部依赖。 | | **编辑器** | [Cursor](https://cursor.com/) 或 [VS Code](https://code.visualstudio.com/) | 搭配专属扩展体验最佳。 | ### 必备与推荐 VS Code / Cursor 扩展 @@ -52,7 +52,7 @@ flowchart LR ```bash # 模块作者开发最小闭环 -npm create @sfmc-bds/module@latest +pnpm dlx @sfmc-bds/create-module@latest cd my-feature && pnpm install && pnpm run typecheck # 联调:通过 VS Code 扩展点击 "Link to SFMC Root" diff --git a/docs/zh/dev/module-author.mdx b/docs/zh/dev/module-author.mdx index ab8f4812..2ed98b69 100644 --- a/docs/zh/dev/module-author.mdx +++ b/docs/zh/dev/module-author.mdx @@ -8,9 +8,7 @@ ```bash # 交互式初始化向导 -npm create @sfmc-bds/module@latest -# 或使用 npx -npx @sfmc-bds/create-module@latest +pnpm dlx @sfmc-bds/create-module@latest ``` 向导将引导您填写并配置: @@ -28,8 +26,6 @@ npx @sfmc-bds/create-module@latest ```bash cd my-feature pnpm install && pnpm run typecheck -# 或使用 npm -npm install && npm run typecheck ``` ## 2. 工程规范与命名矩阵 @@ -50,10 +46,10 @@ SFMC 将模块开发划分为 5 个清晰阶段: ```mermaid flowchart LR - A[1. 脚手架建仓
npm create] --> B[2. 类型与语法检查
typecheck & lint] + A[1. 脚手架建仓
pnpm dlx] --> B[2. 类型与语法检查
typecheck & lint] B --> C[3. 挂接工作区
--link 软链接] C --> D[4. 实时监听
Watch & Hot Reload] - D --> E[5. 生态发布
npm & 官方索引 PR] + D --> E[5. 生态发布
pnpm publish & 官方索引 PR] ``` ### ① 编写与静态检查(Type Safety & Lint) diff --git a/docs/zh/dev/publish.md b/docs/zh/dev/publish.md index 53044d00..d951e99c 100644 --- a/docs/zh/dev/publish.md +++ b/docs/zh/dev/publish.md @@ -3,7 +3,7 @@ 把作者仓发到 npm,并登记到 [`sfmc-modules`](https://github.com/Tanya7z/sfmc-modules) 薄 index,供 `mod search` / `mod install` 发现。 业务模块 **不走** 主仓 changesets。平台 `@sfmc-bds/*` 发包见文末。 -`sfmc mod publish` 已移除;请用扩展 `SFMC: Publish to npm`,或直接 `pnpm publish` / `npm publish`(及可选 `gh` 开 index PR)。 +`sfmc mod publish` 已移除;请用扩展 `SFMC: Publish to npm`,或直接 `pnpm publish`(及可选 `gh` 开 index PR)。 ## 发布前 @@ -13,14 +13,12 @@ | `name` | `@/sfmc-module-` 或官方 `@sfmc-bds/module-` | | `files` | 含 `sapi` | | 类型检查 / lint | `pnpm run typecheck` 与 `pnpm run lint`;发布前按 [验证指南](./testing.md) 完成实际联调 | -| 登录 | `npm login`(npm registry 账号);官方 scope 另需组织权限 | +| 发布权限 | npm registry 账号或 CI 发布凭据;官方 scope 另需组织权限 | ```bash pnpm publish --access public -npm publish --access public # 或按包上的 dist-tag: pnpm publish --tag beta -npm publish --tag beta ``` 向薄 index 开 PR:在 `sfmc-modules` 仓库的 `index.json` 增加条目(`id` / `npm` / 版本说明)。可用 GitHub CLI 自行开 PR。 @@ -40,4 +38,4 @@ npm publish --tag beta ## 附录:平台包发布(贡献者) -主仓 `@sfmc-bds/*` 走 changesets:`pnpm run changeset` / `npm run changeset` → Version PR → `ci-release-packages`。详见 [贡献指南](./contributing.md)。 +主仓 `@sfmc-bds/*` 走 changesets:`pnpm run changeset` → Version PR → `ci-release-packages`。详见 [贡献指南](./contributing.md)。 diff --git a/docs/zh/dev/stable-release-plan.md b/docs/zh/dev/stable-release-plan.md new file mode 100644 index 00000000..e4fc1746 --- /dev/null +++ b/docs/zh/dev/stable-release-plan.md @@ -0,0 +1,86 @@ +# SFMC 正式版行动计划 + +状态:执行中。本文记录发布门槛与证据;版本、运行状态和线上结果以执行当天的检查为准。 + +## 范围与顺序 + +1. 平台 monorepo 中所有公开的 `@sfmc-bds/*` 包,重点是 SDK、CLI、db-server、qq-bridge、bds-tools、devkit、create-module 和聚合包 `sfmc`。 +2. `D:\WorkPlace\_proj\sfmc` 下的 19 个独立模块仓。保留各仓现有未提交改动,逐仓确认功能与发布内容。 +3. 玩家运行环境中的部署包、BDS 日志和客户端行为。源码、npm 包与运行环境分别验收。 + +按依赖顺序发布:平台基础包 → 聚合包 → 官方模块 → 模块索引和文档。使用 pnpm 完成安装、检查、打包与发布;修正仓库内仍调用 npm 的自动化入口。版本号以各包现有语义化版本和实际兼容性决定,不把所有包强行统一为 `1.0.0`。 + +## 阶段一:冻结候选范围 + +- [ ] 记录各仓 HEAD、工作区差异、待消费 changeset、已发布版本和 npm dist-tag。 +- [ ] 将未提交改动按功能分组;逐项决定纳入候选、延后或撤回,不覆盖无关改动。 +- [ ] 记录模块与平台、Minecraft Script API、BDS 的依赖兼容矩阵。 +- [ ] 确定首个正式版的各包版本与升级说明。 + +完成证据:每个公开包有候选提交或明确排除说明;候选源与构建源一致。 + +## 阶段二:标准项目与发布链检查 + +- [ ] 核对公开包的名称、版本、入口与 exports、files、许可、README、仓库与问题反馈地址、运行环境要求、依赖和 peerDependencies。 +- [ ] 核对 pnpm 锁文件与可重复安装;CI 不隐式改锁或混用包管理器。 +- [ ] 核对 pack 内容:源码与声明文件、可执行入口、默认配置和必要资源齐全,不含运行数据或凭据。 +- [ ] 核对平台与模块的 CI、发布权限、Git tag、npm dist-tag、GitHub Release 和模块索引流程。 +- [ ] 更新中英文安装、升级、发布和回退文档,移除过时的 beta-only 说明。 + +完成证据:在干净检出中用 pnpm 安装、构建、检查并验证打包内容;发布流程能生成可审查的候选版本。 + +## 阶段三:功能、升级与运行验收 + +- [ ] 对受影响包运行类型检查、构建、平台自检和相称的静态检查;确认 Windows 与 Ubuntu CI 通过。 +- [ ] 用候选包在隔离的 SFMC_ROOT 中完成安装、初始化、模块安装、构建与加载演练。 +- [ ] 对现有数据做备份、升级、恢复和回退演练,检查数据库迁移及配置兼容。 +- [ ] 在目标 BDS 版本上检查部署包、重启后的新日志和客户端关键路径;运行中 BDS 的重启须先确认影响窗口。 +- [ ] 记录所有未通过项及修复后的复验结果,不以静态构建代替运行验收。 + +完成证据:候选包、部署包和实际运行的版本可追溯;升级与回退均有成功记录。 + +## 阶段四:发布与复核 + +- [ ] 通过前三阶段后退出 Changesets `beta` 预发布模式,审查 Version PR 的版本与包间依赖。 +- [ ] 按依赖顺序发布平台、聚合包和选定模块;核对 npm `latest`、Git tag、GitHub Release 和模块索引。 +- [ ] 从公共 registry 进行全新 pnpm 安装,复验 CLI、模块安装和基本功能。 +- [ ] 发布正式版公告,列明兼容范围、升级步骤、已知限制和回退版本。 + +完成证据:公共渠道可安装正式版,版本与产物一致,且发布后的最小验收通过。 + +## 当前基线(2026-09-27) + +- 平台主仓 HEAD `fe453d4`,本轮工作区已有多项未提交改动;该提交的 `ootb` Windows/Ubuntu 和 `changeset-release` CI 成功。 +- 平台仍在 Changesets `pre` / `beta` 模式;待消费 changeset 为 0。 +- 19 个模块仓均有未提交改动。初查其中 17 个只替换 JSON Schema URL;`chat` 与 `qq-link` 有功能改动,需要单独审查。 +- `chat`、`qq-link` 仍有 npm 驱动的脚本或 CI;pnpm 类型检查未真正运行,依赖准备分别被构建脚本许可和非交互目录确认挡住。 + +以上只是起点,不构成正式版验收结论。 + +## 执行记录(2026-09-27) + +- 19 个模块仓已补齐 `license`、`repository`、`publishConfig` 和 `packageManager`;用 pnpm 锁文件替换了未改动的 npm 锁文件,CI 改用 pnpm;均新增或统一了先验证、打包、发布,再创建标签的发布工作流。模块索引登记仍需在发布阶段逐项完成。 +- 平台发布辅助脚本的直接 `npx` / npm 调用已改为 pnpm;`create-module` 模板与两个公开包的仓库元数据已修正,并添加 changeset。相关包类型检查与 Changesets 状态检查通过。 +- 19 个模块均已通过 pnpm 冻结安装、类型检查和 lint。`qq-link` 的旧管理 API 引用已移除,踢人路径改为服务端命令;该行为仍需在 BDS 中验收。 +- `qq-link` 的现有测试在本机 Node 26 环境中因 `uv_os_get_passwd` 返回 `ENOMEM` 未运行成功,不能据此判定功能通过;需在目标 Node 22 CI 中复验。 +- 公共 registry 查询时,`@sfmc-bds/sfmc` 与 SDK 的 `latest` 均为 `0.1.0`,beta 分别为 `0.2.0-beta.16` 与 `0.2.0-beta.18`;`create-module` 的 `latest` 仍指向 `0.1.0-beta.4`。正式发布后须复核这些 dist-tag。 +- 19 个模块的发布工作流已通过 YAML 解析;发布阶段仍需实际 dry run、凭据可用性检查和远程 CI。现有两个模块的旧索引自动登记逻辑被统一工作流取代,发布后必须单独完成索引更新。 +- 19 个模块均通过 pnpm 打包内容检查:包含 package.json、SAPI manifest、README、许可文件,未包含运行配置、数据或依赖目录。 +- 平台 `pnpm run build`、`pnpm run lint`、`pnpm run typecheck`、`pnpm run verify` 和 9 个公开包的 pnpm 打包检查通过。`verify` 首次在隔离模拟目录清理时遇到沙箱 `EPERM`,在同一代码上使用所需文件权限复验通过 12/12。 +- 后续复核发现 `@sfmc-bds/create-module` 漏在平台发布包清单之外,已纳入清单并添加 changeset;pnpm 打包检查现覆盖全部 9 个公开包。 +- 本轮补齐了贡献指南、脚手架帮助和开发文档中的 npm 命令示例;`pnpm run typecheck` 通过,文档站以 `pnpm run docs build` 构建成功(跳过已有 TypeDoc 生成物)。 +- 最终复核中,主仓 `pnpm run build` 与 `node packages/tools/pack-verify.mjs` 通过(9 个公开包);`pnpm run lint` 通过且报告 35 条既有 warning、0 error。移除了未使用的 npm 子进程 helper。 +- 主仓全量命令入口扫描已清除 npm/npx 安装或脚本替代示例,并修正英文文档、CLI 提示和排障指南。最新 `pnpm run typecheck`、`pnpm run build`、`pnpm run verify` 通过;集成自检 12/12。db-server 自检现使用仓库 `tmp/` 下的一次性隔离根目录与动态端口,不读写真实模块目录;模块接口断言按 catalog schema 移除了不存在的 `type` 字段。 +- 对 19 个独立模块仓再次扫描后,README 与仓库生成器残留的安装/检查命令也已改为 pnpm;全部 19 个均有 `pnpm-lock.yaml`。 +- CLI 的 registry 与 tarball 模块获取路径已从内部 npm 调用切换为 pnpm 临时安装后复制包文件;新增 Windows 子进程兼容依赖,关闭安装脚本和临时工作区 peer 自动安装。使用 `activity-log` 0.2.1 tarball 在隔离 SFMC_ROOT 中验证 tgz 路径与 registry 分支(以本地 tarball 模拟包来源),二者均完成配置播种、catalog/lock 更新及 `check-modules OK`。这不替代正式 registry 查询和 BDS 客户端运行验收。 +- GitHub CLI 当前登录 token 无效(`gh auth status`);远程推送、PR/CI 和发布工作流尚未验证或执行。 +- 当前公共查询中 19 个 `@sfmc-bds/module-*` 包均未查到公开版本,需按首次发布准备 npm scope 权限与逐包登记。 +- 目标 BDS 可执行文件版本为 `1.26.51.1`;检查时未发现运行中的 `bedrock_server` 进程。已有 staged 与部署聚合包的 Script API 依赖一致,`scripts/main.js` SHA256 一致,但它们不是本轮候选的新构建,不能代表新模块功能通过。 + +- 本轮最后一轮本地门禁复验:`pnpm run build`、9 个公开包的 `node packages/tools/pack-verify.mjs`、`pnpm run verify`(12/12)、`pnpm run docs build` 和 `pnpm exec changeset status` 均通过;lint 为 0 error、35 条既有 warning。`git diff --check` 有若干既存/生成 changelog 空白行提示,尚未为此做大范围格式改写。 +- pnpm CLI 模块安装器已用 `activity-log` tarball 在隔离 SFMC_ROOT 中验证安装、配置播种、catalog/lock 更新及 `check-modules OK`;所谓 registry 分支是用本地 tarball 模拟来源,仍待公共 registry 实际安装复验。 +- GitHub device login 已完成。在可访问系统凭据存储的执行环境中,`gh auth status` 确认以 `Tanya7z` 登录,主仓具备推送和管理权限;默认沙箱内的旧凭据报错不再视为远程操作阻断。当前仍处于 beta,不得跳过验收直接切正式版。 +- 针对两个包含功能改动的模块补做复核:`chat` 的 pnpm 类型检查与 lint 通过;`qq-link` 的 pnpm 类型检查通过。其原有测试在 Node 24/26 均因本机 `os.userInfo()` 的 `uv_os_get_passwd ENOMEM` 无法启动;用仅作用于该次 Node 进程的兼容 preload 绕过系统用户名读取后,原有 3 项测试全部通过。该结果覆盖策略函数和 manifest 契约,不替代 BDS 里的入服门槛、绑定放行、踢人命令运行验收。 +- 发布鉴权采用 GitHub Actions;本机 `pnpm login` / `pnpm whoami` 不是必要发布门禁。GitHub Secrets 清单已核实主仓有 `NPM_TOKEN` 和 `SFMC_GITHUB_TOKEN`(仅确认名称,未读取值)。主仓 Changesets workflow 可使用这些 secret 创建 Version PR 与发布平台包。 +- 19 个独立模块仓的 GitHub Secrets 清单均无 `NPM_TOKEN`,而各仓当前 release workflow 在发布前要求它;因此模块独立发布通道尚不能使用。需在模块仓配置发布凭据,或完成并验证由主仓集中发布的工作流。现有 `id-token: write` 尚未接入 npm Trusted Publishing,不能当作已配置的无 token 发布通道。 +- 主仓新增手动 `module-publish` 工作流:只接受 19 个已知模块、模块仓 `main` 上的完整提交 SHA 和与 `package.json` 一致的正式版本;默认只运行凭据、pnpm 检查与打包。dry run 会用主仓的 `NPM_TOKEN` 查询 npm 登录态,并核对 `SFMC_GITHUB_TOKEN` 对模块仓的推送权限;实际发布时再用它们发布 npm 包、创建标签与 Release。此流程尚待推送后的 dry run 和首包发布复验;各模块仓原有独立发布 workflow 仍依赖本仓未配置的 secret,应在集中发布路径验证后统一收敛。 diff --git a/docs/zh/dev/style-sample.mdx b/docs/zh/dev/style-sample.mdx index 941c42f3..bfb3c3ac 100644 --- a/docs/zh/dev/style-sample.mdx +++ b/docs/zh/dev/style-sample.mdx @@ -34,10 +34,10 @@ flowchart LR - + ```bash -npm i -g @sfmc-bds/sfmc@beta +pnpm add -g @sfmc-bds/sfmc@beta mkdir my-server && cd my-server sfmc ``` @@ -49,7 +49,6 @@ sfmc git clone https://github.com/DogeLakeDev/ScriptsForMinecraftServer.git cd ScriptsForMinecraftServer pnpm install && pnpm run build -npm install && npm run build --workspaces --if-present sfmc ``` @@ -110,12 +109,10 @@ sfmc> mod reload -用 `npm create @sfmc-bds/module@latest` 建仓: +用 `pnpm dlx @sfmc-bds/create-module@latest` 建仓: ```bash -npm create @sfmc-bds/module@latest cd my-feature && pnpm install && pnpm run typecheck -cd my-feature && npm install && npm run typecheck ``` 再在 SFMC 工作目录联调(须写明模块短名);作者侧用扩展 Link / Watch,运维侧: diff --git a/docs/zh/dev/testing.md b/docs/zh/dev/testing.md index c7ef097d..152cf9b4 100644 --- a/docs/zh/dev/testing.md +++ b/docs/zh/dev/testing.md @@ -15,6 +15,6 @@ pnpm run lint ## 平台包 -优先运行改动所在包的 `typecheck` 和 `build`。修改公共 SDK、安装器或发布行为时,再运行根目录 `pnpm run verify` 等相关集成自检。涉及 BDS 世界包或客户端资源时,确认源码、构建产物、部署包、服务端日志和客户端表现分别处于预期状态。 +优先运行改动所在包的 `typecheck` 和 `build`。修改公共 SDK、安装器或发布行为时,再运行根目录 `pnpm run verify` 等相关集成自检。涉及 BDS 资源包或客户端资源时,确认源码、构建产物、部署包、服务端日志和客户端表现分别处于预期状态。 一次性验证命令可以用于排查问题;功能完成后不要留下未被产品运行链路使用的测试脚本。没有执行的运行验收需要在交付说明中明确标注。 diff --git a/docs/zh/dev/tools.md b/docs/zh/dev/tools.md index 1cffc360..6dd50b44 100644 --- a/docs/zh/dev/tools.md +++ b/docs/zh/dev/tools.md @@ -6,15 +6,15 @@ | 命令 | 作用 | |------|------| -| `pnpm run verify` / `npm run verify` | 平台集成自检(CI 默认) | +| `pnpm run verify` | 平台集成自检(CI 默认) | | `sfmc mod install ` | 安装模块(独立 SFMC 根,非主仓) | -| `npm create @sfmc-bds/module@latest` | 建仓(`@sfmc-bds/create-module`) | +| `pnpm dlx @sfmc-bds/create-module@latest` | 建仓(`@sfmc-bds/create-module`) | 主仓默认 **不装业务模块**;catalog / lock 由用户环境里的 `mod install` 维护,主仓不再提供 `catalog-sync` / `check-modules` 入口。 ## 发版 -日常:`pnpm run changeset` / `npm run changeset` → push `main` → Version Packages PR → 合并后 CI `ci-release-packages`。 +日常:`pnpm run changeset` → push `main` → Version Packages PR → 合并后 CI `ci-release-packages`。 ## 共享库 diff --git a/docs/zh/guide/backup-upgrade.mdx b/docs/zh/guide/backup-upgrade.mdx index 676755dd..99a090fa 100644 --- a/docs/zh/guide/backup-upgrade.mdx +++ b/docs/zh/guide/backup-upgrade.mdx @@ -75,11 +75,11 @@ SFMC 平台的各层组件相互解耦,可按需分别升级: ### ① 升级 SFMC CLI 与平台运行时 - + ```bash # 获取最新版官方 CLI -npm install -g @sfmc-bds/sfmc@beta +pnpm add -g @sfmc-bds/sfmc@beta # 验证更新后版本 sfmc version diff --git a/docs/zh/guide/index.mdx b/docs/zh/guide/index.mdx index e6e0a7fc..9b952b16 100644 --- a/docs/zh/guide/index.mdx +++ b/docs/zh/guide/index.mdx @@ -1,5 +1,3 @@ -import { Tab, Tabs } from "@rspress/core/theme"; - # 快速上手 欢迎使用 **ScriptsForMinecraftServer (SFMC)**。SFMC 是专为官方原版 Bedrock Dedicated Server (BDS) 打造的现代化 SAPI 行为包生态与 Node.js 伴生服务架构。 @@ -42,7 +40,7 @@ import { Tab, Tabs } from "@rspress/core/theme"; | :--- | :--- | :--- | | **操作系统** | Windows 10 / 11, Linux (Ubuntu 20.04+, Debian 11+) | Linux (云服务器长期运行) / Windows (本地调试) | | **Node.js** | `v22.13.0` 或更高版本(需内置 SQLite 支持) | 最新 LTS 版本(Node ≥22.13) | -| **包管理器** | `npm` 或 `pnpm` | `pnpm` | +| **包管理器** | `pnpm` | `pnpm` | | **磁盘空间** | ≥ 2 GB(用于存放 BDS 核心、行为包、备份与数据存档) | SSD 固态硬盘 | ## 快速安装与初始化 @@ -51,23 +49,10 @@ SFMC 提供了全局编排 CLI 工具,可实现从 BDS 自动下载配置到 ### 步骤 1:全局安装 SFMC CLI - - - ```bash pnpm add -g @sfmc-bds/sfmc@beta ``` - - - -```bash -npm install -g @sfmc-bds/sfmc@beta -``` - - - - :::tip 提示 当前版本处于公测阶段,请务必添加 `@beta` 标签进行安装。如需在特定文件夹持久化数据,也可通过配置 `SFMC_ROOT` 环境变量指定自定义工作目录。 ::: diff --git a/docs/zh/reference/index.md b/docs/zh/reference/index.md index 32c1d3ee..bfcdb577 100644 --- a/docs/zh/reference/index.md +++ b/docs/zh/reference/index.md @@ -4,4 +4,4 @@ 手写导读见 [接口指南 · SDK](../api/sdk/index.md)。 -生成命令:`pnpm run docs -- api` / `npm run docs -- api` 或 `pnpm run docs -- build` / `npm run docs -- build`。 +生成命令:`pnpm run docs api` 或 `pnpm run docs build`。 diff --git a/modules/sdk/@sfmc-eslint-plugin/README.md b/modules/sdk/@sfmc-eslint-plugin/README.md index 63957112..d17b391f 100644 --- a/modules/sdk/@sfmc-eslint-plugin/README.md +++ b/modules/sdk/@sfmc-eslint-plugin/README.md @@ -7,7 +7,7 @@ SFMC 模块 / SDK 约定的 ESLint 插件,形态对齐 [`eslint-plugin-minecra 主仓 workspace 已包含;sfmc-modules 通过 `file:` 依赖主仓路径。 ```bash -npm install -D @sfmc-bds/eslint-plugin +pnpm add -D @sfmc-bds/eslint-plugin ``` ## Flat config @@ -63,7 +63,7 @@ export default [ ```bash pnpm --filter @sfmc-bds/eslint-plugin run build -npm run build -w @sfmc-bds/eslint-plugin +pnpm --filter @sfmc-bds/eslint-plugin run build ``` 发布构建只包含插件源码;变更后运行构建、类型检查与 lint 验证。 diff --git a/modules/sdk/@sfmc-sdk/README.md b/modules/sdk/@sfmc-sdk/README.md index d2e003eb..a4aacbe3 100644 --- a/modules/sdk/@sfmc-sdk/README.md +++ b/modules/sdk/@sfmc-sdk/README.md @@ -19,7 +19,7 @@ ScriptsForMinecraftServer 平台 SDK。SAPI/Node umbrella,统一导出: ## 安装 ```bash -npm install @sfmc-bds/sdk +pnpm add @sfmc-bds/sdk ``` ## 模块作者使用 diff --git a/modules/sdk/@sfmc-sdk/build.mjs b/modules/sdk/@sfmc-sdk/build.mjs index 92a66fed..ec319402 100644 --- a/modules/sdk/@sfmc-sdk/build.mjs +++ b/modules/sdk/@sfmc-sdk/build.mjs @@ -16,7 +16,7 @@ function runTsc7(args) { try { tsc7Entry = require.resolve("@sfmc-bds/tools/tsc7"); } catch { - throw new Error("无法 resolve @sfmc-bds/tools/tsc7。请在 monorepo 根目录执行 npm install"); + throw new Error("无法 resolve @sfmc-bds/tools/tsc7。请在 monorepo 根目录执行 pnpm install"); } const result = spawnSync(process.execPath, [tsc7Entry, ...args], { stdio: "inherit", diff --git a/packages/bds-tools/README.md b/packages/bds-tools/README.md index ffae05c2..732c2dcb 100644 --- a/packages/bds-tools/README.md +++ b/packages/bds-tools/README.md @@ -5,7 +5,7 @@ SFMC BDS 工具集:自动更新、进程管理、行为包装配(pack-manage ## 安装 ```bash -npm install @sfmc-bds/bds-tools +pnpm add @sfmc-bds/bds-tools ``` ## 依赖 diff --git a/packages/bds-tools/recovery.js b/packages/bds-tools/recovery.js index 5130e020..335081fb 100644 --- a/packages/bds-tools/recovery.js +++ b/packages/bds-tools/recovery.js @@ -17,7 +17,7 @@ try { } catch (e) { const err = /** @type {NodeJS.ErrnoException} */ (e); if (err && (err.code === "ERR_MODULE_NOT_FOUND" || err.code === "MODULE_NOT_FOUND")) { - console.error("[BDSRecovery] dist/ 未找到,请先运行 `npm run build`"); + console.error("[BDSRecovery] dist/ 未找到,请先运行 `pnpm run build`"); process.exit(2); } throw e; diff --git a/packages/cli/README.md b/packages/cli/README.md index 07544e83..bb25b5f1 100644 --- a/packages/cli/README.md +++ b/packages/cli/README.md @@ -5,18 +5,17 @@ SFMC 管理 CLI:REPL、服务启停、模块安装、行为包 build/deploy。 ## 安装 ```bash -npm install -g @sfmc-bds/cli -# 或 -npx @sfmc-bds/cli status +pnpm add -g @sfmc-bds/cli +pnpm dlx @sfmc-bds/cli status ``` 服主推荐: ```bash -npm install -g @sfmc-bds/sfmc +pnpm add -g @sfmc-bds/sfmc ``` -仅 CLI(不含后端服务)仍可用 `npm install -g @sfmc-bds/cli`,完整平台请装聚合包。 +仅 CLI(不含后端服务)可用 `pnpm add -g @sfmc-bds/cli`,完整平台请装聚合包。 ## 依赖 diff --git a/packages/cli/package.json b/packages/cli/package.json index 0eb2eebc..5ce05174 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -59,6 +59,7 @@ "@sfmc-bds/bds-tools": "workspace:*", "@sfmc-bds/sdk": "workspace:*", "chalk": "^6.0.0", + "cross-spawn": "7.0.6", "esbuild": "^0.28.1" }, "devDependencies": { diff --git a/packages/cli/scripts/module-install/fetch-module.mjs b/packages/cli/scripts/module-install/fetch-module.mjs index 4b3927a5..d8a6b9cc 100644 --- a/packages/cli/scripts/module-install/fetch-module.mjs +++ b/packages/cli/scripts/module-install/fetch-module.mjs @@ -439,74 +439,61 @@ async function fromLocal(id, source, flags) { } /** - * `sfmc mod install ` 走 npm registry 的主路径。 + * `sfmc mod install ` 走包 registry 的主路径,由 pnpm 下载。 * - * 入口:`npm install --prefix packages/ --omit=dev --no-save --no-package-lock ` + * 入口:临时项目中执行 `pnpm add --prod --ignore-scripts `,再复制包文件。 * - 落地到 packages/,不进主仓根 node_modules(隔离安装) * - 成功后 upsert catalog/lock - * - 失败时把常见 npm 错误翻译成中文可读提示 + * - 失败时把常见 registry 错误翻译成中文可读提示 * @param {string} id * @param {string} pkgName * @param {any} flags */ -async function fromNpm(id, pkgName, flags) { +async function fromRegistry(id, pkgName) { const dir = await ensureTarget(id); - const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; - const { spawn: spawnChild } = await import("node:child_process"); - await /** @type {Promise} */ ( - new Promise((resolve, reject) => { - const proc = spawnChild( - npmCmd, - [ - "install", - "--prefix", - dir, - "--omit=dev", - "--no-save", - "--no-package-lock", - "--no-audit", - "--no-fund", - pkgName, - ], - { stdio: ["ignore", "pipe", "pipe"] } - ); - let stderr = ""; - proc.stderr?.on("data", (d) => { - stderr += d.toString(); - }); - proc.on("exit", (code) => { - if (code === 0) return resolve(); - reject(new Error(translateNpmInstallError(pkgName, stderr))); - }); - proc.on("error", (e) => reject(new Error(`spawn npm failed: ${e.message}`))); - }) - ); - console.log(`[fetch-module] installed ${id} from npm (${pkgName})`); + const os = await import("node:os"); + const tmpPrefix = await fsp.mkdtemp(path.join(os.tmpdir(), "sfmc-pnpm-install-")); + try { + await installWithPnpm(tmpPrefix, pkgName); + const installed = findInstalledModule(tmpPrefix, id, pkgName); + if (!installed) throw new Error(`pnpm add 完成,但 ${pkgName} 中未找到模块 ${id}`); + await copyDir(installed, dir); + } catch (error) { + await removePackageTarget(dir); + const message = error instanceof Error ? error.message : String(error); + throw new Error(translatePnpmInstallError(pkgName, message)); + } finally { + try { + await fsp.rm(tmpPrefix, { recursive: true, force: true }); + } catch { + /* best-effort */ + } + } + console.log(`[fetch-module] installed ${id} from registry (${pkgName})`); console.log(`[fetch-module] target: ${dir}`); afterInstall(id); } /** - * 翻译常见 npm install 错误为中文可读 + 下一步动作。 + * 翻译常见 pnpm registry 错误为中文可读 + 下一步动作。 * @param {string} pkgName * @param {string} stderr */ -function translateNpmInstallError(pkgName, stderr) { +function translatePnpmInstallError(pkgName, stderr) { const text = String(stderr || ""); - if (/404 Not Found/i.test(text) || /not found/i.test(text)) { - return `npm 包未找到: ${pkgName}。请检查 manifest id / scope 拼写,或换 --from local:./。`; + if (/ERR_PNPM_FETCH_404|404 Not Found|not found/i.test(text)) { + return `registry 中未找到包: ${pkgName}。请检查 manifest id / scope 拼写,或换 --from local:./。`; } - if (/EACCES|permission/i.test(text)) { - return `npm 权限错误: ${pkgName}。检查 npm registry 登录态或 token 权限。`; + if (/ERR_PNPM_FETCH_401|ERR_PNPM_FETCH_403|EACCES|permission/i.test(text)) { + return `registry 权限错误: ${pkgName}。检查 pnpm registry 登录态或 token 权限。`; } - if (/ERESOLVE/i.test(text)) { - return `npm 依赖冲突: ${pkgName}。提示: 用 --legacy-peer-deps 重试,或先确认 SDK/宿主版本兼容。`; + if (/ERR_PNPM_PEER_DEP_ISSUES|ERESOLVE/i.test(text)) { + return `依赖冲突: ${pkgName}。请先确认 SDK/宿主版本兼容。`; } - if (/ETIMEDOUT|ECONNRESET|ENOTFOUND/i.test(text)) { - return `npm 网络错误: ${pkgName}。检查代理 / 网络;或离线用 --from local:。`; + if (/ETIMEDOUT|ECONNRESET|ENOTFOUND|ERR_PNPM_FETCH/i.test(text)) { + return `registry 网络错误: ${pkgName}。检查代理 / 网络;或离线用 --from local:。`; } - /* 默认透传原始 stderr(保留 npm 提示的修复建议) */ - return `npm install ${pkgName} 失败:\n${text.trim()}`; + return `pnpm add ${pkgName} 失败:\n${text.trim()}`; } /** @@ -585,7 +572,7 @@ async function installLocalArtifact(id, absPath, flags, opts = {}) { const dir = await ensureTarget(id); if (isTgz) { - await extractTgz(absPath, dir); + await extractTgz(absPath, dir, id); } else { await unzip(absPath, dir); } @@ -773,69 +760,134 @@ async function unzip(zipPath, dstDir) { } /** - * 解 npm tarball(.tgz)到 packages/。 - * 委托 `npm` 本身(DRY;不重新发明 tar):`npm install ` 落临时 prefix, - * 再拷贝到 packages/。npm 已内置 tar-slip 防护。 + * 解 package tarball(.tgz)到 packages/。 + * 委托 pnpm 在临时项目中安装并安全解包,再将包文件复制到模块目录。 * @param {fs.PathLike} tgzPath * @param {string} dstDir + * @param {string} id */ -async function extractTgz(tgzPath, dstDir) { +async function extractTgz(tgzPath, dstDir, id) { const os = await import("node:os"); - const { spawn: spawnChild } = await import("node:child_process"); const tmpPrefix = await fsp.mkdtemp(path.join(os.tmpdir(), "sfmc-tgz-")); - const npmCmd = process.platform === "win32" ? "npm.cmd" : "npm"; + try { + await installWithPnpm(tmpPrefix, tgzPath); + const found = findInstalledModule(tmpPrefix, id); + if (!found) throw new Error(`pnpm add 已解包,但 tarball 中未找到模块 ${id}`); + await copyDir(found, dstDir); + } finally { + try { + await fsp.rm(tmpPrefix, { recursive: true, force: true }); + } catch { + /* best-effort */ + } + } +} + +/** + * 在临时项目中安装包,只读取 registry/tarball 内容,不执行包的生命周期脚本。 + * @param {string} projectDir + * @param {string | fs.PathLike} packageSpec + */ +async function installWithPnpm(projectDir, packageSpec) { + await fsp.writeFile( + path.join(projectDir, "package.json"), + '{"name":"sfmc-module-install-tmp","version":"0.0.0","private":true}\n' + ); + // @ts-ignore - cross-spawn resolves pnpm.cmd safely on Windows. + const spawnChild = (await import("cross-spawn")).default; await new Promise((resolve, reject) => { - // @ts-ignore - const proc = spawnChild(npmCmd, ["install", "--prefix", tmpPrefix, "--omit=dev", "--no-save", tgzPath], { - stdio: ["ignore", "pipe", "pipe"], - }); + const proc = spawnChild( + "pnpm", + [ + "--dir", + projectDir, + "add", + "--ignore-workspace", + "--config.auto-install-peers=false", + "--prod", + "--ignore-scripts", + String(packageSpec), + ], + { stdio: ["ignore", "ignore", "pipe"] } + ); let stderr = ""; - // @ts-ignore - proc.stderr?.on("data", (d) => { - stderr += d.toString(); + proc.stderr?.on("data", (data) => { + stderr += data.toString(); }); - // @ts-ignore - proc.on("exit", (code) => - code === 0 ? resolve() : reject(new Error(`npm install tarball exit ${code}: ${stderr}`)) - ); - // @ts-ignore - proc.on("error", reject); + proc.on("close", (code, signal) => { + if (code === 0) return resolve(); + reject( + new Error( + code === null + ? `pnpm add terminated by ${signal || "unknown signal"}: ${stderr.trim()}` + : `pnpm add failed (${code}): ${stderr.trim()}` + ) + ); + }); + proc.on("error", (error) => reject(new Error(`无法启动 pnpm: ${error.message}`))); }); - /* npm install 把 package.json#name 当作目录名: - * @scope/module-foo → node_modules/@scope/module-foo - * @scope/sfmc-module-foo → node_modules/@scope/sfmc-module-foo - * 我们从 tmpPrefix/node_modules 找出唯一的一个包目录,整体拷贝到 dstDir。 - */ - const installed = path.join(tmpPrefix, "node_modules"); - if (!exists(installed)) { - throw new Error(`npm install tarball produced no node_modules: ${tmpPrefix}`); +} + +/** + * 从 pnpm 临时项目的 node_modules 中解析模块包真实目录。 + * @param {string} projectDir + * @param {string} id + * @param {string} [packageSpec] + */ +function findInstalledModule(projectDir, id, packageSpec) { + const nodeModules = path.join(projectDir, "node_modules"); + if (!exists(nodeModules)) return null; + /** @type {string[]} */ + const candidates = []; + if (packageSpec) { + const packageName = packageNameFromSpec(String(packageSpec)); + if (packageName) candidates.push(path.join(nodeModules, ...packageName.split("/"))); } - let found = null; - for (const e of fs.readdirSync(installed, { withFileTypes: true })) { - if (e.isDirectory()) { - if (e.name.startsWith("@")) { - for (const sub of fs.readdirSync(path.join(installed, e.name), { withFileTypes: true })) { - if (sub.isDirectory()) { - found = path.join(installed, e.name, sub.name); - break; - } - } - } else if (e.isDirectory()) { - found = path.join(installed, e.name); + for (const entry of fs.readdirSync(nodeModules, { withFileTypes: true })) { + if (entry.name === ".bin" || entry.name === ".pnpm" || entry.name.startsWith(".")) continue; + const entryPath = path.join(nodeModules, entry.name); + if (entry.name.startsWith("@")) { + for (const scoped of fs.readdirSync(entryPath, { withFileTypes: true })) { + candidates.push(path.join(entryPath, scoped.name)); } + } else { + candidates.push(entryPath); } - if (found) break; } - if (!found) { - throw new Error(`npm install tarball: no package directory under ${installed}`); + const seen = new Set(); + for (const candidate of candidates) { + let real; + try { + real = fs.realpathSync(candidate); + } catch { + continue; + } + if (seen.has(real)) continue; + seen.add(real); + try { + const manifestPath = path.join(real, "sapi", "manifest.json"); + const moduleId = exists(manifestPath) + ? String(JSON.parse(fs.readFileSync(manifestPath, "utf8")).id || "").replace(/^(feature|core)-/, "") + : ""; + const pkgName = String(JSON.parse(fs.readFileSync(path.join(real, "package.json"), "utf8")).name || ""); + if (moduleId === id || folderFromNpmPackageName(pkgName) === id) return real; + } catch { + /* ignore package entries without module metadata */ + } } - await copyDir(found, dstDir); - /* 清理临时 prefix */ - try { - await fsp.rm(tmpPrefix, { recursive: true, force: true }); - } catch { - /* best-effort */ + return null; +} + +/** @param {string} spec */ +function packageNameFromSpec(spec) { + if (spec.startsWith("@")) { + const slash = spec.indexOf("/"); + if (slash < 0) return null; + const versionSeparator = spec.indexOf("@", slash + 1); + return versionSeparator < 0 ? spec : spec.slice(0, versionSeparator); } + const versionSeparator = spec.indexOf("@"); + return versionSeparator < 0 ? spec : spec.slice(0, versionSeparator); } /** @@ -880,7 +932,7 @@ async function installOne(id, flags) { if (from.startsWith("local:") || from === "local") return fromLocal(id, from, perFlags); if (from.startsWith("tgz:")) return fromTgz(id, from, perFlags); if (from.startsWith("zip:")) return fromZip(id, from, perFlags); - if (from.startsWith("npm:")) return fromNpm(id, from.slice("npm:".length), perFlags); + if (from.startsWith("npm:")) return fromRegistry(id, from.slice("npm:".length)); if (from.startsWith("dir:")) { const base = from.slice("dir:".length); const candidate = path.join(base, id); diff --git a/packages/cli/src/i18n/locales/en.ts b/packages/cli/src/i18n/locales/en.ts index 81b5d228..907591d7 100644 --- a/packages/cli/src/i18n/locales/en.ts +++ b/packages/cli/src/i18n/locales/en.ts @@ -198,7 +198,7 @@ export const en = { "wizard.skipped": "Setup skipped", "wizard.step1": "Step 1 - Runtime Environment", "wizard.runtimeRoot": "Runtime root: {root}", - "wizard.npmMissing": "npm runtime is missing required directories: {list}", + "wizard.runtimeMissingAssets": "Runtime is missing required directories: {list}", "wizard.prepFailed": "Runtime preparation failed: {message}", "wizard.step2": "Step 2 - External Runtimes", "wizard.step2Note": "Select paths for BDS, Database, and QQ bridge backend", diff --git a/packages/cli/src/i18n/locales/zh-CN.ts b/packages/cli/src/i18n/locales/zh-CN.ts index c62de031..88443918 100644 --- a/packages/cli/src/i18n/locales/zh-CN.ts +++ b/packages/cli/src/i18n/locales/zh-CN.ts @@ -195,7 +195,7 @@ export const zhCN: Record = { "wizard.skipped": "已跳过初始化", "wizard.step1": "步骤 1 - 运行环境", "wizard.runtimeRoot": "运行根目录: {root}", - "wizard.npmMissing": "npm 运行时缺少必需目录: {list}", + "wizard.runtimeMissingAssets": "运行环境缺少必需目录: {list}", "wizard.prepFailed": "运行环境准备失败: {message}", "wizard.step2": "步骤 2 - 外部运行时", "wizard.step2Note": "选择 BDS、数据库与 QQ 桥接后端", diff --git a/packages/cli/src/module-commands.ts b/packages/cli/src/module-commands.ts index 46fb392f..83a24f71 100644 --- a/packages/cli/src/module-commands.ts +++ b/packages/cli/src/module-commands.ts @@ -644,7 +644,7 @@ export async function dispatchModuleCommand(sub: string | undefined, args: strin case "watch": case "publish": return c.yellow( - `[mod ${sub}] 已移至扩展「SFMC Module」全周期(Create/Test/Link/Watch/Publish);建仓请用 npm create @sfmc-bds/module@latest;运维请用 mod build|reload|install。` + `[mod ${sub}] 已移至扩展「SFMC Module」全周期(Create/Test/Link/Watch/Publish);建仓请用 pnpm dlx @sfmc-bds/create-module@latest;运维请用 mod build|reload|install。` ); default: return c.yellow(moduleUsage()); @@ -669,4 +669,4 @@ export function listInstalledModuleIdsSync(): string[] { } catch { return []; } -} \ No newline at end of file +} diff --git a/packages/cli/src/module-pack-build.ts b/packages/cli/src/module-pack-build.ts index 611f5ccd..1789a2c7 100644 --- a/packages/cli/src/module-pack-build.ts +++ b/packages/cli/src/module-pack-build.ts @@ -7,7 +7,7 @@ * 实际文件写入(assemble-bp / assemble-rp / deploy / enable-pack / disable-pack / * ensure-permission)通过 spawn bds-tools/cli-pack-manager.ts 的 verb 执行; * 与 module install/uninstall(→ scripts/module-install/fetch-module.mjs)保持同一架构: - * CLI 是 thin wrapper;建仓脚手架在 @sfmc-bds/create-module(npm create)。 + * CLI 是 thin wrapper;建仓脚手架在 @sfmc-bds/create-module(pnpm dlx)。 * * 唯一权威:本文件 = `sfmc mod build|reload` 的派发入口;其他文件不得再 * 直接 import pack-lifecycle 的 cmdPackBuild / deployPacks(确保只有一个 spawn 入口)。 diff --git a/packages/cli/src/wizard.ts b/packages/cli/src/wizard.ts index 39730c10..c0acff4b 100644 --- a/packages/cli/src/wizard.ts +++ b/packages/cli/src/wizard.ts @@ -74,11 +74,11 @@ async function runBdsUpdate(rootDir: string, channel: string): Promise<{ code: n } async function prepareRuntimeAssets(rootDir: string): Promise { - /* monorepo 布局:configs/ modules/ 应由 npm 工作区已提供 */ + /* monorepo 布局:configs/ modules/ 应由 pnpm workspace 提供 */ if (isMonorepoLayout(rootDir)) { const missing = missingRuntimeAssets(rootDir); if (missing.length > 0) { - throw new Error(t("wizard.npmMissing", { list: missing.join(", ") })); + throw new Error(t("wizard.runtimeMissingAssets", { list: missing.join(", ") })); } return; } diff --git a/packages/create-module/README.md b/packages/create-module/README.md index b07d522d..374a257e 100644 --- a/packages/create-module/README.md +++ b/packages/create-module/README.md @@ -3,8 +3,7 @@ 创建 SFMC SAPI 模块骨架。业界入口: ```bash -npm create @sfmc-bds/module@latest -# 等价:npx @sfmc-bds/create-module@latest +pnpm dlx @sfmc-bds/create-module@latest ``` 程序化 API(VS Code/Cursor 扩展使用同一函数): diff --git a/packages/create-module/package.json b/packages/create-module/package.json index fbe72d4d..1c32a49f 100644 --- a/packages/create-module/package.json +++ b/packages/create-module/package.json @@ -1,7 +1,12 @@ { "name": "@sfmc-bds/create-module", "version": "0.1.0-beta.4", - "description": "Create an SFMC SAPI module — npm create @sfmc-bds/module", + "description": "Create an SFMC SAPI module with pnpm", + "repository": { + "type": "git", + "url": "git+https://github.com/DogeLakeDev/ScriptsForMinecraftServer.git", + "directory": "packages/create-module" + }, "type": "module", "license": "AGPL-3.0-only", "engines": { diff --git a/packages/create-module/src/cli.ts b/packages/create-module/src/cli.ts index a4a9b2bf..7644dded 100644 --- a/packages/create-module/src/cli.ts +++ b/packages/create-module/src/cli.ts @@ -1,6 +1,6 @@ #!/usr/bin/env node /** - * npm create @sfmc-bds/module → npx @sfmc-bds/create-module + * pnpm dlx @sfmc-bds/create-module */ import * as p from "@clack/prompts"; @@ -54,13 +54,12 @@ function parseArgs(argv: string[]) { function printHelp(): void { console.log(`用法: - npm create @sfmc-bds/module@latest - npx @sfmc-bds/create-module@latest [id] [dir] + pnpm dlx @sfmc-bds/create-module@latest [id] [dir] 选项: --id 模块短 id(kebab-case) --name 显示名 - --scope 社区 npm scope(不含 @) + --scope 社区包 scope(不含 @) --official 官方包 @sfmc-bds/module- --extra db 附加 db 权限占位 --out 输出目录(默认 ./) @@ -123,7 +122,7 @@ async function main(): Promise { if (!official && !scope) { const kind = await p.select({ - message: "npm 包范围", + message: "包范围", options: [ { value: "community", label: "社区", hint: "@/sfmc-module-" }, { value: "official", label: "官方", hint: "@sfmc-bds/module-" }, @@ -136,7 +135,7 @@ async function main(): Promise { if (kind === "official") official = true; else { const s = await p.text({ - message: "npm scope(不含 @)", + message: "scope(不含 @)", placeholder: "alice", validate: (x) => (!x?.trim() ? "必填" : undefined), }); @@ -211,8 +210,8 @@ async function main(): Promise { p.note( [ `cd ${path.relative(process.cwd(), result.targetDir) || "."}`, - "pnpm install # 或 npm install", - "pnpm run typecheck # 或 npm run typecheck", + "pnpm install", + "pnpm run typecheck", "", "联调:扩展 SFMC: Link to SFMC Root,或:", `sfmc mod install ${result.id} --from dir:${result.targetDir} --link`, diff --git a/packages/create-module/templates/base/.prettierignore b/packages/create-module/templates/base/.prettierignore index b7f08fec..fe733caf 100644 --- a/packages/create-module/templates/base/.prettierignore +++ b/packages/create-module/templates/base/.prettierignore @@ -1,4 +1,4 @@ node_modules/ dist/ *.tgz -package-lock.json +pnpm-lock.yaml diff --git a/packages/create-module/templates/base/README.md b/packages/create-module/templates/base/README.md index 8b8678f4..563526cf 100644 --- a/packages/create-module/templates/base/README.md +++ b/packages/create-module/templates/base/README.md @@ -9,9 +9,9 @@ SAPI 模块 `{{id}}`。 ## 开发 ```bash -pnpm install # 或 npm install -pnpm run typecheck # 或 npm run typecheck -pnpm run lint # 或 npm run lint +pnpm install +pnpm run typecheck +pnpm run lint ``` ## 联调 @@ -23,5 +23,5 @@ pnpm run lint # 或 npm run lint | 命令 | 作用 | | ---------------------------------- | ------------------------------ | -| `pnpm run build` / `npm run build` | tsc --noEmit | -| `pnpm run lint` / `npm run lint` | ESLint / Prettier | +| `pnpm run build` | tsc --noEmit | +| `pnpm run lint` | ESLint / Prettier | diff --git a/packages/create-module/templates/base/package.json b/packages/create-module/templates/base/package.json index ac2d0a9b..a5f0c7c3 100644 --- a/packages/create-module/templates/base/package.json +++ b/packages/create-module/templates/base/package.json @@ -1,8 +1,10 @@ { "name": "{{pkgName}}", "version": "0.1.0", + "packageManager": "pnpm@10.8.0", "type": "module", "description": "SAPI module: {{id}}", + "license": "ISC", "main": "sapi/src/index.ts", "exports": { ".": "./sapi/src/index.ts" @@ -14,7 +16,7 @@ "LICENSE" ], "scripts": { - "build": "npm run typecheck", + "build": "pnpm run typecheck", "typecheck": "tsc --noEmit -p sapi/tsconfig.json", "lint": "eslint \"sapi/**/*.ts\"", "format": "prettier --write ." @@ -37,6 +39,10 @@ "peerDependencies": { "@sfmc-bds/sdk": ">=0.2.0" }, + "publishConfig": { + "access": "public", + "registry": "https://registry.npmjs.org/" + }, "engines": { "node": ">=22.13.0" } diff --git a/packages/create-module/templates/base/pnpm-workspace.yaml b/packages/create-module/templates/base/pnpm-workspace.yaml new file mode 100644 index 00000000..ab0fb77c --- /dev/null +++ b/packages/create-module/templates/base/pnpm-workspace.yaml @@ -0,0 +1,5 @@ +allowBuilds: + esbuild: true + +onlyBuiltDependencies: + - esbuild diff --git a/packages/db-server/README.md b/packages/db-server/README.md index 8473b5e4..c20bbe8b 100644 --- a/packages/db-server/README.md +++ b/packages/db-server/README.md @@ -5,7 +5,7 @@ SFMC SQLite HTTP REST 后端:配置快照、模块启停、游戏数据路由 ## 安装 ```bash -npm install @sfmc-bds/db-server +pnpm add @sfmc-bds/db-server node node_modules/@sfmc-bds/db-server/dist/index.js ``` diff --git a/packages/devkit/README.md b/packages/devkit/README.md index c081de22..ac3d6e44 100644 --- a/packages/devkit/README.md +++ b/packages/devkit/README.md @@ -5,7 +5,7 @@ 建仓请用: ```bash -npm create @sfmc-bds/module@latest +pnpm dlx @sfmc-bds/create-module@latest ``` ```ts diff --git a/packages/devkit/package.json b/packages/devkit/package.json index c4a2500b..d5229d58 100644 --- a/packages/devkit/package.json +++ b/packages/devkit/package.json @@ -2,6 +2,11 @@ "name": "@sfmc-bds/devkit", "version": "1.0.0-beta.12", "description": "SFMC module authoring toolkit — watch, rebuild, enable/disable (for VS Code extension & tooling)", + "repository": { + "type": "git", + "url": "git+https://github.com/DogeLakeDev/ScriptsForMinecraftServer.git", + "directory": "packages/devkit" + }, "type": "module", "license": "AGPL-3.0-only", "engines": { diff --git a/packages/devkit/src/index.ts b/packages/devkit/src/index.ts index 803a2d81..13a90e14 100644 --- a/packages/devkit/src/index.ts +++ b/packages/devkit/src/index.ts @@ -1,6 +1,6 @@ /** * @sfmc-bds/devkit — 模块作者工具核心(扩展 / CI 共用) - * Watch / rebuild / 启停;建仓请用 @sfmc-bds/create-module(npm create @sfmc-bds/module)。 + * Watch / rebuild / 启停;建仓请用 pnpm dlx @sfmc-bds/create-module。 */ export { resolveLocalModuleRoot } from "./paths.js"; diff --git a/packages/meta/README.md b/packages/meta/README.md index b6ec4975..48f12c2f 100644 --- a/packages/meta/README.md +++ b/packages/meta/README.md @@ -1,21 +1,21 @@ # @sfmc-bds/sfmc SFMC 聚合包: -✨ 一次性安装 CLI + db-server + qq-bridge + bds-tools + tools + sdk,装完即可在工作目录里初始化并管理全部服务。 +✨ 一次性安装 CLI + db-server + qq-bridge + bds-tools + sdk,装完即可在工作目录里初始化并管理全部服务。 ## 安装 ```bash -npm install -g @sfmc-bds/sfmc@beta +pnpm add -g @sfmc-bds/sfmc@beta ``` 或在项目目录本地安装: ```bash mkdir my-server && cd my-server -npm init -y -npm install @sfmc-bds/sfmc@beta -npx sfmc +pnpm init +pnpm add @sfmc-bds/sfmc@beta +pnpm exec sfmc ``` > 当前仅开放 **beta** 通道。稳定 `latest` 未开放前请始终带 `@beta`。 diff --git a/packages/qq-bridge/README.md b/packages/qq-bridge/README.md index 9e44a473..3209255d 100644 --- a/packages/qq-bridge/README.md +++ b/packages/qq-bridge/README.md @@ -14,7 +14,7 @@ QQ 侧指令(`菜单` / `ping` / `whoami`)在桥内拦截,不依赖 MC;o ## 安装 ```bash -npm install @sfmc-bds/qq-bridge +pnpm add @sfmc-bds/qq-bridge node node_modules/@sfmc-bds/qq-bridge/dist/index.js ``` diff --git a/packages/tools/README.md b/packages/tools/README.md index 2fd4dd90..38eb4283 100644 --- a/packages/tools/README.md +++ b/packages/tools/README.md @@ -5,21 +5,16 @@ | 对外能力 | 所在包 | |----------|--------| | `sfmc mod install` / fetch-module | `@sfmc-bds/cli` | -| 模块脚手架 `npm create @sfmc-bds/module` | `@sfmc-bds/create-module` | +| 模块脚手架 `pnpm dlx @sfmc-bds/create-module` | `@sfmc-bds/create-module` | ## 仓内常用 ```bash pnpm run verify -npm run verify pnpm exec sfmc-esbuild-transpile -npx sfmc-esbuild-transpile pnpm exec tsc7 --noEmit -npx tsc7 --noEmit -pnpm run docs -- serve -npm run docs -- serve -pnpm run docs -- build -npm run docs -- build +pnpm run docs serve +pnpm run docs build ``` 发版走 Changesets:**push `main` → Version Packages PR → 合并后 CI 自动 `ci-release-packages`**。本地一般不手动发。 diff --git a/packages/tools/changeset-assert-mode.mjs b/packages/tools/changeset-assert-mode.mjs index 28cbf7f1..7f3e5f80 100644 --- a/packages/tools/changeset-assert-mode.mjs +++ b/packages/tools/changeset-assert-mode.mjs @@ -23,8 +23,8 @@ const state = readPreState(); if (mode === "pre" && !pre) { console.error( - "[changeset] 当前不在 pre mode。正式通道请先 `pnpm exec changeset pre exit` / `npx changeset pre exit`;" + - "日常发版走 CI(push main → Version PR)。若要重新进入 beta: `pnpm exec changeset pre enter beta` / `npx changeset pre enter beta`" + "[changeset] 当前不在 pre mode。正式通道请先 `pnpm exec changeset pre exit`;" + + "日常发版走 CI(push main → Version PR)。若要重新进入 beta: `pnpm exec changeset pre enter beta`" ); process.exit(1); } @@ -32,7 +32,7 @@ if (mode === "pre" && !pre) { if (mode === "stable" && pre) { console.error( `[changeset] 仓库仍处于 pre mode (tag=${state?.tag ?? "beta"})。` + - `达标后执行 pnpm exec changeset pre exit / npx changeset pre exit;日常发版走 CI Version PR。` + `达标后执行 pnpm exec changeset pre exit;日常发版走 CI Version PR。` ); process.exit(1); } diff --git a/packages/tools/changeset-ensure.mjs b/packages/tools/changeset-ensure.mjs index 439c0dec..8cba1fcd 100644 --- a/packages/tools/changeset-ensure.mjs +++ b/packages/tools/changeset-ensure.mjs @@ -15,7 +15,7 @@ if (pending.length > 0) { } console.log("[changeset] 无待消费 changeset,启动交互式 changeset …"); -run("npx", ["changeset"]); +run("pnpm", ["exec", "changeset"]); const after = listPendingChangesetFiles(); if (after.length === 0) { diff --git a/packages/tools/changeset-publish.mjs b/packages/tools/changeset-publish.mjs index 92d41ff2..31e202e5 100644 --- a/packages/tools/changeset-publish.mjs +++ b/packages/tools/changeset-publish.mjs @@ -5,8 +5,8 @@ * - 跳过 lifecycle scripts(CI 已先 build;避免多包并行 prepublishOnly 抢 tsc7) * - pre mode 下由 changesets 自动使用 pre.json 的 tag(勿再传 --tag,会报错) */ -import { spawnSync } from "node:child_process"; import { ROOT, isPreMode, readPreState } from "./changeset-release-lib.mjs"; +import { spawnPnpmSync } from "./lib/proc.mjs"; const pre = readPreState(); const preTag = isPreMode() && pre?.tag ? String(pre.tag) : null; @@ -17,15 +17,15 @@ const env = { npm_config_ignore_scripts: "true", }; -const args = ["changeset", "publish"]; +const args = ["exec", "changeset", "publish"]; /* 仅非 pre 时允许显式 --tag;pre mode 会自行打到 pre.json#tag */ if (!preTag && process.env.SFMC_PUBLISH_TAG) { args.push("--tag", process.env.SFMC_PUBLISH_TAG); } console.log( - `[changeset-publish] running: npx ${args.join(" ")}` + + `[changeset-publish] running: pnpm ${args.join(" ")}` + (preTag ? ` (pre mode → npm tag ${preTag})` : "") ); -const r = spawnSync("npx", args, { cwd: ROOT, env, stdio: "inherit", shell: true }); +const r = spawnPnpmSync(args, { cwd: ROOT, env }); process.exit(r.status === null ? 1 : r.status); diff --git a/packages/tools/lib/npm-publish-packages.mjs b/packages/tools/lib/npm-publish-packages.mjs index f7cc8218..65f1b652 100644 --- a/packages/tools/lib/npm-publish-packages.mjs +++ b/packages/tools/lib/npm-publish-packages.mjs @@ -15,6 +15,7 @@ export const NPM_PUBLISH_PACKAGES = { "@sfmc-bds/db-server": "packages/db-server/package.json", "@sfmc-bds/qq-bridge": "packages/qq-bridge/package.json", "@sfmc-bds/bds-tools": "packages/bds-tools/package.json", + "@sfmc-bds/create-module": "packages/create-module/package.json", "@sfmc-bds/devkit": "packages/devkit/package.json", "@sfmc-bds/sfmc": "packages/meta/package.json", }; diff --git a/packages/tools/lib/proc.mjs b/packages/tools/lib/proc.mjs index ec303b8e..7fdcd7d1 100644 --- a/packages/tools/lib/proc.mjs +++ b/packages/tools/lib/proc.mjs @@ -32,21 +32,6 @@ function quote(s) { return /\s/.test(s) ? `"${s}"` : s; } -/** - * 调用 npm CLI。Windows 上 npm 为 `.cmd`,`spawnSync("npm", …)` 无 shell 会 ENOENT; - * 需 `shell: true`(参数由本仓固定传入,非用户拼接)。 - * @param {string[]} args npm 参数(不含 npm 自身) - * @param {import("node:child_process").SpawnSyncOptions} [opts] - * @returns {import("node:child_process").SpawnSyncReturns} - */ -export function spawnNpmSync(args, opts = {}) { - return spawnSync("npm", args, { - stdio: "inherit", - shell: process.platform === "win32", - ...opts, - }); -} - /** * 调用 pnpm CLI。Windows 上 pnpm 为 `.cmd`,需 `shell: true`。 * @param {string[]} args pnpm 参数(不含 pnpm 自身) diff --git a/packages/tools/lib/verify/db-api.mjs b/packages/tools/lib/verify/db-api.mjs index 03d29d10..7c1bc6b3 100644 --- a/packages/tools/lib/verify/db-api.mjs +++ b/packages/tools/lib/verify/db-api.mjs @@ -8,7 +8,6 @@ const MODULE_FIELDS = [ "id", "name", "config_key", - "type", "description", "default_enabled", "can_disable", diff --git a/packages/tools/lib/verify/db-harness.mjs b/packages/tools/lib/verify/db-harness.mjs index 92b7ab15..8c6bcd96 100644 --- a/packages/tools/lib/verify/db-harness.mjs +++ b/packages/tools/lib/verify/db-harness.mjs @@ -3,7 +3,10 @@ * 拉起 db-server,等 /api/health,执行回调后清理 */ import { spawn } from "node:child_process"; +import { createServer } from "node:net"; +import fs from "node:fs"; import process from "node:process"; +import path from "node:path"; import { waitHealth } from "../http.mjs"; import { killProc } from "../proc.mjs"; import { DB_SERVER_DIST, ROOT } from "../paths.mjs"; @@ -23,16 +26,20 @@ import { exists } from "../io.mjs"; * @param {(port: number) => Promise} fn */ export async function withDbServer(opts, fn) { - const dataRoot = opts.dataRoot ?? ROOT; - const port = opts.port ?? parseInt(process.env.DB_PORT || "3001", 10); + const tempRoot = path.join(ROOT, "tmp"); + fs.mkdirSync(tempRoot, { recursive: true }); + const dataRoot = fs.mkdtempSync(path.join(tempRoot, "sfmc-db-verify-")); + const port = opts.port ?? (await findFreePort()); const dbDist = opts.dbDist ?? DB_SERVER_DIST; const cwd = opts.cwd ?? ROOT; const healthTimeoutMs = opts.healthTimeoutMs ?? 20_000; if (!exists(dbDist)) { - throw new Error(`缺少 ${dbDist} — 先 npm run build`); + fs.rmSync(dataRoot, { recursive: true, force: true }); + throw new Error(`缺少 ${dbDist} — 先 pnpm run build`); } + writeVerifyRoot(dataRoot, port); /** @type {import("node:child_process").ChildProcess | null} */ let dbProc = null; try { @@ -42,10 +49,61 @@ export async function withDbServer(opts, fn) { stdio: ["ignore", "pipe", "pipe"], }); const ready = await waitHealth(port, healthTimeoutMs); - if (!ready) throw new Error(`db-server 未就绪 (port=${port})`); + if (!ready || dbProc.exitCode !== null) throw new Error(`db-server 未就绪 (port=${port})`); await fn(port); } finally { if (dbProc) await killProc(dbProc.pid); await new Promise((r) => setTimeout(r, 600)); + fs.rmSync(dataRoot, { recursive: true, force: true }); } } + +function findFreePort() { + return new Promise((resolve, reject) => { + const server = createServer(); + server.once("error", reject); + server.listen(0, "127.0.0.1", () => { + const address = server.address(); + if (!address || typeof address === "string") { + server.close(() => reject(new Error("无法分配 db-server 自检端口"))); + return; + } + server.close((error) => (error ? reject(error) : resolve(address.port))); + }); + }); +} + +function writeVerifyRoot(root, port) { + fs.mkdirSync(path.join(root, "configs"), { recursive: true }); + fs.mkdirSync(path.join(root, "data"), { recursive: true }); + fs.mkdirSync(path.join(root, "modules", "packages"), { recursive: true }); + fs.writeFileSync( + path.join(root, "configs", "db_config.json"), + `${JSON.stringify({ db_port: port, dbDir: "data/sfmc_data.db", modulesDir: "modules" }, null, 2)}\n` + ); + fs.writeFileSync(path.join(root, "configs", "qq_config.json"), "{}\n"); + fs.writeFileSync(path.join(root, "configs", "permissions.json"), "[]\n"); + fs.writeFileSync( + path.join(root, "modules", "catalog.json"), + `${JSON.stringify( + { + version: 1, + modules: [ + { + id: "verify-fixture", + configKey: "verify_fixture", + name: "Verify fixture", + description: "Temporary read-only API contract fixture", + enabledByDefault: true, + canDisable: false, + requires: [], + entry: { kind: "sapi", path: "modules/packages/verify-fixture/sapi/src/index.ts" }, + }, + ], + }, + null, + 2 + )}\n` + ); + fs.writeFileSync(path.join(root, "modules", "module-lock.json"), '{"version":1,"modules":{}}\n'); +} diff --git a/packages/tools/lib/verify/offline.mjs b/packages/tools/lib/verify/offline.mjs index aeda7809..8b164593 100644 --- a/packages/tools/lib/verify/offline.mjs +++ b/packages/tools/lib/verify/offline.mjs @@ -86,7 +86,7 @@ export function runOfflinePhase(reporter) { { const sdkDist = path.join(ROOT, "modules", "sdk", "@sfmc-sdk", "dist"); if (exists(sdkDist)) reporter.pass("@sfmc-bds/sdk dist 存在"); - else reporter.warn("@sfmc-bds/sdk dist 缺失 — 运行 pnpm --filter @sfmc-bds/sdk run build / npm run build -w @sfmc-bds/sdk"); + else reporter.warn("@sfmc-bds/sdk dist 缺失 — 运行 pnpm --filter @sfmc-bds/sdk run build"); } { diff --git a/packages/tools/pack-verify.mjs b/packages/tools/pack-verify.mjs index 44c4d0c0..8b497aec 100644 --- a/packages/tools/pack-verify.mjs +++ b/packages/tools/pack-verify.mjs @@ -9,21 +9,29 @@ import { assertPublishPackageInWorkspaces, } from "./lib/npm-publish-packages.mjs"; import { ROOT } from "./lib/paths.mjs"; -import { spawnNpmSync } from "./lib/proc.mjs"; +import { spawnPnpmSync } from "./lib/proc.mjs"; +import fs from "node:fs"; +import os from "node:os"; +import path from "node:path"; /** @param {string[]} args */ -function runNpm(args) { - const r = spawnNpmSync(args, { cwd: ROOT }); +function runPnpm(args, cwd = ROOT) { + const r = spawnPnpmSync(args, { cwd }); if (r.error) throw r.error; if (r.status !== 0) process.exit(r.status ?? 1); } -runNpm(["run", "build", "--workspaces", "--if-present"]); +runPnpm(["run", "build"]); -for (const name of Object.keys(NPM_PUBLISH_PACKAGES)) { - assertPublishPackageInWorkspaces(name, ROOT); - console.log(`\n[pack:verify] npm pack -w ${name}`); - runNpm(["pack", "-w", name]); +const packDir = fs.mkdtempSync(path.join(os.tmpdir(), "sfmc-pack-verify-")); +try { + for (const [name, rel] of Object.entries(NPM_PUBLISH_PACKAGES)) { + assertPublishPackageInWorkspaces(name, ROOT); + console.log(`\n[pack:verify] pnpm pack ${name}`); + runPnpm(["pack", "--pack-destination", packDir], path.dirname(path.join(ROOT, rel))); + } +} finally { + fs.rmSync(packDir, { recursive: true, force: true }); } console.log(`\n[pack:verify] ok — ${Object.keys(NPM_PUBLISH_PACKAGES).length} packages`); diff --git a/packages/tools/run-release.mjs b/packages/tools/run-release.mjs index 380a2e42..acba40eb 100644 --- a/packages/tools/run-release.mjs +++ b/packages/tools/run-release.mjs @@ -10,7 +10,7 @@ import { spawnSync } from "node:child_process"; import path from "node:path"; import { ROOT, TOOLS_PKG_DIR } from "./lib/paths.mjs"; -import { spawnNpmSync } from "./lib/proc.mjs"; +import { spawnPnpmSync } from "./lib/proc.mjs"; const TOOLS = TOOLS_PKG_DIR; @@ -46,10 +46,10 @@ function runNode(rel, args = []) { if (r.status !== 0) process.exit(r.status ?? 1); } -/** @param {string[]} args npm 参数(不含 npm 自身) */ -function runNpm(args) { - console.log(`\n[run-release] npm ${args.join(" ")}`); - const r = spawnNpmSync(args, { cwd: ROOT, env: process.env }); +/** @param {string[]} args pnpm 参数(不含 pnpm 自身) */ +function runPnpm(args) { + console.log(`\n[run-release] pnpm ${args.join(" ")}`); + const r = spawnPnpmSync(args, { cwd: ROOT, env: process.env }); if (r.error) throw r.error; if (r.status !== 0) process.exit(r.status ?? 1); } @@ -71,7 +71,7 @@ if (mode === "pre") { } runNode("changeset-ensure.mjs"); -runNpm(["run", "version-packages"]); +runPnpm(["run", "version-packages"]); runNode("changeset-commit-version.mjs"); runNode("changeset-tag.mjs"); runNode("changeset-push.mjs"); diff --git a/packages/tools/tsc7.mjs b/packages/tools/tsc7.mjs index 375b1900..459eaf45 100755 --- a/packages/tools/tsc7.mjs +++ b/packages/tools/tsc7.mjs @@ -33,7 +33,7 @@ export function resolveNativeTsc() { try { pkgJson = require.resolve("@typescript/native/package.json"); } catch { - throw new Error("未找到 @typescript/native(TypeScript 7)。请在仓库根目录执行 npm install。"); + throw new Error("未找到 @typescript/native(TypeScript 7)。请在仓库根目录执行 pnpm install。"); } return path.join(path.dirname(pkgJson), "bin", "tsc"); } diff --git a/packages/tools/verify.mjs b/packages/tools/verify.mjs index 3f9ab9cc..6c37265e 100644 --- a/packages/tools/verify.mjs +++ b/packages/tools/verify.mjs @@ -60,7 +60,7 @@ export async function runVerify(argv = process.argv.slice(2)) { if (reporter.ok) { try { - await withDbServer({ dataRoot: ROOT }, async (port) => { + await withDbServer({}, async (port) => { const platform = await assertPlatformDbApi(port); reporter.pass(`db-server 平台 API (modules=${platform.moduleCount})`); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3485dbd1..6cc355ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -173,6 +173,9 @@ importers: chalk: specifier: ^6.0.0 version: 6.0.0 + cross-spawn: + specifier: 7.0.6 + version: 7.0.6 esbuild: specifier: ^0.28.1 version: 0.28.2 diff --git a/website/AGENTS.md b/website/AGENTS.md index 73345e86..3078b2d5 100644 --- a/website/AGENTS.md +++ b/website/AGENTS.md @@ -3,7 +3,7 @@ - 本目录规则继承仓库根 `AGENTS.md`。 - 中文内容放在 `docs/zh/**`;仅在任务明确要求时同步 `docs/en/**`。 - 站点配置位于 `website/rspress.config.ts`,组件和插件分别位于 `website/components/`、`website/plugins/`。 -- 不手工编辑 TypeDoc 生成内容;使用根目录 `pnpm run docs -- api` 重新生成。 -- 文档或站点变更优先运行 `pnpm run docs -- build`;局部开发可运行 `pnpm run docs -- serve`。 +- 不手工编辑 TypeDoc 生成内容;使用根目录 `pnpm run docs api` 重新生成。 +- 文档或站点变更优先运行 `pnpm run docs build`;局部开发可运行 `pnpm run docs serve`。 - `doc_build/` 是生成目录,不提交构建产物。 - 路由排除项以 `website/rspress.config.ts` 当前配置为准,不在本文件重复维护。 diff --git a/website/data/troubleshoot-tree.en.json b/website/data/troubleshoot-tree.en.json index 562cd3a2..23270a58 100644 --- a/website/data/troubleshoot-tree.en.json +++ b/website/data/troubleshoot-tree.en.json @@ -7,7 +7,7 @@ "steps": [ { "text": "Run platform integration verify at repo root to validate packages and dependencies:", - "code": "pnpm run verify\nnpm run verify" + "code": "pnpm run verify" } ] }, @@ -87,7 +87,7 @@ "steps": [ { "text": "Build monorepo first, then pack modules:", - "code": "pnpm run build\nnpm run build --workspaces --if-present\nsfmc> mod build" + "code": "pnpm run build\nsfmc> mod build" }, { "text": "Legacy modules importing \"@sfmc/sdk\" → reinstall using the latest CLI, or verify SDK package resolution" diff --git a/website/data/troubleshoot-tree.zh.json b/website/data/troubleshoot-tree.zh.json index f7c82d98..0a031d11 100644 --- a/website/data/troubleshoot-tree.zh.json +++ b/website/data/troubleshoot-tree.zh.json @@ -7,7 +7,7 @@ "steps": [ { "text": "在仓根运行平台集成自检,验证核心包与依赖一致性:", - "code": "pnpm run verify\nnpm run verify" + "code": "pnpm run verify" } ] }, @@ -87,7 +87,7 @@ "steps": [ { "text": "先构建 monorepo,再组装模块包:", - "code": "pnpm run build\nnpm run build --workspaces --if-present\nsfmc> mod build" + "code": "pnpm run build\nsfmc> mod build" }, { "text": "旧模块若仍 import \"@sfmc/sdk\",请使用当前 CLI 重新安装,或确认 SDK 能够正确解析"