Appearance
定义工作流
这一页专注在 authoring 层
目标只有一个,把 workflow 写得清楚、稳、可维护
最常见的写法
ts
import {
createWorkflowKit,
defineStep,
defineSteps,
defineWorkflow,
} from 'clack-kit';
const steps = defineSteps([
defineStep.text({
id: 'service',
message: '服务名',
required: true,
}),
defineStep.select({
id: 'environment',
message: '环境',
options: ['staging', 'production'],
}),
defineStep.command({
id: 'deploy',
title: '部署服务',
when: (context) => Boolean(context.values.service),
async run(context, io) {
io.stdout(`deploy ${String(context.values.service)}\n`);
return {
deployed: true,
environment: context.values.environment,
};
},
}),
]);
const workflow = defineWorkflow({
id: 'deploy-service',
intro: '部署服务',
outro: (context) => `完成部署:${String(context.values.service)}`,
steps,
});
await createWorkflowKit().run(workflow);这条主线已经覆盖了大部分业务流程
- defineSteps 负责收拢步骤数组
- defineStep.* 负责让单个步骤的类型更稳定
- defineWorkflow 负责把步骤、生命周期和持久化策略装进一条流程
defineWorkflow 常用字段
| 字段 | 作用 |
|---|---|
| id | workflow 唯一标识 |
| title | intro 标题,省略时会根据 id 推断 |
| intro | 开场文案 |
| outro | 收尾文案 |
| steps | 步骤列表 |
| summary | 控制默认摘要 |
| snapshot | 控制 history 快照标签和开关 |
| log | 控制成功后如何保存日志 |
| onStart | workflow 开始时触发 |
| onComplete | workflow 结束前触发 |
defineWorkflow() 不再承载 ASCII banner 配置
如果需要统一的 banner,请在 createWorkflowKit({ id, asciiArt }) 上配置
intro 和 outro
适合放整条流程级别的提示,不适合替代单个步骤的说明
summary
未显式提供 onComplete 时,运行时会使用默认摘要逻辑
log
workflow.log 只定义这条 workflow 如何处理日志,不负责启用日志能力本身
日志能力是否可用,仍然由 createWorkflowKit 的 logs 配置决定
defineSteps 的价值
defineSteps 本身不复杂,价值在于让步骤数组的类型推导更稳定
对实际维护来说,还有两个好处
- 步骤顺序一眼能看完
- 条件流写成平铺结构,阅读成本低
defineStep.* 什么时候更合适
能直接用 defineStep.* 时,优先走这条路
原因很简单
- 编辑器提示更稳
- 每种步骤支持的字段更明确
- 不容易把 type 写错
- 展示类步骤和一次性 command 步骤可以省略 id,让系统自动补齐
如果这一步的值或结果需要在 values、results、CLI 参数映射里长期引用,仍然建议显式写 id
写法上也可以混用
ts
const steps = defineSteps([
defineStep.text({
id: 'name',
message: '项目名',
}),
{
id: 'install',
message: '安装依赖吗',
type: 'confirm',
},
]);when 的推荐写法
把步骤平铺,再用 when 控制命中,是最稳定的组织方式
ts
const steps = defineSteps([
defineStep.select({
id: 'framework',
message: '框架',
options: ['react', 'vue'],
}),
defineStep.note({
id: 'react-note',
message: 'React 模板会额外生成 tsconfig 和 eslint 配置',
when: (context) => context.values.framework === 'react',
}),
]);这种写法有三个优点
- 顺序清晰
- 条件集中在步骤本身
- 不需要把读者带进额外的嵌套结构
同名步骤的规则
多条步骤共用同一个 id 时,when 必须互斥
ts
defineSteps([
defineStep.select({
id: 'channel',
message: '发布通道',
options: ['beta', 'stable'],
when: (context) => context.values.prerelease === true,
}),
defineStep.select({
id: 'channel',
message: '发布通道',
options: ['stable'],
when: (context) => context.values.prerelease === false,
}),
]);如果条件可能同时成立,后面的同名步骤会被跳过,或者覆盖前面的结果
生命周期钩子用在什么地方
onStart
适合放流程级准备动作
- 输出说明
- 初始化外部资源
- 记录额外日志
onComplete
适合放默认摘要之外的收尾逻辑
- 输出定制总结
- 写补充结果
- 调用外部通知或保存额外文件
workflow 层和 kit 层怎么分工
一个简单原则就够用
- 影响单条流程行为的,放 workflow
- 影响运行环境的,放 kit
例如
| 放 workflow | 放 kit |
|---|---|
| intro、outro、steps、summary、log | locale、timeZone、renderer、executor、plugins、history、logs |