# 发布与版本管理 > 本文回答两件事:**① 怎么把插件传上 GitHub;② 加了新功能之后,版本号怎么更新、怎么发出去。** --- ## 0. 发布前环境检查 发布前先确认本机工具链齐备(下表是通用要求,按你的实际环境逐项自查): | 项 | 要求 | 自查命令 | |---|---|---| | `git` | 已安装 | `git --version` | | Git 身份 | 已配置(会写进每个提交) | `git config user.name` / `git config user.email` | | 到 GitHub 的认证 | SSH 密钥已绑账号,**或** HTTPS + PAT | `ssh -T git@github.com` | | `package-lock.json` | **真实可用的 lock**(CI 用 `npm ci`) | 确认文件内含 `integrity` 字段 | | npm 账号 | 对本包作用域有发布权 | `npm whoami` | > ⚠️ **`package-lock.json` 必须是真 lock,不能是占位文件**——CI 的 `npm ci` 依赖它, > 占位文件会让流水线在推送后立刻变红。 ### 0.1 发布前自检:私有词表(可选,但强烈建议) 本仓库是**公开**的,因此**不得出现任何内部信息**——组织名、人名、内部工作区路径、内部代号等。 `npm run verify` 内置了这项扫描,但需要你从**仓库外**挂一份词表: ```sh DSH_PRIVATE_TERMS="<仓库外的词表绝对路径>" npm run verify ``` 词表格式:**一行一个词条**,`#` 开头为注释行,空行忽略。 > **为什么词表不放进仓库**:`package.json` 的 `files` 白名单包含 `scripts/`, > 词表若放在 `scripts/` 下会被**原样打进发布包**——那就成了「防泄漏的机制自己泄漏」。 > 因此改为环境变量挂入;**未设置时该项自动跳过**(CI 不设置,故 CI 永远不接触这些词)。 --- ## 一、首次上传到 GitHub(一次性) > **前置提醒**:若本插件目录**嵌套在另一个 git 仓库内部**,直接在插件目录 `git init` 会让外层仓库 > 把它记成 gitlink(`160000`)——一个**没有 `.gitmodules` 的坏子模块引用**,克隆外层仓库的人会拿到空目录。 > 处理方式:在外层仓库的 `.gitignore` 里加一行本插件目录的相对路径。 > 若插件目录是独立目录(不嵌套在任何仓库内),**可跳过本条**。 ### Step 1 · 在 GitHub 建空仓库 用网页建(当前没有 `gh` CLI): | 字段 | 填 | |---|---| | Owner | `yiyunet` | | Repository name | `dsh-dingtalk-connector` | | Visibility | Public | | **Initialize this repository with:** | **全部不勾** ⚠️ | > ⚠️ **不要勾 "Add a README file" / ".gitignore" / "license"**。 > 本地已经有这些文件,远端若先生成,首次 `git push` 会因历史不一致被拒,还得 `--allow-unrelated-histories` 手动合并。 建完后地址应为:`https://github.com/yiyunet/dsh-dingtalk-connector` (与 `package.json` 里已声明的 `repository` / `homepage` / `bugs` **完全一致**,届时三个 404 自动转正。) ### Step 1.5 · 填 About 区(仓库首页右上角 ⚙️ → Edit repository details) 推送**之前或之后**都可以填。下面是可直接粘贴的文案(与 `package.json` 的 `description` / `keywords` 呼应): **Description**(GitHub 上限 350 字符): ``` 把钉钉 AI 表格接入 DeepSeek Harness | 10 个工具 + 设置面板 + 定时导出 | Read & write DingTalk AI Tables from DeepSeek Harness ``` **Topics**(10 个;⚠️ GitHub topics 只接受**小写字母/数字/连字符**,所以 `多维表` 这类中文关键词**不能**作为 topic): ``` deepseek-harness dsh-plugin dingtalk dingtalk-aitable dws ai-table csv-export scheduled-export mcp agent-tools ``` **Website**(Homepage):留空即可(本插件没有独立站点)。若要填,填 npm 包页: `https://www.npmjs.com/package/@yiyunet/dsh-dingtalk-connector`(需先发布 npm)。 > **`license` 字段不用手动填**:GitHub 会在推送后自动识别仓库根目录的 `LICENSE` 文件(本项目为 MIT), > 并显示在 About 区。**空仓库时它必然显示为空**,这不是配置错误。 ### Step 1.6 · 顺手调一下仓库开关(可选) | 开关 | 默认 | 建议 | 理由 | |---|---|---|---| | Issues | ✅ 开 | **保持开** | README「联系方式」指向 Issues,关了链接就死了 | | Wiki | ✅ 开 | **可关** | 文档都在 `docs/` 里,Wiki 会造成第二处真相 | | Projects | ✅ 开 | **可关** | 单人项目用不上,减少界面噪音 | | Discussions | ❌ 关 | 视需要 | 有外部使用者后再开 | | Pages | ❌ 关 | **保持关** | 无静态站点需求 | --- ### Step 2 · 本地初始化并首次提交 ```sh cd <本仓库所在的目录> git init -b main ``` **★ 提交前必做一次目视核对**——这一步防的是"凭据或导出数据被带进历史"(一旦入仓,回滚也留着): ```sh git add -A git status --short ``` 逐条确认下面这些**不应该出现**: | 绝不能出现 | 为什么 | |---|---| | `lib/` | 构建产物,`.gitignore` 已排除(且 `prepare` 在克隆后 `npm install` 时会自动重建) | | `node_modules/` | 依赖,`.gitignore` 已排除 | | `*.csv` / `dingtalk-export/` | **实际业务数据**,`.gitignore` 已排除 | | `.env` / `.env.*` / `cordis.patch.local.yml` | **凭据**,`.gitignore` 已排除 | | 任何 `CLIENT_SECRET` 明文 | 红线 | 预期**应该**出现的关键文件(自检): ```sh git status --short | grep -E "docs/|assets/|scripts/|plugin-src/|README|CHANGELOG" ``` 确认无误后提交: ```sh git commit -m "feat: 首次提交 —— 钉钉 AI 表格连接器 v1.0.0" ``` > **为什么 `lib/` 不入库是对的**:`package.json` 的 `main` 指向 `lib/index.mjs`, > 而 `prepare` 钩子会在 `npm install` 时自动构建。详见 README「九、开发」。 --- ### Step 3 · 关联远端并推送 ```sh # 先验证 SSH 是否已绑到 GitHub 账号(期望回显 "Hi yiyunet! You've successfully authenticated") ssh -T git@github.com git remote add origin git@github.com:yiyunet/dsh-dingtalk-connector.git git push -u origin main ``` **若 `ssh -T` 失败**(密钥未绑),两条路: ```sh # 路 A:把公钥加到 GitHub → Settings → SSH and GPG keys → New SSH key # 公钥内容见本文第 0 节 # 路 B:改用 HTTPS(需要 Personal Access Token,不是账号密码) git remote set-url origin https://github.com/yiyunet/dsh-dingtalk-connector.git ``` --- ### Step 4 · 验证 CI 自动跑通 推送后,仓库里的 `.github/workflows/ci.yml` 会**自动触发**(`push` 到 `main` 与 PR 都触发),执行 `npm run check`(= `build && test && verify`)。 去 `https://github.com/yiyunet/dsh-dingtalk-connector/actions` 看结果。 **期望:绿。** 本地已确认可过的前提: | 前提 | 状态 | |---|---| | `package-lock.json` 是真实 lock(`npm ci` 需要) | ✅ 851 行、`lockfileVersion: 3` | | 新增的第八类断言(`files` 白名单自洽)能过 | ✅ 白名单 12 条全部存在 | | `npm run build` 能在干净环境重建 `lib/` | ✅ 依赖 `esbuild`(devDependency,`npm ci` 会装) | **若红了**,按 README「构建/校验报错时的排查顺序」走:先 `npm run inspect` 看产物实况。 --- ### Step 5 · 首次发布到 npm ```sh npm login npm whoami # ★ 必须回显 yiyunet,否则见下方"作用域陷阱" ``` > **作用域陷阱**:包名是 `@yiyunet/dsh-dingtalk-connector`,**`@yiyunet` 这个作用域必须归你的 npm 账号所有**。 > 若你的 npm 用户名不是 `yiyunet`,`npm publish` 会因无权限而失败(或 404)。 > 那时需要:改 `package.json` 的 `name` 为你自己的作用域,或去 npm 建一个 `yiyunet` 组织。 **发布前先本地打包自检**(这一步是防止把坏包发出去的最后一道闸): ```sh npm run check # build && test && verify npm pack # 产出 .tgz,不发任何东西 tar -tzf yiyunet-dsh-dingtalk-connector-1.0.0.tgz | grep -E "scripts/|assets/|lib/" ``` **必须看到** `scripts/postinstall.mjs`、`assets/`、`lib/index.mjs` 在列表里—— 这就是之前修的「`files` 漏 `scripts`」缺陷的**回归验证**。 确认后正式发布: ```sh npm publish # publishConfig.access 已设为 public,scoped 包无需再加 --access ``` --- ## 二、日常:加了新功能之后,怎么更新版本 ### 2.1 版本号怎么定 遵循[语义化版本](https://semver.org/lang/zh-CN/)。**本项目特有的"破坏性变更"判定**(普通 semver 不会提醒你,但这里必须按 major 处理): | 改动类型 | 版本 | 本项目的具体例子 | |---|---|---| | 修 bug / 文档 / 内部重构 | **patch** `1.0.0 → 1.0.1` | 修 CSV 的 BOM 判断、改文档措辞 | | **向后兼容的新功能** | **minor** `1.0.0 → 1.1.0` | 新增一个 `dingtalk_aitable_*` 工具、面板加一个区 | | **破坏性变更** | **major** `1.0.0 → 2.0.0` | 见下表 | **必须按 major(或至少 minor)处理的改动**: | 改动 | 为什么 | |---|---| | 已有工具的**入参字段名**变了 | 使用者的既有调用会直接报错 | | `cordis.patch.yml` 里**已有配置项的默认值或语义**变了 | 老配置的行为会静默改变 | | **RPC 方法名**变了 | 面板会**静默失效**(不报错,只是空壳)——本仓库已有一次这类教训 | | 导出 CSV 的**列集或编码**变了 | 下游按列位解析会错位 | | **删除**任何工具或配置项 | 显然 | > 判不准时的经验法则:**问"别人的现有用法会不会在某天早上突然坏掉?"** 会 → major;不会但多了新东西 → minor。 ### 2.2 五步发布流程 ```sh # ── ① 改代码 ──(唯一真源是 plugin-src/,绝不要直接改 lib/) # ── ② 跑全量校验(这一步不能跳)── npm run check # build && test && verify # ── ③ 更新 CHANGELOG.md ── # 把顶部 [Unreleased] 段的内容挪到新版本标题下,格式: # ## [1.1.0] - 2026-09-15 # ### 新增 / ### 修复 / ### 变更 / ### 已知限制 # ── ④ 提版本号(npm version 会同时改 package.json + 建 commit + 建 tag)── npm version minor -m "chore(release): v%s" # → package.json: 1.0.0 → 1.1.0 # → git commit "chore(release): v1.1.0" # → git tag v1.1.0 # ── ⑤ 推代码 + 推 tag(tag 必须单独推)── git push git push --tags # ── ⑥ 发布 npm ── npm publish ``` ### 2.3 三个必须知道的坑 **坑 1 · 忘了推 tag** `npm version` 只建 tag,**不推**。不执行 `git push --tags`,GitHub 上就没有 tag 锚点, 也就**建不了 Release**。这是最常见的一步遗漏。 **坑 2 · 没构建就发布** `lib/` 在 `.gitignore` 里**不入库**,但 `files` 白名单**会把它带进包**。 所以发布前**必须先 `npm run build`**(`npm run check` 的第一步就是 build,跑它就顺带解决了)。 否则发出去的包 `main` 指向一个不存在的 `lib/index.mjs`。 > 这不是靠自觉——**已用 `prepublishOnly` 钩子锁死**(见下)。 **坑 3 · `npm version` 会自己建提交** 如果你的工作区还有**未提交的改动**,`npm version` 仍会建立一个只含 `package.json` 变更的提交, 把未提交内容留在工作区——容易产生"半发布"状态。**先 `git status` 确认工作区干净**再执行 ④。 --- ## 三、发布策略三案并陈 | 方案 | 做法 | 成本 | 何时选 | |---|---|---|---| | **✅ 推荐:手工发布 + 守卫** | 上文的六步流程;用 `prepublishOnly: npm run check` 锁死"没校验不许发" | 每次发布约 5 分钟 | **当前规模最优**。发布频率低、单人维护,自动化收益 < 维护成本 | | **🚀 激进:CI 自动发布** | 新增 workflow:推 `v*` tag 触发 → 自动 `npm publish`(用 `NPM_TOKEN` secret) + `gh release create` | 首次约 1 人日 | 发布频繁、或有外部贡献者时。**代价**:npm token 成为 CI secret,泄露面变大 | | **🛡️ 稳健:只发 GitHub 不发 npm** | 仓库做版本留痕;分发靠 `dsh plugin add git+https://github.com/...` 或本地 `link:` | 0 | 只内部使用、或 npm 作用域未就绪时 | **我的建议:先走推荐案。** 等发布频率真的上来(比如一周多次),再上激进案。 --- ## 四、对现有架构影响 **运行时:零影响。** 本节全是仓库层操作。 **但有四处连带影响,需要知道**: 1. **若本插件目录嵌套在别的仓库内**,不做前置处理会让那个**外层仓库出现坏引用**——见第一节。 2. **`lib/` 不入库 → 全新克隆必须先 `npm install`**,否则插件加载不了 (症状只是一句晦涩的"模块找不到")。`prepare` 已自动构建,但要知道这条因果。 3. **`prepublishOnly` 会让 `npm publish` 变慢**(要跑一遍 build + test + verify)。这是**故意的**—— 用几秒钟换掉"发一个坏包"的概率。 4. **npm 发布后不可撤销**(24 小时内只能 `npm unpublish` 且限制严格)。 发错了只能靠 **发一个新 patch 覆盖**,所以 Step 5 前的 `npm pack` 自检**不能跳**。 --- ## 五、发布前检查清单(逐条打勾) ``` [ ] npm run check 通过(build + test + verify,全部断言绿) [ ] DSH_PRIVATE_TERMS 指向仓库外词表后重跑 npm run verify,**无「命中私有词表」报错** [ ] npm run test 里新增功能的用例已补 [ ] CHANGELOG.md 已把 [Unreleased] 内容归入新版本标题 [ ] README 里若涉及新工具/新配置项,已同步更新(含 README.en.md) [ ] docs/ 里若涉及架构决策,已补 ADR [ ] package.json 的 version 已通过 npm version 提升(不是手改) [ ] git status 干净,无未提交改动 [ ] git push && git push --tags 两条都执行了 [ ] npm pack 解包后确认 scripts/ assets/ lib/ 都在 [ ] npm publish 后用 npm view @yiyunet/dsh-dingtalk-connector version 复核 [ ] GitHub 上建 Release,附 tag 与变更要点 ``` --- ## 附:常用命令速查 ```sh # 版本 npm version patch|minor|major -m "chore(release): v%s" # 推送(tag 必须单独推) git push && git push --tags # 发布前自检 npm run check && npm pack # 发布 npm publish # 事后复核 npm view @yiyunet/dsh-dingtalk-connector version npm view @yiyunet/dsh-dingtalk-connector versions --json # 撤销(仅发布后 24 小时内,且限制严格) npm unpublish @yiyunet/dsh-dingtalk-connector@1.0.1 ``` --- **相关文档**:[安装与前置条件](./安装与前置条件.md) · [ADR-0001](./adr/0001-从手搓REST改为包装dws.md) · [../CHANGELOG.md](../CHANGELOG.md)