check-workflow-syntax.mjs
2.08 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#!/usr/bin/env node
// check-workflow-syntax.mjs — Workflow 脚本的确定性语法门(附录补 18)。
//
// 为什么不能直接 `node --check workflows/coding.mjs`:coding.mjs 是 Claude Workflow 运行时脚本,
// 运行时会把脚本体包进 async function 后执行(顶层 return 是结果通道)。node v26 见到
// `export const meta` 即按 ESM 解析,顶层 return 必报 `Illegal return statement`——基线恒红。
// 恒红的语法门会让人习惯性忽略红灯(掩盖真语法错),故此处复刻运行时的包裹再 --check。
//
// 用法:node lib/check-workflow-syntax.mjs [workflow 文件路径...](缺省 workflows/coding.mjs)
// 退出码:透传 node --check(0=语法通过)。
import { readFileSync, writeFileSync, mkdirSync } from 'node:fs'
import { spawnSync } from 'node:child_process'
import { basename, dirname, join, resolve } from 'node:path'
import { fileURLToPath } from 'node:url'
const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..')
const targets = process.argv.slice(2).length ? process.argv.slice(2) : [join(repoRoot, 'workflows', 'coding.mjs')]
let failed = false
for (const target of targets) {
const src = readFileSync(resolve(target), 'utf8')
// 剥行首 `export `(运行时注入 meta,不走 ESM export),再包进运行时同形的 async 包裹。
const wrapped = [
'async function __wf(args, agent, parallel, phase, log) {',
src.replace(/^export /gm, ''),
'}',
].join('\n')
// 临时文件名由目标 basename 确定性导出(无时间戳/随机数),落在仓库 .tmp/ 下。
const tmpDir = join(repoRoot, '.tmp', 'check-workflow-syntax')
mkdirSync(tmpDir, { recursive: true })
const tmpFile = join(tmpDir, `${basename(target)}.wrapped.mjs`)
writeFileSync(tmpFile, wrapped)
const r = spawnSync(process.execPath, ['--check', tmpFile], { stdio: 'inherit' })
if (r.status !== 0) {
failed = true
console.error(`✗ 语法检查失败:${target}(包裹文件 ${tmpFile})`)
} else {
console.log(`✓ 语法通过:${target}`)
}
}
process.exit(failed ? 1 : 0)