# 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