- Implemented `stage-resolver.ts` to unify old and new pipeline stage definitions. - Created `org-scope.ts` for organization access verification and scoping queries. - Added role-based permissions management in `permissions.ts` and `rbac-service.ts`. - Introduced invitation management in `invitation-service.ts` with validation schemas. - Developed custom field and notification rule services with respective validators. - Established pipeline template CRUD operations in `pipeline-template-service.ts`. - Added Zustand store for managing pipeline builder state in `pipeline-builder-store.ts`.
181 lines
5.4 KiB
TypeScript
181 lines
5.4 KiB
TypeScript
/**
|
|
* Migrate to Dynamic Pipelines
|
|
*
|
|
* Creates a PipelineTemplate "HP CG Standard" for the dev org,
|
|
* copies stages + dependencies from PipelineStageTemplate,
|
|
* backfills stageDefinitionId on existing DeliverableStages,
|
|
* and sets pipelineTemplateId on existing projects.
|
|
*
|
|
* Run with: npx tsx scripts/migrate-to-dynamic-pipelines.ts
|
|
*/
|
|
|
|
import "dotenv/config";
|
|
import { PrismaPg } from "@prisma/adapter-pg";
|
|
import { PrismaClient } from "../src/generated/prisma/client";
|
|
|
|
const adapter = new PrismaPg({ connectionString: process.env.DATABASE_URL! });
|
|
const prisma = new PrismaClient({ adapter });
|
|
|
|
const ORG_ID = "dev-org-001";
|
|
|
|
async function main() {
|
|
// 1. Check if already migrated
|
|
const existing = await prisma.pipelineTemplate.findFirst({
|
|
where: { organizationId: ORG_ID, name: "HP CG Standard" },
|
|
});
|
|
|
|
if (existing) {
|
|
console.log(`Pipeline template "HP CG Standard" already exists (${existing.id}). Skipping creation.`);
|
|
await backfillStages(existing.id);
|
|
await backfillProjects(existing.id);
|
|
console.log("Migration complete!");
|
|
return;
|
|
}
|
|
|
|
// 2. Fetch old global templates with dependencies
|
|
const oldTemplates = await prisma.pipelineStageTemplate.findMany({
|
|
include: { dependsOn: true },
|
|
orderBy: { order: "asc" },
|
|
});
|
|
|
|
if (oldTemplates.length === 0) {
|
|
console.log("No old templates found. Run seed first.");
|
|
return;
|
|
}
|
|
|
|
console.log(`Found ${oldTemplates.length} old pipeline stages. Creating dynamic template...`);
|
|
|
|
// 3. Create the new PipelineTemplate
|
|
const pipeline = await prisma.pipelineTemplate.create({
|
|
data: {
|
|
name: "HP CG Standard",
|
|
description: "Standard HP CG production pipeline with 10 stages",
|
|
organizationId: ORG_ID,
|
|
isDefault: true,
|
|
},
|
|
});
|
|
console.log(`Created PipelineTemplate: ${pipeline.id}`);
|
|
|
|
// 4. Create PipelineStageDefinitions from old templates
|
|
const oldToNewId = new Map<string, string>(); // old template ID → new definition ID
|
|
|
|
for (const old of oldTemplates) {
|
|
const def = await prisma.pipelineStageDefinition.create({
|
|
data: {
|
|
pipelineId: pipeline.id,
|
|
name: old.name,
|
|
slug: old.slug,
|
|
order: old.order,
|
|
isCriticalGate: old.isCriticalGate,
|
|
isOptional: old.isOptional,
|
|
description: old.description,
|
|
estimatedDays: old.estimatedDays,
|
|
},
|
|
});
|
|
oldToNewId.set(old.id, def.id);
|
|
console.log(` Stage: ${old.name} → ${def.id}`);
|
|
}
|
|
|
|
// 5. Create dependencies
|
|
let depCount = 0;
|
|
for (const old of oldTemplates) {
|
|
for (const dep of old.dependsOn) {
|
|
const newStageId = oldToNewId.get(dep.stageId);
|
|
const newPrereqId = oldToNewId.get(dep.prerequisiteId);
|
|
if (!newStageId || !newPrereqId) {
|
|
console.warn(` Skipping dependency: ${dep.stageId} → ${dep.prerequisiteId}`);
|
|
continue;
|
|
}
|
|
await prisma.pipelineStageDependencyV2.create({
|
|
data: { stageId: newStageId, prerequisiteId: newPrereqId },
|
|
});
|
|
depCount++;
|
|
}
|
|
}
|
|
console.log(`Created ${depCount} dependencies.`);
|
|
|
|
// 6. Backfill existing data
|
|
await backfillStages(pipeline.id);
|
|
await backfillProjects(pipeline.id);
|
|
|
|
console.log("Migration complete!");
|
|
}
|
|
|
|
async function backfillStages(pipelineId: string) {
|
|
// Get the stage definitions for this pipeline
|
|
const definitions = await prisma.pipelineStageDefinition.findMany({
|
|
where: { pipelineId },
|
|
});
|
|
|
|
// Get old templates to map slugs
|
|
const oldTemplates = await prisma.pipelineStageTemplate.findMany();
|
|
const slugToDefId = new Map(definitions.map((d) => [d.slug, d.id]));
|
|
const oldIdToSlug = new Map(oldTemplates.map((t) => [t.id, t.slug]));
|
|
|
|
// Find stages without stageDefinitionId
|
|
const stages = await prisma.deliverableStage.findMany({
|
|
where: { stageDefinitionId: null },
|
|
select: { id: true, templateId: true },
|
|
});
|
|
|
|
if (stages.length === 0) {
|
|
console.log("No stages need stageDefinitionId backfill.");
|
|
return;
|
|
}
|
|
|
|
console.log(`Backfilling stageDefinitionId on ${stages.length} stages...`);
|
|
let updated = 0;
|
|
const batchSize = 100;
|
|
|
|
for (let i = 0; i < stages.length; i += batchSize) {
|
|
const batch = stages.slice(i, i + batchSize);
|
|
await prisma.$transaction(
|
|
batch
|
|
.map((stage) => {
|
|
const slug = oldIdToSlug.get(stage.templateId);
|
|
const defId = slug ? slugToDefId.get(slug) : undefined;
|
|
if (!defId) return null;
|
|
return prisma.deliverableStage.update({
|
|
where: { id: stage.id },
|
|
data: { stageDefinitionId: defId },
|
|
});
|
|
})
|
|
.filter(Boolean) as any[]
|
|
);
|
|
updated += batch.length;
|
|
}
|
|
console.log(` Updated ${updated} stages.`);
|
|
}
|
|
|
|
async function backfillProjects(pipelineId: string) {
|
|
const projects = await prisma.project.findMany({
|
|
where: {
|
|
organizationId: ORG_ID,
|
|
pipelineTemplateId: null,
|
|
},
|
|
select: { id: true },
|
|
});
|
|
|
|
if (projects.length === 0) {
|
|
console.log("No projects need pipelineTemplateId backfill.");
|
|
return;
|
|
}
|
|
|
|
console.log(`Setting pipelineTemplateId on ${projects.length} projects...`);
|
|
await prisma.project.updateMany({
|
|
where: {
|
|
id: { in: projects.map((p) => p.id) },
|
|
},
|
|
data: { pipelineTemplateId: pipelineId },
|
|
});
|
|
console.log(` Updated ${projects.length} projects.`);
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(async () => {
|
|
await prisma.$disconnect();
|
|
});
|