# ldesign-tool-release **Repository Path**: ldesign-v1/ldesign-tool-release ## Basic Information - **Project Name**: ldesign-tool-release - **Description**: LDesign versioning, changelog and publish orchestration - **Primary Language**: Unknown - **License**: MIT - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-03-20 - **Last Updated**: 2026-09-10 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # @ldesign/release [English README](./README.en.md) `@ldesign/release` 是 LDesign 的发布编排工具包,负责把“读取发布配置、生成 changelog、构造临时 user.npmrc、发布到一个或多个 npm registry、输出 CLI 自检信息”这些能力收敛为一套可组合、可测试、适合 CI 的基础设施。 ## 核心能力 - 多 registry 发布:支持 npmjs、GitHub Packages、GitLab Packages、Verdaccio 与自定义 registry。 - 配置统一:从 `.ldesign/npm.config.*` 自动查找并合并默认值。 - 发布隔离:发布时生成临时 `user.npmrc` 和隔离 cache,不污染全局 `~/.npmrc`。 - Changelog 薄封装:把 changelog 生成委托给 `@ldesign/changelog`,当前包只负责编排。 - CLI 可测试:CLI 支持注入 `spawnSync`、`console`、`stdout`、`NpmPublisher`、`generateChangelog`、`commandRegistrars`。 - 结构化产物:使用 `@ldesign/pack` 零配置自动识别 `src/` 运行时入口和声明补充文件,生成完整的 ESM、CJS 与 `.d.ts`。 ## 目录结构 ```text . ├─ docs/ 文档站入口 ├─ scripts/ 本地开发脚本 ├─ src/ │ ├─ bin/ TypeScript CLI 可执行入口 │ ├─ changelog/ changelog 动态加载与薄封装 │ ├─ cli/ CLI 装配、命令注册与输出格式化 │ ├─ npm/ 配置加载、registry 处理、发布器与 npmrc 生成 │ ├─ planning/ 发布计划与任务编排 │ └─ system/ 错误、临时目录、package.json 等底层工具 └─ test/ Vitest 测试 ``` ## 安装 ```bash pnpm add -D @ldesign/release # 或 npm i -D @ldesign/release ``` 要求: - Node.js >= 18 ## 快速开始 ### 1. 编写发布配置 ```ts // .ldesign/npm.config.ts import type { NpmReleaseConfig } from '@ldesign/release' export default { defaultRegistry: 'npm', registries: { npm: { registry: 'https://registry.npmjs.org/', tokenEnv: 'NPM_TOKEN', }, github: { registry: 'https://npm.pkg.github.com/', tokenEnv: 'GITHUB_TOKEN', scopes: ['@ldesign'], alwaysAuth: true, }, }, publish: { client: 'npm', tag: 'latest', access: 'public', skipExisting: true, }, } satisfies NpmReleaseConfig ``` ### 2. 构建产物 ```bash pnpm run build ``` ### 3. 发布前自检 ```bash pnpm exec ldesign-release doctor --all-registries ``` ### 4. 生成 changelog 并发布 ```bash pnpm exec ldesign-release release --registry npm --skip-existing ``` ## CLI 本包提供两个二进制命令: - `ldesign-release` - `ld-release` ### 全局参数 - `--cwd `:配置查找起点目录,默认当前工作目录。 - `--config `:显式指定配置文件路径,支持相对 `cwd` 的路径。 ### `config` 打印最终解析后的配置,默认会掩码 token。 ```bash pnpm exec ldesign-release config pnpm exec ldesign-release config --json ``` ### `npmrc` 打印某个 registry 生成的 `user.npmrc`,便于排查鉴权问题。 ```bash pnpm exec ldesign-release npmrc --registry npm pnpm exec ldesign-release npmrc --registry github --show-token ``` ### `doctor` 做发布前检查,覆盖 client、命令可用性、包信息、构建输出与 token 状态。 ```bash pnpm exec ldesign-release doctor pnpm exec ldesign-release doctor --all-registries pnpm exec ldesign-release doctor --check-auth pnpm exec ldesign-release doctor --report .tmp/release-doctor.json ``` ### `pack` 调用 `npm pack --dry-run` 预览实际会发布的文件。 ```bash pnpm exec ldesign-release pack pnpm exec ldesign-release pack --json pnpm exec ldesign-release pack --package-dir packages/button ``` ### `publish` 发布单个包到单个 registry。 ```bash pnpm exec ldesign-release publish --registry npm pnpm exec ldesign-release publish --registry-url https://registry.npmjs.org/ pnpm exec ldesign-release publish --tag next --access public --otp 123456 pnpm exec ldesign-release publish --skip-existing pnpm exec ldesign-release publish --dry-run pnpm exec ldesign-release publish --report .tmp/publish-result.json ``` 常用参数: - `--package-dir ` - `--registry ` - `--registry-url ` - `--tag ` - `--access public|restricted` - `--dry-run` - `--ignore-scripts` - `--skip-existing` - `--skip-git-checks` - `--allow-private` - `--allow-warnings` - `--json` - `--report ` 真实发布前会执行轻量安全检查。告警会写入 `PublishResult.warnings`,并在真实发布时默认阻断执行;确认告警可接受时,使用 `--allow-warnings` 继续发布。`--dry-run` 会保留告警但不要求该开关。 ### `publish-multi` 顺序发布到多个 registry。 ```bash pnpm exec ldesign-release publish-multi npm github pnpm exec ldesign-release publish-multi --all-registries pnpm exec ldesign-release publish-multi npm github --continue-on-error pnpm exec ldesign-release publish-multi npm github --continue-on-error --concurrency 2 pnpm exec ldesign-release publish-multi npm github --json pnpm exec ldesign-release publish-multi npm github --report .tmp/publish-multi.json ``` `--concurrency ` 仅在 `--continue-on-error` 开启时并发执行,默认仍保持串行失败即停。 ### `plan` 只生成发布计划和任务清单,不触发 changelog 或 publish,适合 CI 或人工发布前预览。 ```bash pnpm exec ldesign-release plan pnpm exec ldesign-release plan --bump minor --change "add registry failover" pnpm exec ldesign-release plan --from v1.2.2 --to HEAD pnpm exec ldesign-release plan --workspace --package-dir packages pnpm exec ldesign-release plan --channel next --json pnpm exec ldesign-release plan --report .tmp/release-plan.json ``` 传入 `--from` 或 `--to` 时,`plan` 会读取 `git log --format=%s`,并从 `feat:`、`fix:` 和 breaking 标记推断升级级别。 `--workspace` 会扫描 `--package-dir` 本身及下层 `package.json`,并在报告里返回多包计划。 ### 结构化报告 `doctor`、`plan`、`publish` 和 `publish-multi` 都支持 `--report `。相对路径会基于 `--cwd` 解析,并自动创建父目录,方便 CI 产出机器可读的 JSON 文件。 ### `changelog` 只生成 changelog,不发布。 ```bash pnpm exec ldesign-release changelog pnpm exec ldesign-release changelog --package-dir packages/button pnpm exec ldesign-release changelog --version 1.2.3 pnpm exec ldesign-release changelog --from v1.2.2 --to HEAD ``` ### `release` 先生成 changelog,再发布到单个 registry。 ```bash pnpm exec ldesign-release release pnpm exec ldesign-release release --registry npm pnpm exec ldesign-release release --skip-changelog pnpm exec ldesign-release release --skip-publish ``` ## 配置规则 ### 配置文件查找顺序 从 `cwd` 开始向上查找最近的 `.ldesign/` 目录,并按以下顺序命中第一个存在的文件: - `.ldesign/npm.config.ts` - `.ldesign/npm.config.js` - `.ldesign/npm.config.cjs` - `.ldesign/npm.config.mjs` - `.ldesign/npm.config.json` ### `registries.` 字段 - `registry`:必填,registry URL。 - `tokenEnv`:推荐,token 的环境变量名。 - `token`:可选,直接写 token。 - `scopes`:可选,需要绑定到该 registry 的 scope 列表。 - `alwaysAuth`:可选,写入 `always-auth=true`。 - `npmrc`:可选,附加额外 `.npmrc` 键值。 ### 解析优先级 `registry` 的最终解析顺序: 1. `publishOptions.registryUrl` 2. `package.json.publishConfig.registry` 3. `publishOptions.registry` 4. `config.defaultRegistry` `tag` 与 `access` 的最终解析顺序: 1. `publishOptions.tag` / `publishOptions.access` 2. `package.json.publishConfig.tag` / `package.json.publishConfig.access` 3. `config.publish.tag` / `config.publish.access` ## 编程 API ### 根入口导出 ```ts import { createReleaseService, createUserNpmrc, findNpmConfigPath, generateChangelog, loadNpmConfig, NpmPublisher, resolveRegistryToken, } from '@ldesign/release' const publicApi = { NpmPublisher, createReleaseService, createUserNpmrc, findNpmConfigPath, generateChangelog, loadNpmConfig, resolveRegistryToken, } void publicApi ``` ### 子路径导出 - `@ldesign/release/changelog` - `@ldesign/release/cli` - `@ldesign/release/npm` - `@ldesign/release/planning` ### `loadNpmConfig()` 用途:加载 `.ldesign/npm.config.*` 并合并默认值。 ```ts import { loadNpmConfig } from '@ldesign/release' const config = await loadNpmConfig({ cwd: process.cwd() }) if (config.defaultRegistry !== 'npm') { throw new Error(`unexpected default registry: ${config.defaultRegistry}`) } ``` ### `createUserNpmrc()` 与 `resolveRegistryToken()` 用途:在发布前生成临时 `user.npmrc`,或单独检查 token 解析结果。 ```ts import { createUserNpmrc, resolveRegistryToken } from '@ldesign/release' const registry = { registry: 'https://registry.npmjs.org/', tokenEnv: 'NPM_TOKEN', } const token = resolveRegistryToken(registry) const npmrc = createUserNpmrc({ ...registry, token }) if (!npmrc.includes('registry=https://registry.npmjs.org/')) { throw new Error('invalid npmrc content') } ``` ### `NpmPublisher` 用途:编程式发布单个包或多个 registry。 ```ts import { NpmPublisher } from '@ldesign/release' const publisher = await NpmPublisher.create({ cwd: process.cwd() }) const result = await publisher.publish({ packageDir: process.cwd(), registry: 'npm', skipExisting: true, }) if (!result.ok) { throw new Error(result.error ?? 'publish failed') } ``` 多 registry: ```ts import { NpmPublisher } from '@ldesign/release' const publisher = await NpmPublisher.create({ cwd: process.cwd() }) const results = await publisher.publishToRegistries(['npm', 'github'], { packageDir: process.cwd(), continueOnError: false, }) if (results.some(result => !result.ok)) { throw new Error('multi-registry publish failed') } ``` 需要并发发布时,必须同时开启 `continueOnError`: ```ts const results = await publisher.publishToRegistries(['npm', 'github'], { packageDir: process.cwd(), continueOnError: true, concurrency: 2, }) if (results.some(result => !result.ok)) { throw new Error('multi-registry publish failed') } ``` 宿主工具需要记录审计日志或遥测时,可以注入发布生命周期 hooks: ```ts import type { PublishHooks } from '@ldesign/release' import { NpmPublisher } from '@ldesign/release' const hooks: PublishHooks = { beforePublish(context) { console.info(`publishing ${context.packageJson.name} to ${context.registryUrl}`) }, onResult(result) { if (result.warnings?.length) { console.warn(result.warnings.join('\n')) } }, } const publisher = await NpmPublisher.create({ cwd: process.cwd() }, { hooks }) await publisher.publish({ dryRun: true, packageDir: process.cwd(), registry: 'npm' }) ``` ### `generateChangelog()` 用途:调用 `@ldesign/changelog` 生成 changelog。 ```ts import { generateChangelog } from '@ldesign/release' await generateChangelog({ cwd: process.cwd(), version: '1.2.3', from: 'v1.2.2', to: 'HEAD', }) ``` ### `ReleaseService` 用途:把变更列表映射为版本升级建议和任务清单。 ```ts import { createReleaseService } from '@ldesign/release' const service = createReleaseService() const plan = service.createPlan([ { packageName: '@ldesign/button', summary: '新增尺寸变体', bump: 'minor' }, ]) const tasks = service.buildTasks(plan) if (tasks.length === 0) { throw new Error('expected release tasks') } ``` ### `runCli()` 扩展命令 `@ldesign/release/cli` 导出 `runCli()`、`RunCliOptions` 与 `CliCommandRegistrar`,宿主工具可以注入自定义命令。 ```ts import type { CliCommandRegistrar } from '@ldesign/release/cli' import { runCli } from '@ldesign/release/cli' const registerHelloCommand: CliCommandRegistrar = cli => { cli.command('hello', 'Custom command').action(() => { console.info('hello') }) } await runCli({ commandRegistrars: [registerHelloCommand] }) ``` ## 类型概览 常用公开类型: - `NpmReleaseConfig` - `ResolvedNpmReleaseConfig` - `NpmRegistryConfig` - `PublishPackageOptions` - `PublishToRegistriesOptions` - `PublishHookContext` - `PublishHooks` - `PublishResult` - `CreateReleasePlanOptions` - `GenerateChangelogOptions` - `CliCommandRegistrar` - `RunCliOptions` - `ReleaseChange` - `ReleasePlan` - `ReleaseTask` ## 打包与产物 本包使用 `@ldesign/pack` 零配置构建。`pack build` 会自动识别 `src/` 下的运行时入口、`src/bin/cli.ts` CLI 入口,以及 `src/**/*.d.ts` 声明补充文件: - 输出格式:ESM + CJS - 类型产物:`.d.ts` - 输出目录:`dist/` - CLI 运行入口:`src/bin/cli.ts`,构建后输出为 `dist/bin/cli.js` 可以用 `pnpm exec pack explain --json` 查看当前自动配置计划;本包当前自动识别 37 个库入口、1 个 CLI 入口和 1 个声明补充文件。 构建命令: ```bash pnpm run build pnpm run dev ``` ## 测试与质量检查 当前测试使用本地 `Vitest` runner,并在受限 Windows 环境下关闭默认多进程 worker。 推荐顺序: ```bash pnpm run build pnpm run test:run pnpm run type-check pnpm run lint:check pnpm run format:check ``` 如果直接执行 `pnpm run test:run` 且尚未构建,runner 会明确提示缺少 `dist/` 产物。 ## 常见问题 ### 1. 为什么发布时不会修改我本机的 `~/.npmrc`? 因为 `NpmPublisher` 会为每次发布生成临时 `user.npmrc`,并通过环境变量注入: - `NPM_CONFIG_USERCONFIG` - `npm_config_userconfig` - `NPM_CONFIG_REGISTRY` - `npm_config_registry` - `NPM_CONFIG_CACHE` - `npm_config_cache` ### 2. `--skip-existing` 是怎么实现的? 发布前会执行一次: ```bash npm view @ version --registry ``` 若该版本已存在,则直接返回成功结果并标记为 `skipped`。 ### 3. `--dry-run` 为什么仍然会解析 registry? 因为 dry-run 仍然需要完整还原实际发布参数、registry 与 npmrc 内容,方便你确认最终行为是否正确;但当前实现允许在 dry-run 时不提供 token。 ## 贡献 - 优先保持 CLI 与编程 API 行为一致。 - 优先补测试,再改发布器与命令层行为。 - 若增加新的公开配置字段,请同步更新中英文 README 与类型定义。 - 若调整 `src/` 结构,请同步更新 `AGENTS.md` 中的项目背景与本次发现。 ## 更新日志 详见 [CHANGELOG.md](./CHANGELOG.md)。 ## 许可证 MIT