ci / 质量门禁 (push) Canceled after 0s
- 建立 pnpm workspace + Turborepo 单仓、ESLint 9/Prettier/commitlint/husky 门禁 - NestJS + Fastify API:统一错误体、trace_id、Zod 管道、/health 真实依赖探活、OpenAPI 3.1 契约 - Prisma 基线:outbox_events/idempotency_keys/system_configs 与首版迁移、幂等种子 - shared 契约包:错误码、权限点、ULID、游标分页、命名/时间工具 - ui 包与 Web 壳层:Semi + Remix 图标统一出口、Cloud Blue 视觉、登录/工作台/错误页 - worker 骨架、Playwright 冒烟、Gitea CI、OpenAPI 快照与 oasdiff 回退脚本 - 补充 A0 实施报告与本地开发说明
113 lines
3.0 KiB
TypeScript
113 lines
3.0 KiB
TypeScript
// 灵犀 · 种子数据(A0)
|
||
// 原则:
|
||
// 1) 只 seed 已建表的系统配置(system_configs),不提前创建 A1+ 业务表;
|
||
// 2) 幂等:按 config_key upsert,已存在的配置不覆盖(避免冲掉人工调整值);
|
||
// 3) 超管、角色、字典、电商行业包占位等种子依赖 A1/A3/A4/B0 的建表,见 prisma/SEED.md。
|
||
import { randomBytes } from 'node:crypto';
|
||
import { existsSync } from 'node:fs';
|
||
import { resolve } from 'node:path';
|
||
|
||
import { PrismaClient } from '@prisma/client';
|
||
import { config as loadDotenv } from 'dotenv';
|
||
|
||
for (const file of ['.env.local', '.env']) {
|
||
const path = resolve(process.cwd(), file);
|
||
if (existsSync(path)) {
|
||
loadDotenv({ path });
|
||
}
|
||
}
|
||
|
||
const prisma = new PrismaClient();
|
||
|
||
const ENCODING = '0123456789ABCDEFGHJKMNPQRSTVWXYZ';
|
||
|
||
function ulid(): string {
|
||
let time = Date.now();
|
||
const timeChars = new Array<string>(10);
|
||
for (let i = 9; i >= 0; i -= 1) {
|
||
timeChars[i] = ENCODING[time % 32] ?? '0';
|
||
time = Math.floor(time / 32);
|
||
}
|
||
const random = randomBytes(16);
|
||
const randomChars = Array.from(random, (byte) => ENCODING[byte & 31] ?? '0');
|
||
return `${timeChars.join('')}${randomChars.join('')}`;
|
||
}
|
||
|
||
interface SeedConfig {
|
||
key: string;
|
||
value: string | number | boolean;
|
||
valueType: 'string' | 'number' | 'boolean';
|
||
description: string;
|
||
}
|
||
|
||
const systemConfigs: SeedConfig[] = [
|
||
{
|
||
key: 'platform.name',
|
||
value: '灵犀',
|
||
valueType: 'string',
|
||
description: '平台名称(展示用)',
|
||
},
|
||
{
|
||
key: 'platform.export.signed_url_ttl_seconds',
|
||
value: 900,
|
||
valueType: 'number',
|
||
description: '导出产物签名 URL 有效期(秒),短时效',
|
||
},
|
||
{
|
||
key: 'platform.export.archive_retention_days',
|
||
value: 3,
|
||
valueType: 'number',
|
||
description: '导出打包产物默认保留天数',
|
||
},
|
||
{
|
||
key: 'platform.upload.max_upload_mb',
|
||
value: 500,
|
||
valueType: 'number',
|
||
description: '单文件上传上限(MB)',
|
||
},
|
||
{
|
||
key: 'platform.page.default_limit',
|
||
value: 20,
|
||
valueType: 'number',
|
||
description: '列表默认每页数量',
|
||
},
|
||
{
|
||
key: 'platform.ai.cost_soft_limit_ratio',
|
||
value: 0.8,
|
||
valueType: 'number',
|
||
description: 'AI 成本软上限触发比例',
|
||
},
|
||
{
|
||
key: 'platform.ai.cost_hard_limit_ratio',
|
||
value: 1,
|
||
valueType: 'number',
|
||
description: 'AI 成本硬上限触发比例(达到即拒绝新任务)',
|
||
},
|
||
];
|
||
|
||
async function main(): Promise<void> {
|
||
for (const item of systemConfigs) {
|
||
await prisma.systemConfig.upsert({
|
||
where: { configKey: item.key },
|
||
update: {},
|
||
create: {
|
||
id: ulid(),
|
||
configKey: item.key,
|
||
configValue: item.value,
|
||
valueType: item.valueType,
|
||
description: item.description,
|
||
},
|
||
});
|
||
}
|
||
console.log(`[seed] system_configs 就绪:${String(systemConfigs.length)} 项(幂等)`);
|
||
}
|
||
|
||
main()
|
||
.catch((error: unknown) => {
|
||
console.error('[seed] 失败:', error);
|
||
process.exitCode = 1;
|
||
})
|
||
.finally(async () => {
|
||
await prisma.$disconnect();
|
||
});
|