fix: 分离日常发布与首次初始化(09-09-existing-deploy-seed-separation)
ci-e2e / e2e (push) Canceled after 0s
ci-minimal / check-minimal (push) Canceled after 0s

这个提交包含在:
2026-09-09 12:47:30 +08:00
父节点 81102c26cf
当前提交 b87cc55aca
共修改 17 个文件,包含 662 行新增和 11 行删除
+99
查看文件
@@ -0,0 +1,99 @@
const { spawnSync } = require("node:child_process");
const path = require("node:path");
function executeCommand(command, args) {
const result = spawnSync(command, args, {
cwd: path.resolve(__dirname, "../.."),
env: process.env,
stdio: "inherit",
});
if (result.error || result.status !== 0) {
throw new Error("deployment.init_command_failed");
}
}
async function inspectRequiredConfiguration(prisma) {
return prisma.$transaction(async (transaction) => {
await transaction.$executeRaw`SET TRANSACTION READ ONLY`;
const checks = [
{
code: "expense_types",
count: await transaction.expenseType.count({ where: { enabled: true } }),
maintenance: "管理员核对 /api/expense-types;缺失数据需另批修复",
},
{
code: "approval_rules",
count: await transaction.approvalRule.count({ where: { enabled: true } }),
maintenance: "/system/approval-rules",
},
{
code: "reimbursement_workflow",
count: await transaction.workflowDefinition.count({
where: { entity_type: "reimbursement", is_active: true },
}),
maintenance: "/system/workflow-definitions",
},
];
return checks;
}, { isolationLevel: "RepeatableRead", maxWait: 5000, timeout: 10000 });
}
async function checkRequiredConfiguration() {
const { createPrismaClient } = require("../prismaClient");
const { prisma, pool } = createPrismaClient();
try {
return await inspectRequiredConfiguration(prisma);
} finally {
try {
await prisma.$disconnect();
} finally {
await pool.end();
}
}
}
async function runInitialization({
mode,
runCommand = executeCommand,
checkConfiguration = checkRequiredConfiguration,
report = (result) => process.stdout.write(JSON.stringify(result) + "\n"),
}) {
if (mode !== "existing" && mode !== "initialize") {
throw new Error("deployment.data_mode_required");
}
report({ stage: "validate_config", mode });
await runCommand(process.execPath, ["-e", 'require("./src/config").getConfig()']);
report({ stage: "migrate", mode });
await runCommand("npm", ["run", "prisma:migrate:deploy"]);
if (mode === "initialize") {
for (const script of ["seed:config", "seed:trialrun"]) {
report({ stage: script, mode });
await runCommand("npm", ["run", script]);
}
}
report({ stage: "check_required_configuration", mode });
const checks = await checkConfiguration();
const missing = checks.filter((check) => check.count === 0);
if (missing.length) {
report({ status: "REQUIRED_CONFIGURATION_MISSING", checks: missing });
throw new Error("deployment.required_configuration_missing");
}
report({ status: "INIT_PASS", mode, checks });
}
if (require.main === module) {
runInitialization({ mode: process.env.DEPLOY_DATA_MODE }).catch((error) => {
const allowed = new Set([
"deployment.data_mode_required",
"deployment.init_command_failed",
"deployment.required_configuration_missing",
]);
process.stderr.write(JSON.stringify({
status: "INIT_FAILED",
code: allowed.has(error.message) ? error.message : "deployment.init_failed",
}) + "\n");
process.exitCode = 1;
});
}
module.exports = { runInitialization, inspectRequiredConfiguration };