662 lines
22 KiB
JavaScript
662 lines
22 KiB
JavaScript
#!/usr/bin/env node
|
|
import { createRequire } from 'node:module';
|
|
import { mkdir, writeFile } from 'node:fs/promises';
|
|
import { dirname, resolve } from 'node:path';
|
|
|
|
const FIXTURE_VERSION = 'v2.6-large-data-fixture-1';
|
|
const ID_PREFIX = 'perf-';
|
|
const BASE_DATE = new Date('2026-07-01T09:00:00.000Z');
|
|
const OUTPUT_PATH = '.tmp/perf-fixture.json';
|
|
|
|
const SIZE_PRESETS = {
|
|
small: {
|
|
products: 1,
|
|
projectsPerProduct: 2,
|
|
versionsPerProject: 2,
|
|
requirementsPerVersion: 20,
|
|
looseRequirementsPerProduct: 20,
|
|
plansPerVersion: 3,
|
|
devTasksPerRequirement: 2,
|
|
testCasesPerRequirement: 2,
|
|
bugsPerVersion: 12,
|
|
activitiesPerVersion: 16,
|
|
users: 8,
|
|
},
|
|
medium: {
|
|
products: 2,
|
|
projectsPerProduct: 8,
|
|
versionsPerProject: 4,
|
|
requirementsPerVersion: 80,
|
|
looseRequirementsPerProduct: 300,
|
|
plansPerVersion: 3,
|
|
devTasksPerRequirement: 3,
|
|
testCasesPerRequirement: 3,
|
|
bugsPerVersion: 80,
|
|
activitiesPerVersion: 120,
|
|
users: 40,
|
|
},
|
|
large: {
|
|
products: 4,
|
|
projectsPerProduct: 20,
|
|
versionsPerProject: 6,
|
|
requirementsPerVersion: 150,
|
|
looseRequirementsPerProduct: 2000,
|
|
plansPerVersion: 3,
|
|
devTasksPerRequirement: 4,
|
|
testCasesPerRequirement: 4,
|
|
bugsPerVersion: 180,
|
|
activitiesPerVersion: 300,
|
|
users: 120,
|
|
},
|
|
};
|
|
|
|
const DEV_STATUSES = ['todo', 'in_progress', 'testing', 'submitted'];
|
|
const TEST_STATUSES = ['pending', 'running', 'passed', 'failed', 'blocked'];
|
|
const BUG_STATUSES = ['open', 'fixing', 'fixed', 'verifying', 'closed'];
|
|
const BUG_SEVERITIES = ['normal', 'major', 'critical'];
|
|
const PLAN_TYPES = ['research', 'product', 'ui'];
|
|
const REQUIREMENT_STATUSES = ['adopted', 'planned', 'developing', 'testing', 'released'];
|
|
|
|
function parseArgs(argv) {
|
|
const args = {
|
|
size: 'small',
|
|
dryRun: false,
|
|
json: false,
|
|
output: OUTPUT_PATH,
|
|
};
|
|
|
|
for (let index = 0; index < argv.length; index += 1) {
|
|
const arg = argv[index];
|
|
if (arg === '--dry-run') {
|
|
args.dryRun = true;
|
|
continue;
|
|
}
|
|
if (arg === '--json') {
|
|
args.json = true;
|
|
continue;
|
|
}
|
|
if (arg === '--size') {
|
|
args.size = argv[++index] ?? args.size;
|
|
continue;
|
|
}
|
|
if (arg.startsWith('--size=')) {
|
|
args.size = arg.slice('--size='.length);
|
|
continue;
|
|
}
|
|
if (arg === '--output') {
|
|
args.output = argv[++index] ?? args.output;
|
|
continue;
|
|
}
|
|
if (arg.startsWith('--output=')) {
|
|
args.output = arg.slice('--output='.length);
|
|
continue;
|
|
}
|
|
if (arg === '--help' || arg === '-h') {
|
|
printHelp();
|
|
process.exit(0);
|
|
}
|
|
throw new Error(`Unknown argument: ${arg}`);
|
|
}
|
|
|
|
if (!SIZE_PRESETS[args.size]) {
|
|
throw new Error(`Unsupported size "${args.size}". Use one of: ${Object.keys(SIZE_PRESETS).join(', ')}`);
|
|
}
|
|
return args;
|
|
}
|
|
|
|
function printHelp() {
|
|
console.log(`Usage: node scripts/seed-large-dataset.mjs [--size small|medium|large] [--dry-run] [--json]
|
|
|
|
Creates deterministic V2.6 performance fixture data using perf-* ids.
|
|
|
|
Options:
|
|
--size Fixture size. Defaults to small.
|
|
--dry-run Build and summarize rows without writing PostgreSQL.
|
|
--json Print the summary as JSON.
|
|
--output Metadata path written after a real seed. Defaults to ${OUTPUT_PATH}.
|
|
`);
|
|
}
|
|
|
|
function pad(value, width = 3) {
|
|
return String(value).padStart(width, '0');
|
|
}
|
|
|
|
function hoursAfter(hours) {
|
|
return new Date(BASE_DATE.getTime() + hours * 60 * 60 * 1000);
|
|
}
|
|
|
|
function daysAfter(days) {
|
|
return hoursAfter(days * 24);
|
|
}
|
|
|
|
function priority(index) {
|
|
return index % 5;
|
|
}
|
|
|
|
function buildFixture(sizeName) {
|
|
const preset = SIZE_PRESETS[sizeName];
|
|
const users = buildUsers(preset.users);
|
|
const products = [];
|
|
const projects = [];
|
|
const versions = [];
|
|
const requirements = [];
|
|
const versionPlans = [];
|
|
const devTasks = [];
|
|
const testCases = [];
|
|
const bugs = [];
|
|
const workActivities = [];
|
|
const xiaobaoRiskSummaries = [];
|
|
|
|
for (let productNo = 1; productNo <= preset.products; productNo += 1) {
|
|
const productId = `${ID_PREFIX}product-${pad(productNo)}`;
|
|
products.push({
|
|
id: productId,
|
|
name: `Performance Product ${pad(productNo)}`,
|
|
description: `Deterministic ${sizeName} fixture product ${productNo}`,
|
|
createdAt: daysAfter(productNo),
|
|
updatedAt: daysAfter(productNo),
|
|
});
|
|
|
|
for (let looseNo = 1; looseNo <= preset.looseRequirementsPerProduct; looseNo += 1) {
|
|
requirements.push(buildRequirement({
|
|
productId,
|
|
projectId: null,
|
|
versionId: null,
|
|
productNo,
|
|
projectNo: 0,
|
|
versionNo: 0,
|
|
requirementNo: looseNo,
|
|
loose: true,
|
|
creatorId: users[looseNo % users.length].id,
|
|
}));
|
|
}
|
|
|
|
for (let projectNo = 1; projectNo <= preset.projectsPerProduct; projectNo += 1) {
|
|
const projectId = `${ID_PREFIX}project-${pad(productNo)}-${pad(projectNo)}`;
|
|
projects.push({
|
|
id: projectId,
|
|
productId,
|
|
name: `Performance Project ${pad(productNo)}-${pad(projectNo)}`,
|
|
description: `Hot-path project ${projectNo}`,
|
|
createdAt: daysAfter(projectNo),
|
|
updatedAt: daysAfter(projectNo),
|
|
});
|
|
|
|
for (let versionNo = 1; versionNo <= preset.versionsPerProject; versionNo += 1) {
|
|
const versionId = `${ID_PREFIX}version-${pad(productNo)}-${pad(projectNo)}-${pad(versionNo)}`;
|
|
const version = buildVersion({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
productNo,
|
|
projectNo,
|
|
versionNo,
|
|
users,
|
|
});
|
|
versions.push(version);
|
|
|
|
for (let planNo = 1; planNo <= preset.plansPerVersion; planNo += 1) {
|
|
versionPlans.push(buildVersionPlan({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
planNo,
|
|
ownerId: users[(planNo + versionNo) % users.length].id,
|
|
}));
|
|
}
|
|
|
|
const versionRequirementIds = [];
|
|
for (let requirementNo = 1; requirementNo <= preset.requirementsPerVersion; requirementNo += 1) {
|
|
const req = buildRequirement({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
productNo,
|
|
projectNo,
|
|
versionNo,
|
|
requirementNo,
|
|
loose: false,
|
|
creatorId: users[requirementNo % users.length].id,
|
|
});
|
|
requirements.push(req);
|
|
versionRequirementIds.push(req.id);
|
|
|
|
for (let taskNo = 1; taskNo <= preset.devTasksPerRequirement; taskNo += 1) {
|
|
devTasks.push(buildDevTask({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
requirementId: req.id,
|
|
requirementProductId: productId,
|
|
requirementNo,
|
|
taskNo,
|
|
assigneeId: users[(requirementNo + taskNo) % users.length].id,
|
|
}));
|
|
}
|
|
|
|
for (let caseNo = 1; caseNo <= preset.testCasesPerRequirement; caseNo += 1) {
|
|
testCases.push(buildTestCase({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
requirementId: req.id,
|
|
requirementProductId: productId,
|
|
requirementNo,
|
|
caseNo,
|
|
assigneeId: users[(requirementNo + caseNo + 2) % users.length].id,
|
|
}));
|
|
}
|
|
}
|
|
|
|
for (let bugNo = 1; bugNo <= preset.bugsPerVersion; bugNo += 1) {
|
|
bugs.push(buildBug({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
bugNo,
|
|
assigneeId: users[(bugNo + versionNo) % users.length].id,
|
|
testCaseId: testCases.find((row) => row.versionId === versionId)?.id,
|
|
}));
|
|
}
|
|
|
|
for (let activityNo = 1; activityNo <= preset.activitiesPerVersion; activityNo += 1) {
|
|
const source = devTasks.find((row) => row.versionId === versionId && row.assigneeId === users[activityNo % users.length].id)
|
|
?? devTasks.find((row) => row.versionId === versionId);
|
|
workActivities.push(buildWorkActivity({
|
|
productId,
|
|
projectId,
|
|
versionId,
|
|
activityNo,
|
|
actor: users[activityNo % users.length],
|
|
source,
|
|
}));
|
|
}
|
|
|
|
xiaobaoRiskSummaries.push(buildXiaobaoSummary({
|
|
version,
|
|
bugCount: preset.bugsPerVersion,
|
|
unfinishedCount: versionRequirementIds.length,
|
|
}));
|
|
}
|
|
}
|
|
}
|
|
|
|
return {
|
|
size: sizeName,
|
|
anchors: {
|
|
productId: `${ID_PREFIX}product-001`,
|
|
projectId: `${ID_PREFIX}project-001-001`,
|
|
versionId: `${ID_PREFIX}version-001-001-001`,
|
|
userId: `${ID_PREFIX}user-dev-01`,
|
|
},
|
|
rows: {
|
|
users,
|
|
products,
|
|
projects,
|
|
versions,
|
|
requirements,
|
|
versionPlans,
|
|
devTasks,
|
|
testCases,
|
|
bugs,
|
|
workActivities,
|
|
xiaobaoRiskSummaries,
|
|
},
|
|
};
|
|
}
|
|
|
|
function buildUsers(count) {
|
|
return Array.from({ length: count }, (_, index) => {
|
|
const no = index + 1;
|
|
return {
|
|
id: `${ID_PREFIX}user-dev-${pad(no, 2)}`,
|
|
email: `perf-user-${pad(no, 2)}@example.test`,
|
|
name: `Perf User ${pad(no, 2)}`,
|
|
username: `perf_user_${pad(no, 2)}`,
|
|
departmentId: no % 3 === 0 ? 'dept-2-3' : no % 2 === 0 ? 'dept-2-2' : 'dept-2-1',
|
|
roleId: no % 3 === 0 ? 'role-test' : 'role-dev',
|
|
phone: '',
|
|
password: 'Perf@2026',
|
|
isSystem: false,
|
|
createdAt: BASE_DATE,
|
|
updatedAt: BASE_DATE,
|
|
};
|
|
});
|
|
}
|
|
|
|
function buildVersion(input) {
|
|
const releaseDate = daysAfter(20 + input.versionNo);
|
|
return {
|
|
id: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
name: `V${input.productNo}.${input.projectNo}.${input.versionNo}`,
|
|
description: 'Performance hot-path version',
|
|
status: input.versionNo % 4 === 0 ? 'paused' : 'developing',
|
|
currentStage: input.versionNo % 2 === 0 ? 'testing' : 'development',
|
|
startDate: daysAfter(input.versionNo),
|
|
expectedReleaseDate: releaseDate,
|
|
releaseDate,
|
|
members: input.users.slice(0, Math.min(6, input.users.length)).map((user, index) => ({
|
|
id: user.id,
|
|
name: user.name,
|
|
role: index % 3 === 0 ? 'testing' : index % 2 === 0 ? 'backend' : 'frontend',
|
|
})),
|
|
progress: [],
|
|
priority: priority(input.versionNo),
|
|
links: {},
|
|
createdAt: daysAfter(input.versionNo),
|
|
updatedAt: daysAfter(input.versionNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildRequirement(input) {
|
|
const scoped = input.loose
|
|
? `LOOSE-${pad(input.productNo)}-${pad(input.requirementNo, 5)}`
|
|
: `${pad(input.productNo)}-${pad(input.projectNo)}-${pad(input.versionNo)}-${pad(input.requirementNo, 5)}`;
|
|
return {
|
|
id: `${ID_PREFIX}req-${scoped}`,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
versionId: input.versionId,
|
|
code: `REQ-${scoped}`,
|
|
title: `Requirement ${scoped}`,
|
|
description: `Deterministic requirement ${scoped}`,
|
|
status: input.loose ? 'adopted' : REQUIREMENT_STATUSES[input.requirementNo % REQUIREMENT_STATUSES.length],
|
|
priority: priority(input.requirementNo),
|
|
type: input.requirementNo % 2 === 0 ? 'feature' : 'optimization',
|
|
sourceType: input.requirementNo % 3 === 0 ? 'customer' : 'internal',
|
|
sourceTarget: input.requirementNo % 3 === 0 ? 'perf-customer' : 'perf-team',
|
|
platform: input.requirementNo % 2 === 0 ? 'web,ios' : 'web',
|
|
creatorId: input.creatorId,
|
|
createdAt: hoursAfter(input.requirementNo),
|
|
updatedAt: hoursAfter(input.requirementNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildVersionPlan(input) {
|
|
const type = PLAN_TYPES[(input.planNo - 1) % PLAN_TYPES.length];
|
|
return {
|
|
id: `${ID_PREFIX}plan-${input.versionId}-${type}`,
|
|
versionId: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
type,
|
|
title: `${type} plan for ${input.versionId}`,
|
|
status: input.planNo === 1 ? 'completed' : 'in_progress',
|
|
ownerId: input.ownerId,
|
|
expectedStartAt: daysAfter(input.planNo),
|
|
expectedEndAt: daysAfter(input.planNo + 3),
|
|
actualStartAt: daysAfter(input.planNo),
|
|
completedAt: input.planNo === 1 ? daysAfter(input.planNo + 2) : null,
|
|
resultUrl: input.planNo === 1 ? 'https://example.test/prototype' : null,
|
|
requirementCoverage: [],
|
|
logs: [],
|
|
createdAt: daysAfter(input.planNo),
|
|
updatedAt: daysAfter(input.planNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildDevTask(input) {
|
|
const status = DEV_STATUSES[(input.requirementNo + input.taskNo) % DEV_STATUSES.length];
|
|
const blocked = status !== 'submitted' && input.taskNo % 11 === 0;
|
|
return {
|
|
id: `${ID_PREFIX}dev-${input.versionId}-${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
|
|
versionId: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
requirementId: input.requirementId,
|
|
requirementProductId: input.requirementProductId,
|
|
categoryId: null,
|
|
code: `DEV-${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
|
|
title: `Dev task ${pad(input.requirementNo, 5)}-${pad(input.taskNo, 2)}`,
|
|
description: 'Performance fixture development task',
|
|
status,
|
|
priority: priority(input.requirementNo + input.taskNo),
|
|
assigneeId: input.assigneeId,
|
|
creatorId: input.assigneeId,
|
|
isBlocked: blocked,
|
|
blockReason: blocked ? 'Fixture blocker' : null,
|
|
expectedStartAt: daysAfter(input.taskNo),
|
|
expectedEndAt: daysAfter(input.taskNo + 2),
|
|
startDate: status === 'todo' ? null : daysAfter(input.taskNo),
|
|
completedAt: status === 'submitted' ? daysAfter(input.taskNo + 2) : null,
|
|
estimateHours: 4 + (input.taskNo % 5),
|
|
aiEstimateHours: 3 + (input.taskNo % 4),
|
|
references: [{ type: 'requirement', id: input.requirementId }],
|
|
aiDraft: false,
|
|
aiDraftAt: null,
|
|
createdAt: hoursAfter(input.requirementNo + input.taskNo),
|
|
updatedAt: hoursAfter(input.requirementNo + input.taskNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildTestCase(input) {
|
|
const status = TEST_STATUSES[(input.requirementNo + input.caseNo) % TEST_STATUSES.length];
|
|
return {
|
|
id: `${ID_PREFIX}case-${input.versionId}-${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
|
|
versionId: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
requirementId: input.requirementId,
|
|
requirementProductId: input.requirementProductId,
|
|
categoryId: null,
|
|
code: `TC-${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
|
|
title: `Test case ${pad(input.requirementNo, 5)}-${pad(input.caseNo, 2)}`,
|
|
description: 'Performance fixture test case',
|
|
status,
|
|
roundNo: input.caseNo % 3 === 0 ? 2 : 1,
|
|
priority: priority(input.requirementNo + input.caseNo),
|
|
assigneeId: input.assigneeId,
|
|
creatorId: input.assigneeId,
|
|
plannedTestAt: daysAfter(input.caseNo + 3),
|
|
plannedEndAt: daysAfter(input.caseNo + 4),
|
|
startedAt: status === 'pending' ? null : daysAfter(input.caseNo + 3),
|
|
completedAt: ['passed', 'failed', 'blocked'].includes(status) ? daysAfter(input.caseNo + 4) : null,
|
|
estimateHours: 2 + (input.caseNo % 4),
|
|
aiEstimateHours: 1 + (input.caseNo % 3),
|
|
references: [{ type: 'requirement', id: input.requirementId }],
|
|
aiDraft: false,
|
|
aiDraftAt: null,
|
|
createdAt: hoursAfter(input.requirementNo + input.caseNo),
|
|
updatedAt: hoursAfter(input.requirementNo + input.caseNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildBug(input) {
|
|
const status = BUG_STATUSES[input.bugNo % BUG_STATUSES.length];
|
|
const severity = BUG_SEVERITIES[input.bugNo % BUG_SEVERITIES.length];
|
|
return {
|
|
id: `${ID_PREFIX}bug-${input.versionId}-${pad(input.bugNo, 5)}`,
|
|
versionId: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
testCaseId: input.testCaseId ?? null,
|
|
testCaseVersionId: input.testCaseId ? input.versionId : null,
|
|
code: `BUG-${pad(input.bugNo, 5)}`,
|
|
title: `Bug ${pad(input.bugNo, 5)}`,
|
|
description: 'Performance fixture bug',
|
|
status,
|
|
severity,
|
|
priority: priority(input.bugNo),
|
|
assigneeId: input.assigneeId,
|
|
reporterId: input.assigneeId,
|
|
plannedFixAt: daysAfter(input.bugNo % 10),
|
|
resolvedAt: ['fixed', 'verifying', 'closed'].includes(status) ? daysAfter((input.bugNo % 10) + 1) : null,
|
|
closedAt: status === 'closed' ? daysAfter((input.bugNo % 10) + 2) : null,
|
|
resolution: status === 'closed' ? 'fixed' : null,
|
|
createdAt: hoursAfter(input.bugNo),
|
|
updatedAt: hoursAfter(input.bugNo + 1),
|
|
};
|
|
}
|
|
|
|
function buildWorkActivity(input) {
|
|
const source = input.source;
|
|
return {
|
|
id: `${ID_PREFIX}activity-${input.versionId}-${pad(input.activityNo, 5)}`,
|
|
versionId: input.versionId,
|
|
productId: input.productId,
|
|
projectId: input.projectId,
|
|
actorId: input.actor.id,
|
|
actorName: input.actor.name,
|
|
sourceType: 'dev_task',
|
|
sourceId: source?.id ?? `${ID_PREFIX}missing-source`,
|
|
sourceVersionId: input.versionId,
|
|
action: input.activityNo % 5 === 0 ? 'blocked' : 'progress',
|
|
title: `Activity ${pad(input.activityNo, 5)}`,
|
|
metadata: {
|
|
category: input.activityNo % 5 === 0 ? 'risk' : 'progress',
|
|
summary: `Performance activity ${input.activityNo}`,
|
|
},
|
|
occurredAt: hoursAfter(input.activityNo),
|
|
createdAt: hoursAfter(input.activityNo),
|
|
};
|
|
}
|
|
|
|
function buildXiaobaoSummary(input) {
|
|
const riskScore = Math.min(100, 40 + (input.bugCount % 50));
|
|
const riskLevel = riskScore >= 75 ? 'likely_delayed' : 'at_risk';
|
|
const riskSignature = `${input.version.id}|${riskLevel}|${riskScore}|${input.unfinishedCount}`;
|
|
return {
|
|
versionId: input.version.id,
|
|
riskLevel,
|
|
riskScore,
|
|
confidence: 72,
|
|
forecastReleaseDate: daysAfter(24),
|
|
riskSignature,
|
|
summary: {
|
|
versionId: input.version.id,
|
|
versionName: input.version.name,
|
|
riskLevel,
|
|
riskScore,
|
|
confidence: 72,
|
|
riskSignature,
|
|
dirty: false,
|
|
signals: {
|
|
unfinishedCount: input.unfinishedCount,
|
|
openBugCount: input.bugCount,
|
|
criticalBugCount: Math.floor(input.bugCount / 12),
|
|
failedTestCount: Math.floor(input.unfinishedCount / 8),
|
|
blockedCount: Math.floor(input.unfinishedCount / 15),
|
|
silentRiskCount: Math.floor(input.unfinishedCount / 20),
|
|
},
|
|
reasons: [
|
|
{
|
|
key: 'remaining_work',
|
|
title: 'Remaining fixture work',
|
|
detail: `Fixture has ${input.unfinishedCount} scoped requirements.`,
|
|
severity: 'warning',
|
|
},
|
|
],
|
|
currentSnapshot: {
|
|
versionId: input.version.id,
|
|
date: BASE_DATE.toISOString().slice(0, 10),
|
|
riskScore,
|
|
riskLevel,
|
|
openBugCount: input.bugCount,
|
|
criticalBugCount: Math.floor(input.bugCount / 12),
|
|
failedTestCount: Math.floor(input.unfinishedCount / 8),
|
|
blockedCount: Math.floor(input.unfinishedCount / 15),
|
|
silentRiskCount: Math.floor(input.unfinishedCount / 20),
|
|
confidence: 72,
|
|
createdAt: BASE_DATE.toISOString(),
|
|
},
|
|
},
|
|
dirty: false,
|
|
recomputedAt: BASE_DATE,
|
|
updatedAt: BASE_DATE,
|
|
};
|
|
}
|
|
|
|
function summarizeFixture(fixture) {
|
|
return {
|
|
fixtureVersion: FIXTURE_VERSION,
|
|
size: fixture.size,
|
|
anchors: fixture.anchors,
|
|
counts: Object.fromEntries(
|
|
Object.entries(fixture.rows).map(([key, rows]) => [key, rows.length]),
|
|
),
|
|
};
|
|
}
|
|
|
|
async function seedDatabase(fixture, outputPath) {
|
|
const serverRequire = createRequire(new URL('../apps/server/package.json', import.meta.url));
|
|
const { PrismaClient } = serverRequire('@prisma/client');
|
|
const prisma = new PrismaClient();
|
|
|
|
try {
|
|
await clearPerfRows(prisma);
|
|
await prisma.user.createMany({ data: fixture.rows.users, skipDuplicates: true });
|
|
await prisma.product.createMany({ data: fixture.rows.products, skipDuplicates: true });
|
|
await prisma.project.createMany({ data: fixture.rows.projects, skipDuplicates: true });
|
|
await prisma.version.createMany({ data: fixture.rows.versions, skipDuplicates: true });
|
|
await prisma.requirement.createMany({ data: fixture.rows.requirements, skipDuplicates: true });
|
|
await prisma.versionPlan.createMany({ data: fixture.rows.versionPlans, skipDuplicates: true });
|
|
await prisma.devTask.createMany({ data: fixture.rows.devTasks, skipDuplicates: true });
|
|
await prisma.testCase.createMany({ data: fixture.rows.testCases, skipDuplicates: true });
|
|
await prisma.bug.createMany({ data: fixture.rows.bugs, skipDuplicates: true });
|
|
await prisma.workActivity.createMany({ data: fixture.rows.workActivities, skipDuplicates: true });
|
|
await prisma.xiaobaoRiskSummary.createMany({ data: fixture.rows.xiaobaoRiskSummaries, skipDuplicates: true });
|
|
} finally {
|
|
await prisma.$disconnect();
|
|
}
|
|
|
|
const summary = summarizeFixture(fixture);
|
|
await mkdir(dirname(outputPath), { recursive: true });
|
|
await writeFile(outputPath, `${JSON.stringify(summary, null, 2)}\n`, 'utf8');
|
|
return summary;
|
|
}
|
|
|
|
async function clearPerfRows(prisma) {
|
|
await prisma.$transaction([
|
|
prisma.xiaobaoRiskSummary.deleteMany({ where: { versionId: { startsWith: `${ID_PREFIX}version-` } } }),
|
|
prisma.workActivity.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}activity-` } } }),
|
|
prisma.bug.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}bug-` } } }),
|
|
prisma.testCase.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}case-` } } }),
|
|
prisma.devTask.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}dev-` } } }),
|
|
prisma.versionPlan.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}plan-` } } }),
|
|
prisma.requirement.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}req-` } } }),
|
|
prisma.version.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}version-` } } }),
|
|
prisma.project.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}project-` } } }),
|
|
prisma.product.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}product-` } } }),
|
|
prisma.user.deleteMany({ where: { id: { startsWith: `${ID_PREFIX}user-` } } }),
|
|
]);
|
|
}
|
|
|
|
async function main() {
|
|
const args = parseArgs(process.argv.slice(2));
|
|
const fixture = buildFixture(args.size);
|
|
const summary = summarizeFixture(fixture);
|
|
|
|
if (args.dryRun) {
|
|
if (args.json) {
|
|
console.log(JSON.stringify(summary, null, 2));
|
|
} else {
|
|
printSummary(summary, 'dry-run');
|
|
}
|
|
return;
|
|
}
|
|
|
|
const outputPath = resolve(process.cwd(), args.output);
|
|
const seeded = await seedDatabase(fixture, outputPath);
|
|
if (args.json) {
|
|
console.log(JSON.stringify(seeded, null, 2));
|
|
} else {
|
|
printSummary(seeded, `seeded; metadata=${args.output}`);
|
|
}
|
|
}
|
|
|
|
function printSummary(summary, mode) {
|
|
console.log(`Large dataset fixture ${mode}`);
|
|
console.log(`version=${summary.fixtureVersion} size=${summary.size}`);
|
|
console.log(`anchors=${JSON.stringify(summary.anchors)}`);
|
|
for (const [name, count] of Object.entries(summary.counts)) {
|
|
console.log(`${name}=${count}`);
|
|
}
|
|
}
|
|
|
|
main().catch((error) => {
|
|
console.error(error instanceof Error ? error.message : error);
|
|
process.exitCode = 1;
|
|
});
|