import 'reflect-metadata'; import { createHash, randomUUID } from 'node:crypto'; import { closeSync, openSync, readFileSync, statSync, unlinkSync, writeFileSync, } from 'node:fs'; import { basename, resolve } from 'node:path'; import { createRequire } from 'node:module'; import { parseArgs, parseEnv } from 'node:util'; const require = createRequire(import.meta.url); const { PrismaService } = require('../dist/database/prisma.service.js'); const { WorksheetImporter } = require('../dist/imports/worksheet-import.js'); const { HttpException } = require('@nestjs/common'); let database; let reportFd; let reportPath; let reportWritten = false; let committed = false; try { const { values } = parseArgs({ options: { file: { type: 'string' }, apply: { type: 'boolean', default: false }, 'metadata-only': { type: 'boolean', default: false }, report: { type: 'string' }, }, }); if (!values.file) throw new Error('请用 --file 指定题型导出;默认预演,--apply 才写库'); const file = resolve(values.file); if (statSync(file).size > 10 * 1024 * 1024) throw new Error('题型文件不能超过 10 MiB'); const source = readFileSync(file, 'utf8'); const sourceSha256 = createHash('sha256').update(source).digest('hex'); const local = parseEnv( readFileSync(new URL('../.env', import.meta.url), 'utf8'), ); const url = new URL(process.env.DATABASE_URL ?? local.DATABASE_URL); // 元数据暂存仅限本地开发库,不能带着未就绪的预览图导入到生产。 if ( !['127.0.0.1', 'localhost'].includes(url.hostname) || url.pathname !== '/doodle_dev' ) throw new Error('题型导入工具只允许本机 doodle_dev'); if (values.report) { reportPath = resolve(values.report); reportFd = openSync(reportPath, 'wx'); } database = new PrismaService({ databaseUrl: url.toString() }); const requestId = `worksheets-import-${randomUUID()}`; const result = await new WorksheetImporter(database).importExport(source, { dryRun: !values.apply, metadataOnly: values['metadata-only'], requestId, sourceSha256, }); committed = !result.dryRun; const report = { source: basename(file), sourceSha256, requestId, ...result, }; const output = `${JSON.stringify(report, null, 4)}\n`; process.stdout.write(output); if (reportFd !== undefined) { writeFileSync(reportFd, output); reportWritten = true; } } catch (error) { const message = error instanceof HttpException ? error.message : error instanceof Error && !/(postgres|password|connection|prisma|\/\/)/i.test( error.message, ) ? error.message : '题型导入失败,请检查本地数据库配置和文件'; process.stderr.write(`${message}\n`); if (committed) process.stderr.write( '数据库事务已提交;报告保存失败,批次见上方输出及审计。\n', ); process.exitCode = 1; } finally { if (reportFd !== undefined) { closeSync(reportFd); if (!reportWritten) unlinkSync(reportPath); } await database?.onModuleDestroy(); }