Files
2026-08-01 23:09:49 +08:00

122 lines
3.5 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env node
/**
* 数据库迁移运行器
* 按顺序执行 migrations/ 目录下的所有 .sql 文件
* 记录已执行的迁移到 schema_migrations 表(幂等性保障)
*
* 用法:
* node migrations/_runner.js # 执行所有待执行迁移
* node migrations/_runner.js --status # 查看迁移状态
* node migrations/_runner.js --reset # 重置数据库(慎用)
*/
import { DatabaseSync } from 'node:sqlite'
import { readFileSync, readdirSync, mkdirSync, unlinkSync, existsSync } from 'node:fs'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
const __dirname = dirname(fileURLToPath(import.meta.url))
const root = join(__dirname, '..')
const dataDir = join(root, 'data')
mkdirSync(dataDir, { recursive: true })
const dbPath = join(dataDir, 'reporter-station.db')
/** 初始化数据库连接(确保 schema_migrations 表存在) */
function initDb() {
const db = new DatabaseSync(dbPath)
db.exec(`
PRAGMA journal_mode = WAL;
PRAGMA foreign_keys = ON;
CREATE TABLE IF NOT EXISTS schema_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL UNIQUE,
applied_at TEXT NOT NULL DEFAULT (datetime('now', 'localtime'))
);
`)
return db
}
const args = process.argv.slice(2)
if (args.includes('--status')) {
const db = initDb()
const applied = db.prepare('SELECT name, applied_at FROM schema_migrations ORDER BY id').all()
const allFiles = readdirSync(__dirname)
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
.sort()
console.log('\n迁移状态:')
for (const f of allFiles) {
const name = f.replace('.sql', '')
const row = applied.find(a => a.name === name)
if (row) {
console.log(` [x] ${name} (applied at ${row.applied_at})`)
} else {
console.log(` [ ] ${name}`)
}
}
db.close()
process.exit(0)
}
if (args.includes('--seed')) {
const db = initDb()
const seedFile = '002_seed_data.sql'
const sql = readFileSync(join(__dirname, seedFile), 'utf-8')
try {
db.exec(sql)
console.log(`已重新执行 seed 数据(${seedFile}`)
} catch (err) {
console.error(`seed 失败: ${err.message}`)
process.exit(1)
}
db.close()
process.exit(0)
}
if (args.includes('--reset')) {
for (const ext of ['', '-wal', '-shm']) {
const f = dbPath + ext
if (existsSync(f)) { unlinkSync(f); console.log(`已删除: ${f}`) }
}
console.log('数据库已重置,重新启动服务将自动执行迁移。')
process.exit(0)
}
// 默认:执行待执行的迁移
const db = initDb()
const applied = new Set(db.prepare('SELECT name FROM schema_migrations').all().map(r => r.name))
const allFiles = readdirSync(__dirname)
.filter(f => f.endsWith('.sql') && !f.startsWith('_'))
.sort()
let appliedCount = 0
for (const file of allFiles) {
const name = file.replace('.sql', '')
if (applied.has(name)) {
console.log(` 跳过 (已执行): ${name}`)
continue
}
const sql = readFileSync(join(__dirname, file), 'utf-8')
try {
db.exec('BEGIN')
db.exec(sql)
db.prepare('INSERT INTO schema_migrations (name) VALUES (?)').run(name)
db.exec('COMMIT')
console.log(` [x] ${name}`)
appliedCount++
} catch (err) {
db.exec('ROLLBACK')
console.error(` [!] ${name} 执行失败: ${err.message}`)
process.exit(1)
}
}
db.close()
if (appliedCount === 0) {
console.log('\n没有待执行的迁移,数据库已是最新。')
} else {
console.log(`\n成功执行 ${appliedCount} 个迁移。`)
}