1+ import { execSync } from "child_process" ;
12import { mkdir , writeFile } from "fs/promises" ;
23import { join , dirname } from "path" ;
34import { stringify } from "yaml" ;
4- import { VAPI_ENV , VAPI_BASE_URL , VAPI_TOKEN , RESOURCES_DIR } from "./config.ts" ;
5+ import { VAPI_ENV , VAPI_BASE_URL , VAPI_TOKEN , RESOURCES_DIR , BASE_DIR } from "./config.ts" ;
56import { loadState , saveState } from "./state.ts" ;
67import type { StateFile , ResourceType } from "./types.ts" ;
78
@@ -28,10 +29,6 @@ const EXCLUDED_FIELDS = [
2829 "workflowIds" , // Server-managed: workflows are a separate resource type
2930] ;
3031
31- // ─────────────────────────────────────────────────────────────────────────────
32- // API Functions
33- // ─────────────────────────────────────────────────────────────────────────────
34-
3532// Map resource types to their API endpoints
3633const ENDPOINT_MAP : Record < ResourceType , string > = {
3734 tools : "/tool" ,
@@ -56,6 +53,34 @@ const FOLDER_MAP: Record<ResourceType, string> = {
5653 simulationSuites : "simulations/suites" ,
5754} ;
5855
56+ // ─────────────────────────────────────────────────────────────────────────────
57+ // Git Helpers (merge support — stash local changes before pull, reapply after)
58+ // ─────────────────────────────────────────────────────────────────────────────
59+
60+ function gitCmd ( args : string ) : string {
61+ return execSync ( `git ${ args } ` , {
62+ cwd : BASE_DIR ,
63+ encoding : "utf-8" ,
64+ stdio : [ "pipe" , "pipe" , "pipe" ] ,
65+ } ) . trim ( ) ;
66+ }
67+
68+ function isGitRepo ( ) : boolean {
69+ try { gitCmd ( "rev-parse --is-inside-work-tree" ) ; return true ; } catch { return false ; }
70+ }
71+
72+ function gitHasCommits ( ) : boolean {
73+ try { gitCmd ( "rev-parse HEAD" ) ; return true ; } catch { return false ; }
74+ }
75+
76+ function gitHasChanges ( ) : boolean {
77+ return gitCmd ( "status --porcelain" ) . length > 0 ;
78+ }
79+
80+ // ─────────────────────────────────────────────────────────────────────────────
81+ // API Functions
82+ // ─────────────────────────────────────────────────────────────────────────────
83+
5984export async function fetchAllResources ( resourceType : ResourceType ) : Promise < VapiResource [ ] > {
6085 const endpoint = ENDPOINT_MAP [ resourceType ] ;
6186 const url = `${ VAPI_BASE_URL } ${ endpoint } ` ;
@@ -406,6 +431,18 @@ async function main(): Promise<void> {
406431 console . log ( ` API: ${ VAPI_BASE_URL } ` ) ;
407432 console . log ( "═══════════════════════════════════════════════════════════════" ) ;
408433
434+ // Git merge support: stash local changes before overwriting with platform state
435+ const gitEnabled = isGitRepo ( ) && gitHasCommits ( ) ;
436+ const hadLocalChanges = gitEnabled && gitHasChanges ( ) ;
437+
438+ if ( hadLocalChanges ) {
439+ console . log ( "\n📦 Stashing local changes before pull..." ) ;
440+ gitCmd ( 'stash push -m "gitops: stash before pull"' ) ;
441+ console . log ( " ✅ Local changes stashed\n" ) ;
442+ } else if ( gitEnabled ) {
443+ console . log ( "\n📦 No local changes to stash\n" ) ;
444+ }
445+
409446 const state = loadState ( ) ;
410447
411448 const stats : Record < string , PullStats > = {
@@ -419,14 +456,7 @@ async function main(): Promise<void> {
419456 simulationSuites : { created : 0 , updated : 0 } ,
420457 } ;
421458
422- // Pull in dependency order:
423- // 1. Base resources (tools, structuredOutputs)
424- // 2. Assistants (references tools, structuredOutputs)
425- // 3. Squads (references assistants)
426- // 4. Simulation building blocks (personalities, scenarios - no cross-dependencies)
427- // 5. Simulations (references personalities, scenarios)
428- // 6. Simulation suites (references simulations)
429-
459+ // Pull in dependency order
430460 stats . tools = await pullResourceType ( "tools" , state ) ;
431461 stats . structuredOutputs = await pullResourceType ( "structuredOutputs" , state ) ;
432462 stats . assistants = await pullResourceType ( "assistants" , state ) ;
@@ -436,9 +466,32 @@ async function main(): Promise<void> {
436466 stats . simulations = await pullResourceType ( "simulations" , state ) ;
437467 stats . simulationSuites = await pullResourceType ( "simulationSuites" , state ) ;
438468
439- // Save updated state
469+ // Reapply local changes on top of pulled platform state
470+ let hasConflicts = false ;
471+ if ( hadLocalChanges ) {
472+ console . log ( "\n📦 Reapplying local changes..." ) ;
473+ try {
474+ gitCmd ( "stash pop" ) ;
475+ console . log ( " ✅ Local changes merged cleanly\n" ) ;
476+ } catch {
477+ hasConflicts = true ;
478+ }
479+ }
480+
481+ // Always save state last — overwrites any stash-induced changes to the state file
440482 await saveState ( state ) ;
441483
484+ if ( hasConflicts ) {
485+ console . error ( "\n⚠️ Merge conflicts detected!" ) ;
486+ console . error ( " Platform changes conflict with your local changes.\n" ) ;
487+ console . error ( " To see conflicted files:" ) ;
488+ console . error ( " git diff --name-only --diff-filter=U\n" ) ;
489+ console . error ( " After resolving conflicts:" ) ;
490+ console . error ( ` npm run push:${ VAPI_ENV } # push to platform` ) ;
491+ console . error ( " git stash drop # clean up the stash\n" ) ;
492+ process . exit ( 1 ) ;
493+ }
494+
442495 // Summary
443496 console . log ( "\n═══════════════════════════════════════════════════════════════" ) ;
444497 console . log ( "✅ Pull complete!" ) ;
0 commit comments