scripts-setup-test-db-template.mjs
4.96 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
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
#!/usr/bin/env node
// scripts/setup-test-db.mjs — DROP + CREATE 开发/沙盒库。
// 由 coding.mjs 的 test-gate 调用;schema 由 Flyway 在 Spring Boot 启动时重放。
// DB 凭据从仓库根 config-vars.yaml 的 database: 段读取;host / user / password 信任该文件,port 仅校验范围。
import { spawnSync } from 'node:child_process'
import { existsSync, readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'
const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url))
const CONFIG_FILE = join(SCRIPT_DIR, '..', 'config-vars.yaml')
// 极简 YAML 读取(2 层 map + 标量;与插件 lib/yaml-config.mjs 同规则,内联以免运行时依赖)。
function parseScalar(raw) {
let s = String(raw).trim()
if (s === '' || s[0] === '#') return ''
const q = s[0]
if (q === '"' || q === "'") {
const end = s.indexOf(q, 1)
if (end !== -1) return s.slice(1, end)
}
const hash = s.indexOf(' #')
if (hash !== -1) s = s.slice(0, hash).trim()
return s
}
function parseYamlConfig(text) {
const root = {}
let section = null
for (const rawLine of text.split('\n')) {
const line = rawLine.replace(/\r$/, '')
const trimmed = line.trim()
if (trimmed === '' || trimmed[0] === '#') continue
const colon = line.indexOf(':')
if (colon === -1) continue
const key = line.slice(0, colon).trim()
if (key === '') continue
const indent = line.length - line.replace(/^\s+/, '').length
const value = parseScalar(line.slice(colon + 1))
if (indent === 0) {
if (value === '') {
section = {}
root[key] = section
} else {
root[key] = value
section = null
}
} else if (section) {
section[key] = value
} else {
root[key] = value
}
}
return root
}
if (!existsSync(CONFIG_FILE)) {
console.error(`[setup-test-db] config-vars.yaml 不存在(${CONFIG_FILE})`)
process.exit(1)
}
const db = parseYamlConfig(readFileSync(CONFIG_FILE, 'utf8')).database || {}
// lane 兜底 marker:并行波次执行器建好 lane worktree 后在仓库根写 .tmp/erp-lane-db(一行 lane schema 名)。
// lane 注入是 prompt-only(提示词链两跳,任何一跳丢 ERP_TEST_DB_SCHEMA 前缀即对共享库 DROP+CREATE),
// 本脚本是最具毁灭性的操作,必须有确定性兜底;主根无 marker 且 env 缺省时走 config,串行行为一字不变。
function readLaneDbMarker() {
const markerFile = join(SCRIPT_DIR, '..', '.tmp', 'erp-lane-db')
if (!existsSync(markerFile)) return ''
return readFileSync(markerFile, 'utf8').split('\n')[0].trim()
}
const DB_HOST = db.host ?? ''
const DB_PORT = db.port ?? '3306'
const DB_USER = db.user ?? ''
const DB_PASSWORD = db.password ?? ''
// 取值顺序 env > marker > config(并行 lane 化;校验逻辑沿用,作用于生效值)。
const LANE_MARKER_SCHEMA = readLaneDbMarker()
const DB_SCHEMA = process.env.ERP_TEST_DB_SCHEMA || LANE_MARKER_SCHEMA || (db.schema ?? '')
// marker 是不可见的兜底路径(env 前缀在命令行上自明),生效时必须留痕便于排查指错库。
if (!process.env.ERP_TEST_DB_SCHEMA && LANE_MARKER_SCHEMA) {
console.log(`[setup-test-db] lane DB marker 生效: ${LANE_MARKER_SCHEMA}`)
}
function rejectPlaceholder(key, value) {
if (typeof value === 'string' && value.includes('【人工填写')) {
console.error(`[setup-test-db] database.${key} 仍是占位,请先在 config-vars.yaml 填真实值(database.password 可填 '' 表示空密码)`)
process.exit(1)
}
}
for (const [key, value] of [['host', DB_HOST], ['port', DB_PORT], ['user', DB_USER], ['password', DB_PASSWORD], ['schema', DB_SCHEMA]]) {
rejectPlaceholder(key, value)
}
if (!/^\d+$/.test(DB_PORT) || Number(DB_PORT) <= 0 || Number(DB_PORT) > 65535) {
console.error(`[setup-test-db] database.port 非法: ${DB_PORT}(必须是 1..65535 的整数)`)
process.exit(1)
}
if (String(DB_SCHEMA).trim() === '') {
console.error('[setup-test-db] database.schema 未填')
process.exit(1)
}
function quoteMySqlIdent(value) {
return '`' + String(value).replaceAll('`', '``') + '`'
}
const DB_SCHEMA_SQL = quoteMySqlIdent(DB_SCHEMA)
console.log(`[setup-test-db] 即将 DROP + CREATE ${DB_SCHEMA_SQL} on ${DB_HOST}:${DB_PORT}`)
const sql =
`DROP DATABASE IF EXISTS ${DB_SCHEMA_SQL}; ` +
`CREATE DATABASE ${DB_SCHEMA_SQL} CHARACTER SET utf8 COLLATE utf8_general_ci;`
const mysqlArgs = [
`--host=${DB_HOST}`,
`--port=${DB_PORT}`,
`--user=${DB_USER}`,
`--password=${DB_PASSWORD}`,
'-e',
sql,
]
const res = spawnSync('mysql', mysqlArgs, { stdio: 'inherit' })
if (res.error) {
console.error(`[setup-test-db] FATAL: 无法执行 mysql(请确认其在 PATH 中): ${res.error.message}`)
process.exit(1)
}
if (res.status !== 0) {
console.error(`[setup-test-db] FAIL: mysql exit=${res.status}`)
process.exit(res.status === null ? 1 : res.status)
}
console.log('[setup-test-db] done — schema will be applied by Flyway when Spring Boot starts')