scripts-test-template.mjs
3.82 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
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
#!/usr/bin/env node
// scripts/test.mjs —— 合并到默认分支(main / master)前的测试闸门。
// 顺序:detect → setup-db → build → lint → unit+integration → e2e
// (不在尾部 reset:下次跑的 setup-db 会 DROP+CREATE,重复清库无意义)
// 由 coding.mjs 的 test-gate stage(通过子会话)调用。
//
// 跨平台:所有命令经 child_process.spawnSync(cmd, { shell:true }) 执行,
// 在 Windows 走 cmd.exe,在 *nix 走 /bin/sh,无需 WSL / Git-Bash。
// 命令字符串来自 docs/04 §零(构建/lint/单测/e2e)——由 skeleton-gen 在 Plan 期填充。
import { spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const PROJECT_ROOT = join(dirname(fileURLToPath(import.meta.url)), '..')
// 测试 DB lane 化:本闸门自身不连库,schema 经 ERP_TEST_DB_SCHEMA env 贯通到全部子进程——
// setup-test-db.mjs 直读该 env;gradle test fork 的 JVM 与 e2e globalSetup 起的 bootRun 默认继承 env,
// 由后端 application*.yml 的 ${ERP_TEST_DB_SCHEMA:<缺省 schema>} 占位消费(锁定约定见 docs/04 §一)。
// env 缺省时读 lane 兜底 marker(.tmp/erp-lane-db,并行波次执行器建 lane worktree 后写入)回填 process.env,
// 保证提示词链丢前缀时整个闸门仍确定性指向 lane 库;主根无 marker,串行行为一字不变。
if (!process.env.ERP_TEST_DB_SCHEMA) {
const markerFile = join(PROJECT_ROOT, '.tmp', 'erp-lane-db')
if (existsSync(markerFile)) {
const laneSchema = readFileSync(markerFile, 'utf8').split('\n')[0].trim()
if (laneSchema !== '') {
process.env.ERP_TEST_DB_SCHEMA = laneSchema
console.log(`[test.mjs] lane DB marker 生效: ERP_TEST_DB_SCHEMA=${laneSchema}`)
}
}
}
// 在指定子目录下跑一条 shell 命令;非零退出码即终止整个闸门并透传该码。
function run(label, command, cwd = PROJECT_ROOT) {
console.log(`[test.mjs] ${label}: ${command}`)
const res = spawnSync(command, { cwd, shell: true, stdio: 'inherit' })
if (res.error) {
console.error(`[test.mjs] FATAL: 无法执行 (${label}): ${res.error.message}`)
process.exit(1)
}
if (res.status !== 0) {
console.error(`[test.mjs] FAIL (${label}) exit=${res.status}`)
process.exit(res.status === null ? 1 : res.status)
}
}
// Stack detection (runtime, mode-agnostic)
const hasBackend = existsSync(join(PROJECT_ROOT, 'backend'))
const hasFrontend = existsSync(join(PROJECT_ROOT, 'frontend'))
if (!hasBackend && !hasFrontend) {
console.error('[test.mjs] FATAL: neither backend/ nor frontend/ exists')
process.exit(1)
}
const backendDir = join(PROJECT_ROOT, 'backend')
const frontendDir = join(PROJECT_ROOT, 'frontend')
console.log('[test.mjs] 1/5 setup test db')
run('setup-test-db', `node ${JSON.stringify(join('scripts', 'setup-test-db.mjs'))}`)
console.log('[test.mjs] 2/5 build')
if (hasBackend) run('backend build', '{{backend_build}}', backendDir)
else console.log('[test.mjs] skip backend build')
if (hasFrontend) run('frontend build', '{{frontend_build}}', frontendDir)
else console.log('[test.mjs] skip frontend build')
console.log('[test.mjs] 3/5 lint')
if (hasBackend) run('backend lint', '{{backend_lint}}', backendDir)
else console.log('[test.mjs] skip backend lint')
if (hasFrontend) run('frontend lint', '{{frontend_lint}}', frontendDir)
else console.log('[test.mjs] skip frontend lint')
console.log('[test.mjs] 4/5 unit + integration')
if (hasBackend) run('backend test', '{{backend_test}}', backendDir)
else console.log('[test.mjs] skip backend test')
if (hasFrontend) run('frontend test', '{{frontend_test}}', frontendDir)
else console.log('[test.mjs] skip frontend test')
console.log('[test.mjs] 5/5 E2E')
run('e2e', '{{e2e_cmd}}')
console.log('[test.mjs] GREEN')