scripts-drop-test-db-template.mjs
4.57 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
#!/usr/bin/env node
// scripts/drop-test-db.mjs —— 删除一次性测试副本库(跑完收尾;源库绝不被删)。
//
// 与 scripts/setup-test-db.mjs 同一命名约定:
// SOURCE = config-vars.yaml database.schema(真实持久库,本脚本硬拒删除)。
// COPY = ERP_TEST_DB_SCHEMA(env) > .tmp/erp-lane-db(marker) > `<source>__test`(串行缺省)。
// 由 test.mjs / seed / 行为门 runner 的 finally 调用(做完测试直接删副本)。幂等:副本不存在也返回 0。
// 纯 mysql CLI(spawnSync),零 npm 依赖。
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 + 标量;与 scripts/setup-test-db.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(`[drop-test-db] config-vars.yaml 不存在(${CONFIG_FILE})`)
process.exit(1)
}
const db = parseYamlConfig(readFileSync(CONFIG_FILE, 'utf8')).database || {}
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 ?? ''
const SOURCE_SCHEMA = db.schema ?? ''
function rejectPlaceholder(key, value) {
if (typeof value === 'string' && value.includes('【人工填写')) {
console.error(`[drop-test-db] database.${key} 仍是占位,请先在 config-vars.yaml 填真实值`)
process.exit(1)
}
}
for (const [key, value] of [['host', DB_HOST], ['port', DB_PORT], ['user', DB_USER], ['password', DB_PASSWORD], ['schema', SOURCE_SCHEMA]]) {
rejectPlaceholder(key, value)
}
if (!/^\d+$/.test(DB_PORT) || Number(DB_PORT) <= 0 || Number(DB_PORT) > 65535) {
console.error(`[drop-test-db] database.port 非法: ${DB_PORT}(必须是 1..65535 的整数)`)
process.exit(1)
}
if (String(SOURCE_SCHEMA).trim() === '') {
console.error('[drop-test-db] database.schema(源库名)未填')
process.exit(1)
}
const LANE_MARKER_SCHEMA = readLaneDbMarker()
const COPY_SCHEMA = process.env.ERP_TEST_DB_SCHEMA || LANE_MARKER_SCHEMA || `${SOURCE_SCHEMA}__test`
// **安全红线**:绝不删源库。副本名与源库同名 → 拒绝(说明副本名注入出错)。
if (COPY_SCHEMA === SOURCE_SCHEMA) {
console.error(`[drop-test-db] 拒绝:待删副本名与源库名相同(${SOURCE_SCHEMA})——绝不删源库。`)
process.exit(1)
}
function quoteMySqlIdent(value) {
return '`' + String(value).replaceAll('`', '``') + '`'
}
console.log(`[drop-test-db] 删除一次性副本 ${COPY_SCHEMA} on ${DB_HOST}:${DB_PORT}(源库 ${SOURCE_SCHEMA} 不动)`)
const res = spawnSync(
'mysql',
[`--host=${DB_HOST}`, `--port=${DB_PORT}`, `--user=${DB_USER}`, `--password=${DB_PASSWORD}`, '-e', `DROP DATABASE IF EXISTS ${quoteMySqlIdent(COPY_SCHEMA)};`],
{ stdio: 'inherit' },
)
if (res.error) {
console.error(`[drop-test-db] FATAL: 无法执行 mysql(请确认其在 PATH 中): ${res.error.message}`)
process.exit(1)
}
if (res.status !== 0) {
console.error(`[drop-test-db] FAIL: mysql exit=${res.status}`)
process.exit(res.status === null ? 1 : res.status)
}
console.log('[drop-test-db] done')