Skip to content

Commit a6585d1

Browse files
committed
chore(deployment-versioning): add migration script into repo
1 parent d3f7ef4 commit a6585d1

1 file changed

Lines changed: 177 additions & 0 deletions

File tree

Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
#!/usr/bin/env bun
2+
3+
import { sql } from 'drizzle-orm'
4+
import { v4 as uuidv4 } from 'uuid'
5+
import { db } from '../db'
6+
import { workflow, workflowDeploymentVersion } from '../db/schema'
7+
import { loadWorkflowFromNormalizedTables as loadNormalizedWorkflow } from '../lib/workflows/db-helpers'
8+
import type { WorkflowState } from '../stores/workflows/workflow/types'
9+
10+
const DRY_RUN = process.argv.includes('--dry-run')
11+
const BATCH_SIZE = 50
12+
13+
// Use centralized normalization logic from lib/workflows/db-helpers
14+
15+
async function migrateWorkflows() {
16+
console.log('Starting deployment version migration...')
17+
console.log(`Mode: ${DRY_RUN ? 'DRY RUN' : 'LIVE'}`)
18+
console.log(`Batch size: ${BATCH_SIZE}`)
19+
console.log('---')
20+
21+
try {
22+
// Get all workflows
23+
const workflows = await db
24+
.select({
25+
id: workflow.id,
26+
name: workflow.name,
27+
isDeployed: workflow.isDeployed,
28+
deployedState: workflow.deployedState,
29+
deployedAt: workflow.deployedAt,
30+
userId: workflow.userId,
31+
})
32+
.from(workflow)
33+
34+
console.log(`Found ${workflows.length} workflows to process`)
35+
36+
// Check for existing deployment versions
37+
const existingVersions = await db
38+
.select({
39+
workflowId: workflowDeploymentVersion.workflowId,
40+
})
41+
.from(workflowDeploymentVersion)
42+
43+
const existingWorkflowIds = new Set(existingVersions.map((v) => v.workflowId))
44+
console.log(`${existingWorkflowIds.size} workflows already have deployment versions`)
45+
46+
let successCount = 0
47+
let skipCount = 0
48+
let errorCount = 0
49+
const errors: Array<{ workflowId: string; error: string }> = []
50+
51+
// Process in batches
52+
for (let i = 0; i < workflows.length; i += BATCH_SIZE) {
53+
const batch = workflows.slice(i, i + BATCH_SIZE)
54+
console.log(
55+
`\nProcessing batch ${Math.floor(i / BATCH_SIZE) + 1} (workflows ${i + 1}-${Math.min(i + BATCH_SIZE, workflows.length)})`
56+
)
57+
58+
const deploymentVersions = []
59+
60+
for (const wf of batch) {
61+
// Skip if already has deployment version
62+
if (existingWorkflowIds.has(wf.id)) {
63+
console.log(` [SKIP] ${wf.id} (${wf.name}) - already has deployment version`)
64+
skipCount++
65+
continue
66+
}
67+
68+
let state: WorkflowState | null = null
69+
70+
// First try to use existing deployedState
71+
if (wf.deployedState) {
72+
state = wf.deployedState as WorkflowState
73+
console.log(` [DEPLOYED] ${wf.id} (${wf.name}) - using existing deployedState`)
74+
} else {
75+
// Load from normalized tables for all workflows without deployedState
76+
const normalized = await loadNormalizedWorkflow(wf.id)
77+
if (normalized) {
78+
state = {
79+
blocks: normalized.blocks,
80+
edges: normalized.edges,
81+
loops: normalized.loops,
82+
parallels: normalized.parallels,
83+
} as WorkflowState
84+
console.log(
85+
` [NORMALIZED] ${wf.id} (${wf.name}) - loaded from normalized tables (was deployed: ${wf.isDeployed})`
86+
)
87+
} else {
88+
console.log(` [SKIP] ${wf.id} (${wf.name}) - no state available`)
89+
skipCount++
90+
continue
91+
}
92+
}
93+
94+
if (state) {
95+
deploymentVersions.push({
96+
id: uuidv4(),
97+
workflowId: wf.id,
98+
version: 1,
99+
state: state,
100+
createdAt: wf.deployedAt || new Date(),
101+
createdBy: wf.userId || 'migration',
102+
isActive: true, // Set ALL to active so schedules/webhooks keep working
103+
})
104+
successCount++
105+
}
106+
}
107+
108+
// Insert batch if not dry run
109+
if (deploymentVersions.length > 0) {
110+
if (DRY_RUN) {
111+
console.log(` [DRY RUN] Would insert ${deploymentVersions.length} deployment versions`)
112+
console.log(` [DRY RUN] Would mark ${deploymentVersions.length} workflows as deployed`)
113+
} else {
114+
try {
115+
// Insert deployment versions
116+
await db.insert(workflowDeploymentVersion).values(deploymentVersions)
117+
console.log(` [SUCCESS] Inserted ${deploymentVersions.length} deployment versions`)
118+
119+
// Update workflow.isDeployed to true for all workflows that got a version
120+
const workflowIds = deploymentVersions.map((v) => v.workflowId)
121+
await db
122+
.update(workflow)
123+
.set({
124+
isDeployed: true,
125+
deployedAt: new Date(), // Set deployedAt if it wasn't already set
126+
})
127+
.where(
128+
sql`${workflow.id} IN (${sql.join(
129+
workflowIds.map((id) => sql`${id}`),
130+
sql`, `
131+
)})`
132+
)
133+
console.log(` [SUCCESS] Marked ${workflowIds.length} workflows as deployed`)
134+
} catch (error) {
135+
console.error(` [ERROR] Failed to insert batch:`, error)
136+
errorCount += deploymentVersions.length
137+
successCount -= deploymentVersions.length
138+
}
139+
}
140+
}
141+
}
142+
143+
console.log('\n---')
144+
console.log('Migration Summary:')
145+
console.log(` Success: ${successCount} workflows`)
146+
console.log(` Skipped: ${skipCount} workflows`)
147+
console.log(` Errors: ${errorCount} workflows`)
148+
149+
if (errors.length > 0) {
150+
console.log('\nErrors:')
151+
errors.forEach(({ workflowId, error }) => {
152+
console.log(` - ${workflowId}: ${error}`)
153+
})
154+
}
155+
156+
if (DRY_RUN) {
157+
console.log('\n[DRY RUN] No changes were made to the database.')
158+
console.log('Run without --dry-run flag to apply changes.')
159+
} else {
160+
console.log('\nMigration completed successfully!')
161+
}
162+
} catch (error) {
163+
console.error('Fatal error during migration:', error)
164+
process.exit(1)
165+
}
166+
}
167+
168+
// Run the migration
169+
migrateWorkflows()
170+
.then(() => {
171+
console.log('\nDone!')
172+
process.exit(0)
173+
})
174+
.catch((error) => {
175+
console.error('Unexpected error:', error)
176+
process.exit(1)
177+
})

0 commit comments

Comments
 (0)