原来只有 `apps/web` 在 Prettier 下(配置在 `apps/web/.prettierrc.toml`、脚本在 web 的 package.json),后端和契约从来没格式化过 —— 手写在 100 列上下,`db/schema.ts` 还是 drizzle-kit pull 留下的 tab 缩进。两套口径分叉久了,跨端改一处就得记着「这边 什么风格」。 - 配置搬到根目录 `.prettierrc.toml`,内容不变(`semi=false`,其余全默认, printWidth 80 —— 和前端已有的格式一致,不另立一套宽度); - 脚本统一成根目录 `bun run fmt`,覆盖 `apps/*/src`、`apps/web/tests` 和两个构建 配置;web 自己那份 `fmt` 和重复的 prettier 依赖删掉; - `.prettierignore` 挡掉两类不该碰的:drizzle-kit 生成的 `src/db/meta/` 结构快照 (它是 db:generate 的比对输入,只该由 drizzle-kit 写)、unplugin 每次 dev 都会 重写的 `auto-imports.d.ts` / `components.d.ts`; - 全量跑了一遍。纯格式,无行为改动:api typecheck / check:routes / check:ast、 前端 type-check 全过,起 api 打了接口确认正常。前端这 39 个文件的小改动是 prettier 版本漂移(类型断言的换行口径变了),不是新配置带来的。 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
1 parent
e600fd24cf
commit
ed56a209ea
122 files changed
+10557
-4843
No files matched your search
@@ -42,8 +42,12 @@ const missing: Array<{ language: string; target: string; node: string }> = []
|
||||
for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||||
const wasmPath = WASM_BY_LANGUAGE[language]
|
||||
if (!wasmPath) {
|
||||
console.log(`⚠ ${language} 在 AST_NODE_TARGETS_BY_LANGUAGE 里,但这个脚本没有它的语法 wasm`)
|
||||
console.log(` 加语言时记得同步 WASM_BY_LANGUAGE 和 judge/ast.ts 的 loadLanguage`)
|
||||
console.log(
|
||||
`⚠ ${language} 在 AST_NODE_TARGETS_BY_LANGUAGE 里,但这个脚本没有它的语法 wasm`,
|
||||
)
|
||||
console.log(
|
||||
` 加语言时记得同步 WASM_BY_LANGUAGE 和 judge/ast.ts 的 loadLanguage`,
|
||||
)
|
||||
process.exit(2)
|
||||
}
|
||||
const loaded = await Language.load(wasmPath)
|
||||
@@ -55,7 +59,8 @@ for (const [language, table] of Object.entries(AST_NODE_TARGETS_BY_LANGUAGE)) {
|
||||
}
|
||||
for (const [target, entry] of Object.entries(table)) {
|
||||
checked++
|
||||
if (!declared.has(entry.node)) missing.push({ language, target, node: entry.node })
|
||||
if (!declared.has(entry.node))
|
||||
missing.push({ language, target, node: entry.node })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -66,7 +71,11 @@ if (missing.length === 0) {
|
||||
}
|
||||
for (const { language, target, node } of missing) {
|
||||
console.log(`\n⚠ ${language} 的 ${target} → "${node}"`)
|
||||
console.log(` 这个节点类型在语法里不存在,规则永远失败(或永远通过),且不报错`)
|
||||
console.log(` 改法:在 packages/contract/src/problem.ts 把它的 node 改成语法里真实的名字`)
|
||||
console.log(
|
||||
` 这个节点类型在语法里不存在,规则永远失败(或永远通过),且不报错`,
|
||||
)
|
||||
console.log(
|
||||
` 改法:在 packages/contract/src/problem.ts 把它的 node 改成语法里真实的名字`,
|
||||
)
|
||||
}
|
||||
process.exit(1)
|
||||
@@ -63,7 +63,9 @@ export function shadows(pattern: string, target: string) {
|
||||
function collect(): Route[] {
|
||||
const routerFile = new Map<string, string>()
|
||||
for (const file of walk(SRC)) {
|
||||
for (const m of readFileSync(file, "utf8").matchAll(/export const (\w+) = new Hono/g)) {
|
||||
for (const m of readFileSync(file, "utf8").matchAll(
|
||||
/export const (\w+) = new Hono/g,
|
||||
)) {
|
||||
routerFile.set(m[1]!, file)
|
||||
}
|
||||
}
|
||||
@@ -72,7 +74,10 @@ function collect(): Route[] {
|
||||
const file = routerFile.get(router)
|
||||
if (!file) return []
|
||||
const text = readFileSync(file, "utf8")
|
||||
const pattern = new RegExp(`${router}\\.(get|post|put|delete|patch)\\(\\s*"([^"]+)"`, "g")
|
||||
const pattern = new RegExp(
|
||||
`${router}\\.(get|post|put|delete|patch)\\(\\s*"([^"]+)"`,
|
||||
"g",
|
||||
)
|
||||
return [...text.matchAll(pattern)].map((m) => ({
|
||||
method: m[1]!.toUpperCase(),
|
||||
path: (prefix + m[2]!).replace(/\/+/g, "/").replace(/\/$/, "") || "/",
|
||||
@@ -83,10 +88,14 @@ function collect(): Route[] {
|
||||
// 挂载顺序就是匹配顺序,所以必须按 index.ts 里出现的先后来摊平
|
||||
const index = readFileSync(join(SRC, "index.ts"), "utf8")
|
||||
const adminIndex = readFileSync(join(SRC, "routes/admin/index.ts"), "utf8")
|
||||
const adminMounts = [...adminIndex.matchAll(/\.route\(\s*"([^"]*)"\s*,\s*(\w+)\s*\)/g)]
|
||||
const adminMounts = [
|
||||
...adminIndex.matchAll(/\.route\(\s*"([^"]*)"\s*,\s*(\w+)\s*\)/g),
|
||||
]
|
||||
|
||||
const all: Route[] = []
|
||||
for (const m of index.matchAll(/app\.route\(\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g)) {
|
||||
for (const m of index.matchAll(
|
||||
/app\.route\(\s*"([^"]+)"\s*,\s*(\w+)\s*\)/g,
|
||||
)) {
|
||||
const [, prefix, router] = m
|
||||
if (router === "adminRoutes") {
|
||||
for (const a of adminMounts) all.push(...routesOf(a[2]!, prefix! + a[1]!))
|
||||
@@ -102,7 +111,8 @@ const hits: [Route, Route][] = []
|
||||
for (let i = 0; i < routes.length; i++) {
|
||||
for (let j = i + 1; j < routes.length; j++) {
|
||||
if (routes[i]!.method !== routes[j]!.method) continue
|
||||
if (shadows(routes[i]!.path, routes[j]!.path)) hits.push([routes[i]!, routes[j]!])
|
||||
if (shadows(routes[i]!.path, routes[j]!.path))
|
||||
hits.push([routes[i]!, routes[j]!])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -113,7 +123,9 @@ if (hits.length === 0) {
|
||||
}
|
||||
for (const [first, second] of hits) {
|
||||
console.log(`\n⚠ ${second.method} ${second.path} (${second.file})`)
|
||||
console.log(` 进不去:被先注册的 ${first.method} ${first.path} 吃掉(${first.file})`)
|
||||
console.log(
|
||||
` 进不去:被先注册的 ${first.method} ${first.path} 吃掉(${first.file})`,
|
||||
)
|
||||
console.log(` 改法:把它挪到那条之前注册,或换一个不同形的路径`)
|
||||
}
|
||||
process.exit(1)
|
||||
+159
-56
@@ -3,7 +3,11 @@ import { eq, sql } from "drizzle-orm"
|
||||
import { db, schema } from "../db"
|
||||
import { JudgeStatus, isAccepted } from "../judge/status"
|
||||
import { objectValue } from "../routes/helpers"
|
||||
import { metaAchievements, refreshUnlockedCount, rescanAchievement } from "../services/achievements"
|
||||
import {
|
||||
metaAchievements,
|
||||
refreshUnlockedCount,
|
||||
rescanAchievement,
|
||||
} from "../services/achievements"
|
||||
|
||||
/**
|
||||
* 把反范式的计数列重算回与 submission 表一致。
|
||||
@@ -50,7 +54,11 @@ type ProblemExpected = {
|
||||
* contestId,只有 user_profile 那一段才分。
|
||||
*/
|
||||
async function expectedProblems() {
|
||||
const rows = await db.execute<{ problem_id: number; result: number; n: number }>(sql`
|
||||
const rows = await db.execute<{
|
||||
problem_id: number
|
||||
result: number
|
||||
n: number
|
||||
}>(sql`
|
||||
select problem_id, result, count(*)::int as n
|
||||
from submission
|
||||
where result not in (${UNJUDGED[0]}, ${UNJUDGED[1]})
|
||||
@@ -93,7 +101,11 @@ type ProfileExpected = {
|
||||
* 所以按 create_time 算,不额外记判完时间。
|
||||
*/
|
||||
async function expectedProfiles() {
|
||||
const totals = await db.execute<{ user_id: number; submissions: number; accepted: number }>(sql`
|
||||
const totals = await db.execute<{
|
||||
user_id: number
|
||||
submissions: number
|
||||
accepted: number
|
||||
}>(sql`
|
||||
select user_id,
|
||||
count(*)::int as submissions,
|
||||
count(distinct problem_id) filter (where result in (${JudgeStatus.ACCEPTED}, ${JudgeStatus.AST_CHECK_FAILED}))::int as accepted
|
||||
@@ -122,7 +134,11 @@ async function expectedProfiles() {
|
||||
`)
|
||||
|
||||
const expected = new Map<number, ProfileExpected>()
|
||||
const blank = (): ProfileExpected => ({ submissionNumber: 0, acceptedNumber: 0, status: {} })
|
||||
const blank = (): ProfileExpected => ({
|
||||
submissionNumber: 0,
|
||||
acceptedNumber: 0,
|
||||
status: {},
|
||||
})
|
||||
for (const row of totals) {
|
||||
const current = expected.get(row.user_id) ?? blank()
|
||||
current.submissionNumber = row.submissions
|
||||
@@ -146,7 +162,9 @@ async function expectedProfiles() {
|
||||
function stable(value: unknown): string {
|
||||
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`
|
||||
if (value && typeof value === "object") {
|
||||
const entries = Object.entries(value as Record<string, unknown>).sort(([a], [b]) => (a < b ? -1 : 1))
|
||||
const entries = Object.entries(value as Record<string, unknown>).sort(
|
||||
([a], [b]) => (a < b ? -1 : 1),
|
||||
)
|
||||
return `{${entries.map(([k, v]) => `${JSON.stringify(k)}:${stable(v)}`).join(",")}}`
|
||||
}
|
||||
return JSON.stringify(value) ?? "null"
|
||||
@@ -156,7 +174,10 @@ type Diff = { label: string; field: string; before: unknown; after: unknown }
|
||||
type Plan = {
|
||||
diffs: Diff[]
|
||||
problemFixes: { id: number; value: ProblemExpected }[]
|
||||
profileFixes: { id: number; value: ProfileExpected & { merged: Record<string, unknown> } }[]
|
||||
profileFixes: {
|
||||
id: number
|
||||
value: ProfileExpected & { merged: Record<string, unknown> }
|
||||
}[]
|
||||
/** achievement_unlocked_count 不对的用户 */
|
||||
unlockedCountFixes: number[]
|
||||
/** 按正确计数已达标、却没持有元成就的 (用户, 元成就) */
|
||||
@@ -183,22 +204,43 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
metaAchievements(),
|
||||
])
|
||||
const holders = metas.length
|
||||
? await db.select({ userId: schema.userAchievement.userId, achievementId: schema.userAchievement.achievementId })
|
||||
.from(schema.userAchievement)
|
||||
.where(sql`${schema.userAchievement.achievementId} in ${metas.map((meta) => meta.id)}`)
|
||||
? await db
|
||||
.select({
|
||||
userId: schema.userAchievement.userId,
|
||||
achievementId: schema.userAchievement.achievementId,
|
||||
})
|
||||
.from(schema.userAchievement)
|
||||
.where(
|
||||
sql`${schema.userAchievement.achievementId} in ${metas.map((meta) => meta.id)}`,
|
||||
)
|
||||
: []
|
||||
const held = new Set(holders.map((row) => `${row.userId}:${row.achievementId}`))
|
||||
const held = new Set(
|
||||
holders.map((row) => `${row.userId}:${row.achievementId}`),
|
||||
)
|
||||
|
||||
for (const row of rows) {
|
||||
const label = `用户 ${row.user_id}`
|
||||
if (row.counter !== row.actual) {
|
||||
plan.diffs.push({ label, field: "achievement_unlocked_count", before: row.counter ?? null, after: row.actual })
|
||||
plan.diffs.push({
|
||||
label,
|
||||
field: "achievement_unlocked_count",
|
||||
before: row.counter ?? null,
|
||||
after: row.actual,
|
||||
})
|
||||
plan.unlockedCountFixes.push(row.user_id)
|
||||
}
|
||||
for (const meta of metas) {
|
||||
const met = meta.operator === "gte" ? row.actual >= meta.threshold : row.actual <= meta.threshold
|
||||
const met =
|
||||
meta.operator === "gte"
|
||||
? row.actual >= meta.threshold
|
||||
: row.actual <= meta.threshold
|
||||
if (!met || held.has(`${row.user_id}:${meta.id}`)) continue
|
||||
plan.diffs.push({ label, field: `成就「${meta.name}」`, before: "未发", after: "补发" })
|
||||
plan.diffs.push({
|
||||
label,
|
||||
field: `成就「${meta.name}」`,
|
||||
before: "未发",
|
||||
after: "补发",
|
||||
})
|
||||
plan.metaGrants.push({ userId: row.user_id, achievementId: meta.id })
|
||||
}
|
||||
}
|
||||
@@ -206,26 +248,37 @@ async function unlockedCountPlan(plan: Plan) {
|
||||
|
||||
/** 只算差异,不写库。预演和落库后的复核共用它 —— 两边口径必须是同一份代码 */
|
||||
async function computePlan(): Promise<Plan> {
|
||||
const [problems, profiles, expectedProblem, expectedProfile] = await Promise.all([
|
||||
db.select({
|
||||
id: schema.problem.id,
|
||||
displayId: schema.problem.displayId,
|
||||
submissionNumber: schema.problem.submissionNumber,
|
||||
acceptedNumber: schema.problem.acceptedNumber,
|
||||
statisticInfo: schema.problem.statisticInfo,
|
||||
}).from(schema.problem),
|
||||
db.select({
|
||||
id: schema.userProfile.id,
|
||||
userId: schema.userProfile.userId,
|
||||
submissionNumber: schema.userProfile.submissionNumber,
|
||||
acceptedNumber: schema.userProfile.acceptedNumber,
|
||||
acmProblemsStatus: schema.userProfile.acmProblemsStatus,
|
||||
}).from(schema.userProfile),
|
||||
expectedProblems(),
|
||||
expectedProfiles(),
|
||||
])
|
||||
const [problems, profiles, expectedProblem, expectedProfile] =
|
||||
await Promise.all([
|
||||
db
|
||||
.select({
|
||||
id: schema.problem.id,
|
||||
displayId: schema.problem.displayId,
|
||||
submissionNumber: schema.problem.submissionNumber,
|
||||
acceptedNumber: schema.problem.acceptedNumber,
|
||||
statisticInfo: schema.problem.statisticInfo,
|
||||
})
|
||||
.from(schema.problem),
|
||||
db
|
||||
.select({
|
||||
id: schema.userProfile.id,
|
||||
userId: schema.userProfile.userId,
|
||||
submissionNumber: schema.userProfile.submissionNumber,
|
||||
acceptedNumber: schema.userProfile.acceptedNumber,
|
||||
acmProblemsStatus: schema.userProfile.acmProblemsStatus,
|
||||
})
|
||||
.from(schema.userProfile),
|
||||
expectedProblems(),
|
||||
expectedProfiles(),
|
||||
])
|
||||
|
||||
const plan: Plan = { diffs: [], problemFixes: [], profileFixes: [], unlockedCountFixes: [], metaGrants: [] }
|
||||
const plan: Plan = {
|
||||
diffs: [],
|
||||
problemFixes: [],
|
||||
profileFixes: [],
|
||||
unlockedCountFixes: [],
|
||||
metaGrants: [],
|
||||
}
|
||||
|
||||
for (const problem of problems) {
|
||||
const want = expectedProblem.get(problem.id) ?? {
|
||||
@@ -236,13 +289,30 @@ async function computePlan(): Promise<Plan> {
|
||||
const label = `题目 ${problem.displayId}(id=${problem.id})`
|
||||
const rows: Diff[] = []
|
||||
if (problem.submissionNumber !== want.submissionNumber) {
|
||||
rows.push({ label, field: "submission_number", before: problem.submissionNumber, after: want.submissionNumber })
|
||||
rows.push({
|
||||
label,
|
||||
field: "submission_number",
|
||||
before: problem.submissionNumber,
|
||||
after: want.submissionNumber,
|
||||
})
|
||||
}
|
||||
if (problem.acceptedNumber !== want.acceptedNumber) {
|
||||
rows.push({ label, field: "accepted_number", before: problem.acceptedNumber, after: want.acceptedNumber })
|
||||
rows.push({
|
||||
label,
|
||||
field: "accepted_number",
|
||||
before: problem.acceptedNumber,
|
||||
after: want.acceptedNumber,
|
||||
})
|
||||
}
|
||||
if (stable(objectValue(problem.statisticInfo)) !== stable(want.statisticInfo)) {
|
||||
rows.push({ label, field: "statistic_info", before: problem.statisticInfo, after: want.statisticInfo })
|
||||
if (
|
||||
stable(objectValue(problem.statisticInfo)) !== stable(want.statisticInfo)
|
||||
) {
|
||||
rows.push({
|
||||
label,
|
||||
field: "statistic_info",
|
||||
before: problem.statisticInfo,
|
||||
after: want.statisticInfo,
|
||||
})
|
||||
}
|
||||
if (rows.length) {
|
||||
plan.diffs.push(...rows)
|
||||
@@ -262,19 +332,38 @@ async function computePlan(): Promise<Plan> {
|
||||
const merged: Record<string, unknown> = { ...existing }
|
||||
delete merged.problems
|
||||
delete merged.contest_problems
|
||||
for (const [bucket, value] of Object.entries(want.status)) merged[bucket] = value
|
||||
for (const [bucket, value] of Object.entries(want.status))
|
||||
merged[bucket] = value
|
||||
|
||||
const label = `用户 ${profile.userId}`
|
||||
const rows: Diff[] = []
|
||||
if (profile.submissionNumber !== want.submissionNumber) {
|
||||
rows.push({ label, field: "submission_number", before: profile.submissionNumber, after: want.submissionNumber })
|
||||
rows.push({
|
||||
label,
|
||||
field: "submission_number",
|
||||
before: profile.submissionNumber,
|
||||
after: want.submissionNumber,
|
||||
})
|
||||
}
|
||||
if (profile.acceptedNumber !== want.acceptedNumber) {
|
||||
rows.push({ label, field: "accepted_number", before: profile.acceptedNumber, after: want.acceptedNumber })
|
||||
rows.push({
|
||||
label,
|
||||
field: "accepted_number",
|
||||
before: profile.acceptedNumber,
|
||||
after: want.acceptedNumber,
|
||||
})
|
||||
}
|
||||
if (stable(existing) !== stable(merged)) {
|
||||
const keys = new Set([...Object.keys(objectValue(existing.problems)), ...Object.keys(want.status.problems ?? {})])
|
||||
rows.push({ label, field: "acm_problems_status", before: `${Object.keys(objectValue(existing.problems)).length} 题`, after: `${keys.size} 题(含比赛桶重建)` })
|
||||
const keys = new Set([
|
||||
...Object.keys(objectValue(existing.problems)),
|
||||
...Object.keys(want.status.problems ?? {}),
|
||||
])
|
||||
rows.push({
|
||||
label,
|
||||
field: "acm_problems_status",
|
||||
before: `${Object.keys(objectValue(existing.problems)).length} 题`,
|
||||
after: `${keys.size} 题(含比赛桶重建)`,
|
||||
})
|
||||
}
|
||||
if (rows.length) {
|
||||
plan.diffs.push(...rows)
|
||||
@@ -286,11 +375,16 @@ async function computePlan(): Promise<Plan> {
|
||||
}
|
||||
|
||||
function report(plan: Plan) {
|
||||
console.log(`发现 ${plan.diffs.length} 处不一致(题目 ${plan.problemFixes.length} 道 / 用户 ${plan.profileFixes.length} 人 / 已解锁数 ${plan.unlockedCountFixes.length} 人 / 元成就补发 ${plan.metaGrants.length} 条):`)
|
||||
console.log(
|
||||
`发现 ${plan.diffs.length} 处不一致(题目 ${plan.problemFixes.length} 道 / 用户 ${plan.profileFixes.length} 人 / 已解锁数 ${plan.unlockedCountFixes.length} 人 / 元成就补发 ${plan.metaGrants.length} 条):`,
|
||||
)
|
||||
for (const diff of plan.diffs.slice(0, 40)) {
|
||||
console.log(` ${diff.label} ${diff.field}: ${JSON.stringify(diff.before)} → ${JSON.stringify(diff.after)}`)
|
||||
console.log(
|
||||
` ${diff.label} ${diff.field}: ${JSON.stringify(diff.before)} → ${JSON.stringify(diff.after)}`,
|
||||
)
|
||||
}
|
||||
if (plan.diffs.length > 40) console.log(` ……另有 ${plan.diffs.length - 40} 处`)
|
||||
if (plan.diffs.length > 40)
|
||||
console.log(` ……另有 ${plan.diffs.length - 40} 处`)
|
||||
}
|
||||
|
||||
/** 退出码:0 = 一致或预演正常,1 = 落库后复核仍有差异 */
|
||||
@@ -309,27 +403,36 @@ export async function recount(options: { apply: boolean }) {
|
||||
|
||||
await db.transaction(async (tx) => {
|
||||
for (const fix of plan.problemFixes) {
|
||||
await tx.update(schema.problem).set({
|
||||
submissionNumber: fix.value.submissionNumber,
|
||||
acceptedNumber: fix.value.acceptedNumber,
|
||||
statisticInfo: fix.value.statisticInfo,
|
||||
}).where(eq(schema.problem.id, fix.id))
|
||||
await tx
|
||||
.update(schema.problem)
|
||||
.set({
|
||||
submissionNumber: fix.value.submissionNumber,
|
||||
acceptedNumber: fix.value.acceptedNumber,
|
||||
statisticInfo: fix.value.statisticInfo,
|
||||
})
|
||||
.where(eq(schema.problem.id, fix.id))
|
||||
}
|
||||
for (const fix of plan.profileFixes) {
|
||||
await tx.update(schema.userProfile).set({
|
||||
submissionNumber: fix.value.submissionNumber,
|
||||
acceptedNumber: fix.value.acceptedNumber,
|
||||
acmProblemsStatus: fix.value.merged,
|
||||
}).where(eq(schema.userProfile.id, fix.id))
|
||||
await tx
|
||||
.update(schema.userProfile)
|
||||
.set({
|
||||
submissionNumber: fix.value.submissionNumber,
|
||||
acceptedNumber: fix.value.acceptedNumber,
|
||||
acmProblemsStatus: fix.value.merged,
|
||||
})
|
||||
.where(eq(schema.userProfile.id, fix.id))
|
||||
}
|
||||
})
|
||||
// 先改计数、再补发:rescanAchievement 读的是 metrics 里的计数。
|
||||
// 补发幂等(唯一键 + 冲突忽略),重跑不会重复发
|
||||
const recounted = await refreshUnlockedCount(plan.unlockedCountFixes)
|
||||
if (plan.metaGrants.length) {
|
||||
for (const meta of await metaAchievements()) await rescanAchievement(meta.id)
|
||||
for (const meta of await metaAchievements())
|
||||
await rescanAchievement(meta.id)
|
||||
}
|
||||
console.log(`\n已订正题目 ${plan.problemFixes.length} 道、用户 ${plan.profileFixes.length} 人、已解锁数 ${recounted.length} 人,补发元成就 ${plan.metaGrants.length} 条,复核中……`)
|
||||
console.log(
|
||||
`\n已订正题目 ${plan.problemFixes.length} 道、用户 ${plan.profileFixes.length} 人、已解锁数 ${recounted.length} 人,补发元成就 ${plan.metaGrants.length} 条,复核中……`,
|
||||
)
|
||||
|
||||
// 复核跑的是同一份 computePlan。这里还剩差异说明口径本身有问题(不是数据脏),
|
||||
// 必须让部署脚本看见非零退出码,而不是打一行字了事。
|
||||
|
||||
@@ -8,7 +8,9 @@ import { db, schema } from "../db"
|
||||
* raw_password,其中一个还是超管。对着生产库跑一次就是把超管密码改掉,
|
||||
* 所以这里按 DATABASE_URL 的主机名拦一道,需要绕过时显式设 OJ2_SEED_FORCE=true。
|
||||
*/
|
||||
const url = process.env.DATABASE_URL ?? "postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge"
|
||||
const url =
|
||||
process.env.DATABASE_URL ??
|
||||
"postgres://onlinejudge:onlinejudge@localhost:5433/onlinejudge"
|
||||
const host = (() => {
|
||||
try {
|
||||
return new URL(url).hostname
|
||||
@@ -93,7 +95,9 @@ async function seed(account: SeedAccount) {
|
||||
})
|
||||
}
|
||||
|
||||
console.log(` ${account.adminType.padEnd(13)} ${user.username} / ${account.password}`)
|
||||
console.log(
|
||||
` ${account.adminType.padEnd(13)} ${user.username} / ${account.password}`,
|
||||
)
|
||||
}
|
||||
|
||||
console.log("Seeded development logins:")
|
||||
|
||||
Reference in new issue
Block a user