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,让系统自动补齐

如果这一步的值或结果需要在 valuesresults、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

下一步建议