Skip to content

Commit 9d3b270

Browse files
authored
feat(versioning): added api deployment versioning, change detection in workflow, added ci job to run migrations on merge to main (#220)
* added initial code for versioning * ran migrations & improvements to versioning * change sensitivity of state diff checker * debounce the change detection function * cleaned up file structure * added ci job to run migrations when merging to main * ran migrations * cleanup unused files, remove unused dependencies
1 parent c7ad9d5 commit 9d3b270

File tree

17 files changed

+9648
-9764
lines changed

17 files changed

+9648
-9764
lines changed

.github/workflows/ci.yml

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@ jobs:
2626
working-directory: ./sim
2727
run: npm ci
2828

29+
- name: Fix Rollup module issue
30+
working-directory: ./sim
31+
run: |
32+
rm -rf node_modules package-lock.json
33+
npm install
34+
2935
- name: Run tests with coverage
3036
working-directory: ./sim
3137
env:
@@ -41,4 +47,30 @@ jobs:
4147
with:
4248
directory: ./sim/coverage
4349
fail_ci_if_error: false
44-
verbose: true
50+
verbose: true
51+
52+
migrations:
53+
name: Apply Database Migrations
54+
runs-on: ubuntu-latest
55+
if: github.event_name == 'push' && github.ref == 'refs/heads/main'
56+
needs: test
57+
steps:
58+
- name: Checkout code
59+
uses: actions/checkout@v4
60+
61+
- name: Setup Node.js
62+
uses: actions/setup-node@v4
63+
with:
64+
node-version: '20'
65+
cache: 'npm'
66+
cache-dependency-path: './sim/package-lock.json'
67+
68+
- name: Install dependencies
69+
working-directory: ./sim
70+
run: npm ci
71+
72+
- name: Apply migrations
73+
working-directory: ./sim
74+
env:
75+
POSTGRES_URL: ${{ secrets.DATABASE_URL }}
76+
run: npx drizzle-kit push

sim/app/api/workflows/[id]/deploy/route.ts

Lines changed: 21 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
3232
isDeployed: workflow.isDeployed,
3333
deployedAt: workflow.deployedAt,
3434
userId: workflow.userId,
35+
state: workflow.state,
36+
deployedState: workflow.deployedState,
3537
})
3638
.from(workflow)
3739
.where(eq(workflow.id, id))
@@ -51,6 +53,7 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
5153
isDeployed: false,
5254
deployedAt: null,
5355
apiKey: null,
56+
needsRedeployment: false,
5457
})
5558
}
5659

@@ -63,11 +66,22 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
6366
.where(eq(apiKey.userId, workflowData.userId))
6467
.limit(1)
6568

69+
// Check if the workflow has meaningful changes that would require redeployment
70+
let needsRedeployment = false
71+
if (workflowData.deployedState) {
72+
const { hasWorkflowChanged } = await import('@/lib/workflows/utils')
73+
needsRedeployment = hasWorkflowChanged(
74+
workflowData.state as any,
75+
workflowData.deployedState as any
76+
)
77+
}
78+
6679
logger.info(`[${requestId}] Successfully retrieved deployment info: ${id}`)
6780
return createSuccessResponse({
6881
apiKey: userApiKey.length > 0 ? userApiKey[0].key : null,
6982
isDeployed: workflowData.isDeployed,
7083
deployedAt: workflowData.deployedAt,
84+
needsRedeployment,
7185
})
7286
} catch (error: any) {
7387
logger.error(`[${requestId}] Error fetching deployment info: ${id}`, error)
@@ -88,10 +102,11 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
88102
return createErrorResponse(validation.error.message, validation.error.status)
89103
}
90104

91-
// Get the workflow to find the user
105+
// Get the workflow to find the user and current state
92106
const workflowData = await db
93107
.select({
94108
userId: workflow.userId,
109+
state: workflow.state,
95110
})
96111
.from(workflow)
97112
.where(eq(workflow.id, id))
@@ -103,6 +118,7 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
103118
}
104119

105120
const userId = workflowData[0].userId
121+
const currentState = workflowData[0].state
106122
const deployedAt = new Date()
107123

108124
// Check if the user already has an API key
@@ -132,12 +148,13 @@ export async function POST(request: NextRequest, { params }: { params: Promise<{
132148
userKey = userApiKey[0].key
133149
}
134150

135-
// Update the workflow deployment status
151+
// Update the workflow deployment status and save current state as deployed state
136152
await db
137153
.update(workflow)
138154
.set({
139155
isDeployed: true,
140156
deployedAt,
157+
deployedState: currentState,
141158
})
142159
.where(eq(workflow.id, id))
143160

@@ -165,12 +182,13 @@ export async function DELETE(
165182
return createErrorResponse(validation.error.message, validation.error.status)
166183
}
167184

168-
// Update the workflow to remove deployment status
185+
// Update the workflow to remove deployment status and deployed state
169186
await db
170187
.update(workflow)
171188
.set({
172189
isDeployed: false,
173190
deployedAt: null,
191+
deployedState: null,
174192
})
175193
.where(eq(workflow.id, id))
176194

sim/app/api/workflows/[id]/execute/route.ts

Lines changed: 12 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,8 +61,18 @@ async function executeWorkflow(workflow: any, requestId: string, input?: any) {
6161
runningExecutions.add(workflowId)
6262
logger.info(`[${requestId}] Starting workflow execution: ${workflowId}`)
6363

64-
// Get the workflow state
65-
const state = workflow.state as WorkflowState
64+
// Use the deployed state if available, otherwise fall back to current state
65+
const workflowState = workflow.deployedState || workflow.state
66+
67+
if (!workflow.deployedState) {
68+
logger.warn(
69+
`[${requestId}] No deployed state found for workflow: ${workflowId}, using current state`
70+
)
71+
} else {
72+
logger.info(`[${requestId}] Using deployed state for workflow execution: ${workflowId}`)
73+
}
74+
75+
const state = workflowState as WorkflowState
6676
const { blocks, edges, loops } = state
6777

6878
// Use the same execution flow as in scheduled executions

sim/app/api/workflows/[id]/status/route.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import { NextRequest } from 'next/server'
22
import { createLogger } from '@/lib/logs/console-logger'
3+
import { hasWorkflowChanged } from '@/lib/workflows/utils'
34
import { validateWorkflowAccess } from '../../middleware'
45
import { createErrorResponse, createSuccessResponse } from '../../utils'
56

@@ -17,15 +18,26 @@ export async function GET(request: NextRequest, { params }: { params: Promise<{
1718
return createErrorResponse(validation.error.message, validation.error.status)
1819
}
1920

21+
// Check if the workflow has meaningful changes that would require redeployment
22+
let needsRedeployment = false
23+
if (validation.workflow.isDeployed && validation.workflow.deployedState) {
24+
needsRedeployment = hasWorkflowChanged(
25+
validation.workflow.state as any,
26+
validation.workflow.deployedState as any
27+
)
28+
}
29+
2030
logger.info(`[${requestId}] Retrieved status for workflow: ${id}`, {
2131
isDeployed: validation.workflow.isDeployed,
2232
isPublished: validation.workflow.isPublished,
33+
needsRedeployment,
2334
})
2435

2536
return createSuccessResponse({
2637
isDeployed: validation.workflow.isDeployed,
2738
deployedAt: validation.workflow.deployedAt,
2839
isPublished: validation.workflow.isPublished,
40+
needsRedeployment,
2941
})
3042
} catch (error) {
3143
logger.error(`[${requestId}] Error getting status for workflow: ${(await params).id}`, error)

0 commit comments

Comments
 (0)