Files
yml2213 1155b3c608 文档对齐 React 技术栈,并支持渐进式数据库迁移
将 README 与部署文档从仅 init 改为 001 基线 + 增量迁移约定,并补充 create/status 脚手架与序号校验。
2026-07-10 12:36:36 +08:00

117 lines
3.5 KiB
TypeScript
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.
/**
* 创建下一份渐进式数据库迁移文件。
*
* 用法:
* npm run db:migrate:create -- add_order_note
* npm run db:migrate:create -- "add order note"
*
* 规则:
* - 001_init.sql 是基线,只用于空库首次建表
* - 后续结构变更一律新增 002_xxx.sql / 003_xxx.sql ...
* - 已应用到任何环境的迁移文件禁止修改内容
*/
import fs from 'node:fs'
import path from 'node:path'
import process from 'node:process'
import { fileURLToPath } from 'node:url'
const CURRENT_DIR = path.dirname(fileURLToPath(import.meta.url))
const MIGRATIONS_DIR = path.join(CURRENT_DIR, '../src/db/migrations')
const MIGRATION_FILE_PATTERN = /^(\d{3})_(.+)\.sql$/i
function slugify(input: string): string {
return input
.trim()
.toLowerCase()
.replace(/[^a-z0-9]+/g, '_')
.replace(/^_+|_+$/g, '')
.replace(/_+/g, '_')
}
function listMigrationFiles(): string[] {
if (!fs.existsSync(MIGRATIONS_DIR)) {
throw new Error(`迁移目录不存在: ${MIGRATIONS_DIR}`)
}
return fs
.readdirSync(MIGRATIONS_DIR)
.filter((name) => MIGRATION_FILE_PATTERN.test(name))
.sort((a, b) => a.localeCompare(b, 'en'))
}
function resolveNextVersion(files: string[]): number {
let maxVersion = 0
for (const file of files) {
const match = file.match(MIGRATION_FILE_PATTERN)
if (!match) {
continue
}
maxVersion = Math.max(maxVersion, Number(match[1]))
}
return maxVersion + 1
}
function buildTemplate(version: string, name: string): string {
const stamp = new Date().toISOString()
return `-- ${version}_${name}.sql
-- 创建时间: ${stamp}
-- 说明: TODO 填写本次结构变更目的
--
-- 渐进迁移约定:
-- 1. 只写增量 SQLALTER / CREATE INDEX / 数据回填等)
-- 2. 禁止修改已经提交并应用过的历史迁移文件
-- 3. 空库会按文件名顺序执行 001_init.sql → 后续增量迁移
-- 4. 线上/开发库启动时自动应用尚未记录在 schema_migrations 中的文件
-- 在此编写增量 SQL
`
}
function main() {
const rawName = process.argv.slice(2).join(' ').trim()
if (!rawName || rawName.startsWith('-')) {
console.error('用法: npm run db:migrate:create -- <migration_name>')
console.error('示例: npm run db:migrate:create -- add_task_priority')
process.exitCode = 1
return
}
const name = slugify(rawName)
if (!name) {
console.error('迁移名称无效,请使用英文/数字/下划线描述,例如 add_task_priority')
process.exitCode = 1
return
}
const existing = listMigrationFiles()
const nextVersion = resolveNextVersion(existing)
if (nextVersion > 999) {
console.error('迁移序号已超过 999,请调整命名策略')
process.exitCode = 1
return
}
const version = String(nextVersion).padStart(3, '0')
const fileName = `${version}_${name}.sql`
const filePath = path.join(MIGRATIONS_DIR, fileName)
if (fs.existsSync(filePath)) {
console.error(`迁移文件已存在: ${fileName}`)
process.exitCode = 1
return
}
const conflict = existing.find((item) => item.startsWith(`${version}_`))
if (conflict) {
console.error(`迁移序号冲突: ${version} 已被 ${conflict} 占用`)
process.exitCode = 1
return
}
fs.writeFileSync(filePath, buildTemplate(version, name), 'utf8')
console.info(`[db:migrate:create] 已创建 ${path.relative(process.cwd(), filePath)}`)
console.info('[db:migrate:create] 编辑 SQL 后提交;后端启动或 npm run db:migrate 会自动应用')
}
main()