check-workflow-syntax.mjs 2.08 KB
#!/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)