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 能够正确解析"