文档对齐 React 技术栈,并支持渐进式数据库迁移
将 README 与部署文档从仅 init 改为 001 基线 + 增量迁移约定,并补充 create/status 脚手架与序号校验。
This commit is contained in:
@@ -1,3 +1,14 @@
|
||||
/**
|
||||
* 数据库迁移入口。
|
||||
*
|
||||
* 策略:
|
||||
* - 001_init.sql:空库基线,建立完整结构
|
||||
* - 002_xxx.sql 起:渐进增量迁移,禁止改已应用历史文件
|
||||
* - 启动时自动 up;也可用 npm run db:migrate 手动执行
|
||||
* - 新建迁移:npm run db:migrate:create -- <name>
|
||||
* - 查看状态:npm run db:migrate:status
|
||||
*/
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import process from 'node:process'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
@@ -12,10 +23,14 @@ const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
|
||||
const MIGRATIONS_DIR = path.join(CURRENT_DIR, 'migrations')
|
||||
const MIGRATIONS_TABLE = 'schema_migrations'
|
||||
const SHORT_NUMERIC_PREFIX_MESSAGE = /^Can't determine timestamp for \d+$/
|
||||
const MIGRATION_FILE_PATTERN = /^(\d{3})_.+\.sql$/i
|
||||
|
||||
export async function runDatabaseMigrations() {
|
||||
assertMigrationFilesOrdered()
|
||||
await ensureNodePgMigrateMetadataCompatibility()
|
||||
|
||||
console.info(`[db:migrate] 迁移目录: ${MIGRATIONS_DIR}`)
|
||||
|
||||
await runner({
|
||||
databaseUrl: createMigrationDatabaseConfig(),
|
||||
dir: MIGRATIONS_DIR,
|
||||
@@ -45,6 +60,44 @@ export async function runDatabaseMigrations() {
|
||||
await syncLegacyMigrationColumns()
|
||||
}
|
||||
|
||||
function assertMigrationFilesOrdered() {
|
||||
if (!fs.existsSync(MIGRATIONS_DIR)) {
|
||||
throw new Error(`迁移目录不存在: ${MIGRATIONS_DIR}`)
|
||||
}
|
||||
|
||||
const files = fs
|
||||
.readdirSync(MIGRATIONS_DIR)
|
||||
.filter((name) => name.endsWith('.sql'))
|
||||
.sort((a, b) => a.localeCompare(b, 'en'))
|
||||
|
||||
if (files.length === 0) {
|
||||
throw new Error(`迁移目录为空: ${MIGRATIONS_DIR}`)
|
||||
}
|
||||
|
||||
const invalid = files.filter((name) => !MIGRATION_FILE_PATTERN.test(name))
|
||||
if (invalid.length > 0) {
|
||||
throw new Error(
|
||||
`迁移文件命名必须为 NNN_name.sql(三位序号),非法文件: ${invalid.join(', ')}`,
|
||||
)
|
||||
}
|
||||
|
||||
const versions = files.map((name) => Number(name.slice(0, 3)))
|
||||
for (let index = 1; index < versions.length; index += 1) {
|
||||
if (versions[index] < versions[index - 1]) {
|
||||
throw new Error(`迁移序号乱序: ${files[index - 1]} 之后出现 ${files[index]}`)
|
||||
}
|
||||
if (versions[index] === versions[index - 1]) {
|
||||
throw new Error(`迁移序号重复: ${files[index - 1]} 与 ${files[index]}`)
|
||||
}
|
||||
}
|
||||
|
||||
if (files[0] !== '001_init.sql') {
|
||||
console.warn(
|
||||
`[db:migrate] 警告: 首个迁移不是 001_init.sql(当前为 ${files[0]}),请确认是否刻意调整基线`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureNodePgMigrateMetadataCompatibility() {
|
||||
await query(`
|
||||
CREATE TABLE IF NOT EXISTS ${MIGRATIONS_TABLE} (
|
||||
|
||||
@@ -1,5 +1,10 @@
|
||||
-- 初始化完整业务库结构。
|
||||
-- 本项目当前不维护历史测试库的逐步升级链路;新环境只需要执行这一份 init 迁移。
|
||||
-- 001_init.sql —— 空库基线迁移(完整业务库结构)。
|
||||
--
|
||||
-- 约定:
|
||||
-- 1. 仅在空库首次迁移时执行本文件,建立全量基线结构。
|
||||
-- 2. 后续结构变更不要改本文件,改为新增 002_xxx.sql / 003_xxx.sql 等渐进迁移。
|
||||
-- 3. 新环境按文件名顺序执行:001_init → 后续增量;已有环境只应用尚未记录的增量。
|
||||
-- 4. 已应用到任何共享/生产环境的迁移文件禁止修改内容。
|
||||
|
||||
CREATE EXTENSION IF NOT EXISTS pgcrypto;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user