文档
工作流程
版本化的 YAML 检查和服务步骤针对触发它们的确切项目提交运行。
从英文自动翻译。 查看英文原文
跑步评估什么
工作流程文件提交于 .cadcamfun/workflows/ 与一个 .yml 或 .yaml 扩大。 sidecar 从选定的提交引用加载它们。编辑未提交的图形或工作流程不会更改现有运行看到的文件。
@cadcamfun/workflow 解析 YAML、验证依赖关系、匹配触发器并执行注册的函数。它不执行任意 shell 命令、容器映像或下载的操作。每个 uses 在任何步骤开始之前,标识符必须存在于运行程序的当前注册表中。
编写工作流程
name: Project checks
on:
manual: true
jobs:
validate:
steps:
- id: structure
uses: project/validate
- uses: project/require-gcode
- uses: cam/sequence 工作流程有一个名称, on 触发器和由标识符键入的作业。一项作业至少包含一个步骤并且可以声明 needs。一步有 uses, 选修的 id 和姓名,可选 with 论据, if, 和 continueOnError.
标识符以字母开头,最多包含 64 个字母、数字、下划线或连字符。步骤标识符在整个工作流程中是唯一的。解析会拒绝无效的 YAML、模式失败、未知的作业依赖项、循环依赖项和重复的步骤标识符。
内置检查
project/validate解析提交的 JSON 文件并根据其文件名检查已识别的绘图/组件/刀具路径实体 ID 并验证其名称。它返回资源计数。project/require-gcode如果提交的刀具路径有空或缺少 G 代码,则会失败。它不要求项目至少包含一个刀具路径。cam/verify从提交的刀具路径 G 代码构建重播请求,并且Toolpath.data.request。它会在错误严重性重放结果或警告时失败with.severity是warning。可选参数是toolpath和tolerance.cam/gcode从承诺中重生data.request并将其修剪后的 NC 与提交的文本进行比较。它接受一个可选的toolpath选择器。cam/sequence检查存储的请求是否存在冲突的刀具编号分配以及其先前刀具不大于活动刀具的剩余加工请求。
CAM 步骤读取存储的生成器请求格式。缺少 NC、缺少生成请求或不受支持的验证请求会报告在 skipped 适用时的输出;他们不会自动失败该步骤。检查已验证/匹配的计数和跳过的原因。仅包含跳过的 CAM 工作的绿色工作流程并不能证明程序经过验证。
服务支持的步骤
实时 sidecar 注册 vcs/commit, notify/push,并且,在助理服务存在之后, ai/prompt。注册是显式的:在没有这些服务的情况下创建的运行器会导致工作流程失败,并命名为不可用的步骤。
vcs/commit 需要一条消息并接受 files 下的文本记录 .cadcamfun/ 带有 YAML、Markdown、JSON 或文本扩展。它充当运行帐户并遵守受保护分支策略。 extra-file 参数不能直接替换 CAD 实体路径。当前的实现委托项目提交,而不检查干净的工作副本:它还会快照已签出分支上的当前数据库编辑,这可能与正在检查的引用不同。仅当要提交当前分支和工作状态时才运行此写入步骤。
notify/push 为运行的参与者创建应用程序通知,其中包含可选的标题、正文和成功/失败结果。这是一个持续的通知步骤;它本身并不通过外部推送提供商建立交付。
ai/prompt 需要提示并可选择接受系统文本和模型。它可以替换提交的文件内容并返回答案文本和模型标识。缺少凭据或提供商错误导致此步骤失败;模型答案是咨询输出,与确定性几何检查分开。
纯内置步骤注册在 builtinSteps (apps/sidecar/src/services/workflow-steps.ts): project/validate, project/require-gcode, cam/verify, cam/gcode, 和 cam/sequence。他们只需要提交的文件和 @cadcamfun/core,因此即使助手未运行,它们也可用。
执行、输出和条件
作业在依赖级别中运行,同一级别中的独立作业同时运行。作业中的步骤按顺序运行。依赖关系未成功的作业将被跳过。默认的步骤条件是 success; always 在任一结果之后运行 failure 在该作业中先前未处理的步骤失败后运行。
continueOnError: true 记录失败步骤事件而不设置作业的失败标志。否则,失败的步骤将跳过后面仅成功的步骤并标记作业失败。只有每项工作都取得成功,总体结果才是成功。
早期的命名步骤输出可以参考 with 使用如下所示的表达式的值。仅由一个表达式组成的值保留其类型;较长字符串内的替换将输出转换为文本。作业在消耗另一个作业的输出时应该声明依赖关系。
with:
body: "Checked drawings: ${{ steps.structure.outputs.drawings }}" 触发器和调度
on.push 支持分支和更改路径全局过滤器; on.pullRequest 过滤基础分支; on.manual 启用显式调度。单个星号在路径段内匹配,双星号可以跨越路径段。
HTTP 提交/推送集成当前向自动推送触发器提供一个空的更改路径列表。因此,具有推送路径过滤器的工作流与这些事件不匹配。使用仅分支推送过滤器或手动调度进行此集成,直到提供更改的路径数据。
阅读提交的定义 GET /api/projects/:id/actions/workflows,可选地与 ref。阅读一篇: /workflows/:name。返回格式错误的文件及其内容和 problem 所以可以修复。要编辑定义,请将其安全工作流程路径包含在项目提交中。
POST /api/projects/:id/actions/dispatch 接受可选的引用并在该引用处分派每个匹配的手动工作流程;它不是一个工作流程名称的选择器。读取保存的结果 GET /api/projects/:id/actions/runs.
运行持久性和事件
运行器将工作流程、事件、引用、提交 SHA、状态、时间戳和累积事件记录在 WorkflowRun。工作事件标志着开始和结束;步骤事件包括索引、标识符、状态、可选输出和失败文本。无效的 YAML 将保留为带有无效文件事件的失败运行。
当前的 HTTP 触发器路径等待工作流解决,然后一起发布运行开始和运行完成的实时事件。它不会通过该 HTTP 请求流式传输每个运行步骤。核心跑步者暴露了 onEvent 需要步骤进度的集成的回调。
精确解释结果
工作流报告其捕获的提交和注册的步骤实现。它不验证以后的工作副本编辑或在失败时删除已记录的提交。在使用成功运行作为发布门之前,检查跳过的检查、警告策略和相关刀具路径证据。
实施分为 packages/workflow/src 用于模式/触发/执行和 sidecar workflow-runner.ts, workflow-steps.ts, 和 workflow-service-steps.ts 用于提交的项目数据、持久运行和应用程序服务。
运行独立的工作流引擎示例
包使用者提供注册的函数及其类型化上下文。此示例解析手动工作流程,执行一项注册检查,并返回其作业结果,而无需打开 SQLite 或调用 AI。
import { Effect } from "effect"
import { parseWorkflow, runWorkflow } from "@cadcamfun/workflow"
const source = [
"name: Isolated check", "on:", " manual: true", "jobs:",
" inspect:", " steps:", " - id: checked", " uses: inspection/check"
].join("\n")
const result = await Effect.runPromise(Effect.gen(function* () {
const workflow = yield* parseWorkflow(source)
return yield* runWorkflow(workflow, {
ctx: { drawingCount: 2 },
registry: { "inspection/check": ({ ctx }) => Effect.succeed({ drawings: ctx.drawingCount }) }
})
}))
console.log(result) // { status: "success", jobs: { inspect: "success" } } 在工作区中运行,具体取决于效果和 @cadcamfun/workflow, 例如 apps/sidecar。上面的自定义步骤是所提供的注册表的本地步骤;在桌面工作流程中命名它不会将其安装到生产 sidecar 中。
来源: packages/workflow/src/schema.ts 和 run.ts.
匹配过滤器并安全地消耗输出
推送触发器需要其分支过滤器进行匹配,并且当存在路径列表时,至少需要一个提供的更改路径进行匹配。省略的过滤器不受限制;明确的空列表不匹配任何内容。手动事件需要 on.manual 出席。拉取请求匹配使用基础分支,而不是头提案名称。
输出替换遍历嵌套数组和记录。精确缺失的输出表达式的计算结果为未定义;嵌入较长字符串中的缺失输出将变为空文本。不要依赖跳过或失败的步骤来产生输出。宣布 needs 对于跨作业消费者,因此依赖调度建立数据顺序。
步 if 是三种文字模式之一,不是通用的表达语言。该引擎未实现工作流变量、shell 插值、矩阵构建、任意 JavaScript 条件和第三方操作加载。分开的 ai/prompt 已提交处理程序替代者 files.<path> 提示中的参考文献;普通步骤不会获得额外的替换。
来源: packages/workflow/src/trigger.ts, run.ts, 和 apps/sidecar/src/services/workflow-service-steps.ts.
省略的分支或路径过滤器匹配每个值。手动调度仅匹配声明的工作流 on.manual,并且拉取请求事件与工作流程的匹配 branches 针对拉取请求的基础分支的 globs (packages/workflow/src/trigger.ts).
诊断工作流程结果
| 寻找 | 含义和下一步检查 |
|---|---|
返回工作流程文件 problem | 修复 YAML/架构或依赖项错误并提交修复的文件。文件列表保留无效内容以供编辑。 |
| 执行前的未知步骤 | 比较每一个 uses 带有已注册的 sidecar 步骤 ID。包示例的本地注册表不是生产注册表。 |
| 推后不跑 | 确认提交的引用中存在工作流、分支匹配,并且没有不支持的 HTTP 更改路径过滤器排除该事件。 |
运行成功但是 verified: 0 | 读 skipped。缺少生成器请求或不受支持的刀具路径可能会导致检查没有几何证据。 |
| 已跳过从属作业 | 检查每项结果 needs 工作;依赖关系失败会阻止作业启动,包括其始终受条件限制的步骤。 |
| 进程终止后 Run 仍保持运行状态 | 检查其时间戳/事件和确切的提交。运行器分别保持开始状态和最终状态,并且不会自动恢复中断的运行。 |
默认情况下,保存的运行历史记录会返回服务边界处的最新 50 运行。实时事件通知是尽力而为的侧通道:检查持久运行结果的持久行,而不是将通知传递视为执行记录。
来源: apps/sidecar/src/services/workflow-runner.ts, workflow-steps.ts, 和 http/vcs.ts.
工作流程文件格式
工作流程是一个 YAML 文件,位于 .cadcamfun/workflows/<name>.yml 在项目存储库中,使用它检查的模型进行版本控制。它的形状反映了 GitHub Actions (on, jobs, needs, steps, uses, with), 但 uses 始终命名已注册的 CADCAMFUN 步骤。工作流无法运行 shell 命令或容器映像。
name: Release check
on:
push:
branches: [main, "release/*"]
paths: ["toolpaths/**"]
pullRequest:
branches: [main]
manual: {}
jobs:
validate:
steps:
- uses: project/validate
- uses: project/require-gcode
verify:
needs: [validate]
steps:
- id: verify
uses: cam/verify
with: { severity: warning }
gcode:
needs: [validate]
steps:
- uses: cam/gcode
- uses: cam/sequence
report:
needs: [verify, gcode]
steps:
- id: review
uses: ai/prompt
with:
prompt: "Summarise risks in this program: ${{ files.toolpaths/op1.json }}"
continueOnError: true
- uses: notify/push
if: always
with:
title: "Release check finished"
body: "${{ steps.review.outputs.text }}" needs 成功了;否则报告为 skipped.| 钥匙 | 规则 |
|---|---|
name | 必填,1–128 个字符。 |
on.push | 选修的 branches 和 paths 全局列表。当分支匹配并且至少一个更改的路径匹配时,推送就匹配。 |
on.pullRequest | 选修的 branches globs,在打开时与拉取请求的基本分支进行匹配。 |
on.manual | 任何值(例如 {}) 允许从桌面或移动设备进行调度。 |
jobs.<id> | 职位 ID 以字母开头,可能包含字母、数字、 _, 和 - (最多 64 个字符)。每项工作至少需要一个步骤。 |
needs | 必须首先成功的作业 ID。解析文件时,未知的 ID 和周期将被拒绝。 |
steps[].uses | 带有至少一个斜杠的小写步骤标识符,例如 cam/verify. |
steps[].id | 选修的;在整个工作流程中都是独一无二的。后续步骤需要读取其输出。 |
steps[].with | 步骤的自由格式参数。字符串被插值。 |
steps[].if | success (默认值:仅当作业到目前为止没有失败时才运行), failure (仅在失败后运行),或 always. |
steps[].continueOnError | 如果为 true,则此步骤的失败不会将作业标记为失败。 |
全局使用 * 在一个路径段内匹配并且 ** 跨段匹配。省略的过滤器列表匹配所有内容。 toolpaths/*.json 比赛 toolpaths/a.json 但不是 toolpaths/x/a.json; toolpaths/** 两者都匹配。
步骤参考
纯步骤仅读取正在检查的提交的文件,而不是实时数据库,因此运行会准确报告已推送的树。一旦服务存在,服务支持的步骤就会被注册。在那之前,命名一个是一个硬错误,而不是一个无声的无操作。
| 步 | 和 | 输出 | 失败时 |
|---|---|---|---|
project/validate | 没有任何 | issues (0), drawings, workflows | 实体 JSON 文件无效, drawings/, components/, 或者 toolpaths/ 文件的 id 与其文件名不匹配,或者 name 失踪了。 |
project/require-gcode | 没有任何 | toolpaths | 提交中的任何刀具路径都没有 G 代码。 |
cam/verify | toolpath?, tolerance?, severity? (error 默认,或 warning) | verified, toolpaths, skipped | 重播发现错误(或警告,带有 severity: warning):急流进入毛坯、凿孔、柄部或夹具碰撞、材料留下。没有 G 代码或存储的生成请求的程序列于 skipped,从未算作通过。 |
cam/gcode | toolpath? | matched, toolpaths, skipped | 从存储的请求重新生成不会重现提交的 G 代码(忽略标头标记和周围的空白)。 |
cam/sequence | toolpath? | toolpaths, tools | 一个刀具编号命名两种不同的刀具,或残料加工操作的名称 restToolDiameter 不大于其 toolDiameter. |
vcs/commit | message (必需的), files 地图 | commit, files | 工作副本脏了,路径在外面 .cadcamfun/,或者文件不是 .yml, .yaml, .md, .json, 或者 .txt。人工智能演员被拒绝 main. |
notify/push | title?, body?, outcome? (success 默认,或 failure) | notified | 运行永远不会失败。创建一个 WORKFLOW_PASSED 或 WORKFLOW_FAILED 调度帐户的通知,链接到项目历史记录。 |
ai/prompt | prompt (必需的), system?, model? | text, model | 没有可用型号或调用失败。只是这一步失败了;几何判定不受影响。 |
畸形 with 值的读取是防御性的:忽略需要字符串的非字符串或非有限数字,而不是使运行失败。检查输出以确认过滤器,例如 toolpath 实际应用了。
输出和插值
一步与 id 在运行范围内存储其输出,因此作业可以读取其作业的输出 needs。在任何字符串内 with, ${{ steps.<id>.outputs.<key> }} 通过对象和数组递归地替换。当字符串仅由一个表达式组成时,该值将保持其类型(数字仍然是数字)。嵌入其他文本后,该值将转换为字符串,缺失值将变为空字符串。 ai/prompt 另外扩展 ${{ files.<path> }} 提交中该文件的文本(如果该文件不存在则为空)。
| 表达式为 | 产值 | 结果 |
|---|---|---|
"${{ steps.verify.outputs.verified }}" | 3 | 3 (数字) |
"Verified ${{ steps.verify.outputs.verified }} programs" | 3 | "Verified 3 programs" |
"${{ steps.missing.outputs.x }}" | 没有任何 | undefined |
运行生命周期和错误
- 触发事件(推送、拉取请求或手动)选择引用树中的工作流,从提交而不是工作副本中读取。
- 每个文件都会被解析。 YAML 错误(
yaml),架构错误(schema),未知needs(needs),周期(cycle) 和重复的步骤 ID (duplicate-step-id)被报告为无效运行并给出原因。 - 每一个
uses在任何运行之前都会根据注册表进行检查。未知步骤导致运行失败Unknown step(s): ...,因此拼写错误永远不会一半应用更改。 - 工作按级别排序。每个级别同时运行;一份工作,其
needs没有全部成功则跳过。 - 进度事件(
job-start,step带有状态和输出或消息,job-end)流式传输到桌面和配套设备,并且运行及其事件会持续存在。 - 仅当每个作业都成功时,运行才会成功。跳过的作业算作不成功。
现场活动 workflow.started (带有触发事件)和 workflow.finished (success, failure, 或者 cancelled)每次运行都会发布。
证据,而非装饰
在内置步骤中,仅 cam/verify 针对库存和工具重播程序。其他检查确认所提交内容的一致性。读 skipped 列表:无法验证的刀具路径不是经过验证的刀具路径。
完整运行,逐个事件跟踪
该工作流程同时执行引擎的每条规则:在作业之间传递的输出、全值和嵌入式插值, continueOnError, if: failure、由于依赖失败而跳过的作业以及独立作业。它是用 runWorkflow 和四个测试步骤处理程序: demo/measure 回报 { volume: 7842.92, part }, demo/check 打印收到的内容, demo/fail 失败了 StepError("tolerance exceeded") 和 demo/notify 什么也不返回。
name: Release check
on:
push: { branches: [main, "release/**"], paths: ["parts/**/*.json"] }
manual: {}
jobs:
measure:
steps:
- id: size
uses: demo/measure
with: { part: bracket }
verify:
needs: [measure]
steps:
- uses: demo/check
with: { volume: "${{ steps.size.outputs.volume }}", label: "vol=${{ steps.size.outputs.volume }} mm3" }
- uses: demo/fail
continueOnError: true
- uses: demo/fail
- uses: demo/notify
if: failure
- uses: demo/notify
publish:
needs: [verify]
steps:
- uses: demo/notify
lint:
steps:
- uses: demo/notify levels [["measure","lint"],["verify"],["publish"]]
{"type":"job-start","job":"measure"}
{"type":"step","job":"measure","index":0,"uses":"demo/measure","status":"success","outputs":{"volume":7842.92,"part":"bracket"}}
{"type":"job-end","job":"measure","status":"success"}
{"type":"job-start","job":"lint"}
{"type":"step","job":"lint","index":0,"uses":"demo/notify","status":"success","outputs":{}}
{"type":"job-end","job":"lint","status":"success"}
{"type":"job-start","job":"verify"}
check got {"volume":7842.92,"label":"vol=7842.92 mm3"}
{"type":"step","job":"verify","index":0,"uses":"demo/check","status":"success","outputs":{"ok":true}}
{"type":"step","job":"verify","index":1,"uses":"demo/fail","status":"failure","message":"Cause([Fail(StepError: tolerance exceeded)])"}
{"type":"step","job":"verify","index":2,"uses":"demo/fail","status":"failure","message":"Cause([Fail(StepError: tolerance exceeded)])"}
{"type":"step","job":"verify","index":3,"uses":"demo/notify","status":"success","outputs":{}}
{"type":"step","job":"verify","index":4,"uses":"demo/notify","status":"skipped"}
{"type":"job-end","job":"verify","status":"failure"}
{"type":"job-end","job":"publish","status":"skipped"}
result {"status":"failure","jobs":{"measure":"success","lint":"success","verify":"failure","publish":"skipped"}} | 观察 | 规则 |
|---|---|
measure 和 lint 在第一级运行 | 没有工作 needs 表单级别 0 并同时运行 |
volume 到达的数字为 7842.92, label 作为字符串 | 恰好是一个表达式的字符串保留输出的类型;嵌入的表达式被字符串化 |
| 步骤 1 失败,但作业继续正常进行 | continueOnError: true 记录失败而不将作业标记为失败 |
| 步骤 2 失败并标记作业失败 | 默认行为 |
| 运行步骤 3,跳过步骤 4 | if: failure 仅在失败后运行;默认值 if: success 1 后被跳过 |
publish 被跳过,从未开始 | 一份工作,其 needs 并非全部成功仅发出 job-end 和 skipped |
全面的 failure | 仅当每项作业都完成时,运行才会成功 success;跳过的作业算作不成功 |
触发匹配,已测试
| 事件 | 比赛? | 为什么 |
|---|---|---|
推到 main,改变了 parts/a/b.json | 是的 | 支线比赛;路径匹配 parts/**/*.json |
推到 main,改变了 README.md | 否 | 没有更改的路径匹配 |
推到 release/6.1,改变了 parts/x.json | 否 | parts/**/*.json 编译为 ^parts\/.*\/[^/]*\.json$,其中需要一个目录 parts/ 和文件 |
推到 feature/x | 否 | 分支不匹配 |
| 手动的 | 是的 | manual 被宣布 |
将请求拉入 main | 否 | 不 pullRequest 已声明触发器 |
** 这里每边都需要一个段
因为 ** 变成 .* 在两个斜杠之间, parts/**/*.json 不直接匹配文件 parts/。列出两种模式, parts/*.json 和 parts/**/*.json,覆盖两者。
* 在一个路径段内匹配; ** 跨段匹配。省略 branches 或 paths 表示任何值匹配。
重现每个解析错误
| 代码 | 输入问题 | 信息 |
|---|---|---|
yaml | 格式错误的 YAML,例如 name: [x | 无效的 YAML:SyntaxError:YAML 解析错误:意外的标记 |
schema | uses: Shell (不是小写 area/step iD) | SchemaError(需要与 RegExp ... 匹配的字符串)与路径 ["jobs"]["a"]["steps"][0] |
needs | needs: [b] 没有工作 b | 工作 a 需要未知的工作 b |
cycle | a 需要 b 和 b 需要 a | 工作流作业形成一个循环:a、b |
duplicate-step-id | 两步与 id: s,即使在不同的工作中 | 步骤 ID 在整个工作流程中必须是唯一的 |
在运行时,任何 uses 如果没有注册处理程序,则第一步之前的整个运行都会失败,列出每个未知的 id: Unknown step(s): demo/measure, demo/check, …。因此,拼写错误绝不会影响工作流程的应用。
| 场地 | 规则 |
|---|---|
name | 1–128 个字符 |
| 作业和步骤 ID | 先写字母,再写字母、数字, _ 或 -;最多 64 个字符 |
uses | 小写字母段由 /, 至少两段 |
steps | 每个职位至少有一个 |
if | success (默认), always, failure |