Skip to content

定义工作流 ​

返回总览

这一页专注在 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 常用字段 ​

字段作用
idworkflow 唯一标识
titleintro 标题,省略时会根据 id 推断
intro开场文案
outro收尾文案
steps步骤列表
summary控制默认摘要
snapshot控制 history 快照标签和开关
log控制成功后如何保存日志
onStartworkflow 开始时触发
onCompleteworkflow 结束前触发

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、loglocale、timeZone、renderer、executor、plugins、history、logs

下一步建议 ​