Skip to content

Commit 79536aa

Browse files
committed
refactor: streamline apply functionality for bidirectional sync
- Simplified the apply process by consolidating steps for pulling, merging, and pushing platform changes. - Updated comments and logging for clarity, emphasizing the new workflow: Pull → Merge → Push. - Removed redundant functions related to local changes and improved error handling during the sync process. - Enhanced user guidance in error messages for better conflict resolution.
1 parent 2ae7952 commit 79536aa

1 file changed

Lines changed: 23 additions & 143 deletions

File tree

src/apply.ts

Lines changed: 23 additions & 143 deletions
Original file line numberDiff line numberDiff line change
@@ -1,191 +1,71 @@
11
import { execSync } from "child_process";
2-
import type { ExecSyncOptionsWithStringEncoding } from "child_process";
32
import { join, dirname } from "path";
43
import { fileURLToPath } from "url";
54

65
// ─────────────────────────────────────────────────────────────────────────────
7-
// Bidirectional Sync: pull platform changes, merge with local changes, push
6+
// Apply: Pull → Merge → Push (safe bidirectional sync)
87
//
9-
// Flow:
10-
// 1. Check for local uncommitted changes (git status)
11-
// 2. If local changes exist, stash them
12-
// 3. Pull platform state (npm run pull:<env>)
13-
// 4. Commit the pulled state as a "platform sync" commit
14-
// 5. Pop the stash (reapply local changes)
15-
// 6. If merge conflicts, warn and exit for manual resolution
16-
// 7. Push merged state to platform (npm run push:<env>)
8+
// 1. Pull latest platform state, merge with local changes (git stash/pop)
9+
// 2. If merge is clean, push the result to the platform
10+
// 3. If conflicts, stop — user resolves, then runs push manually
1711
// ─────────────────────────────────────────────────────────────────────────────
1812

1913
const __dirname = dirname(fileURLToPath(import.meta.url));
2014
const BASE_DIR = join(__dirname, "..");
2115

2216
const VALID_ENVIRONMENTS = ["dev", "staging", "prod"] as const;
2317

24-
// ─────────────────────────────────────────────────────────────────────────────
25-
// Helpers
26-
// ─────────────────────────────────────────────────────────────────────────────
27-
28-
const execOpts: ExecSyncOptionsWithStringEncoding = {
29-
cwd: BASE_DIR,
30-
encoding: "utf-8",
31-
stdio: ["pipe", "pipe", "pipe"],
32-
};
33-
34-
function run(cmd: string, opts?: { silent?: boolean }): string {
35-
try {
36-
const output = execSync(cmd, execOpts).trim();
37-
if (!opts?.silent && output) {
38-
console.log(output);
39-
}
40-
return output;
41-
} catch (error: unknown) {
42-
const execError = error as { stderr?: string; stdout?: string; status?: number };
43-
const stderr = execError.stderr?.trim() || "";
44-
const stdout = execError.stdout?.trim() || "";
45-
throw new Error(`Command failed: ${cmd}\n${stderr}\n${stdout}`);
46-
}
47-
}
48-
4918
function runPassthrough(cmd: string): number {
5019
try {
5120
execSync(cmd, { cwd: BASE_DIR, stdio: "inherit" });
5221
return 0;
5322
} catch (error: unknown) {
54-
const execError = error as { status?: number };
55-
return execError.status ?? 1;
23+
return (error as { status?: number }).status ?? 1;
5624
}
5725
}
5826

59-
function hasLocalChanges(): boolean {
60-
const status = run("git status --porcelain", { silent: true });
61-
return status.length > 0;
62-
}
63-
64-
function isGitRepo(): boolean {
65-
try {
66-
run("git rev-parse --is-inside-work-tree", { silent: true });
67-
return true;
68-
} catch {
69-
return false;
70-
}
71-
}
72-
73-
function hasStash(): boolean {
74-
const stashList = run("git stash list", { silent: true });
75-
return stashList.length > 0;
76-
}
77-
78-
// ─────────────────────────────────────────────────────────────────────────────
79-
// Main Sync Flow
80-
// ─────────────────────────────────────────────────────────────────────────────
81-
8227
async function main(): Promise<void> {
8328
const env = process.argv[2];
8429
const extraArgs = process.argv.slice(3).join(" ");
8530

8631
if (!env || !VALID_ENVIRONMENTS.includes(env as typeof VALID_ENVIRONMENTS[number])) {
87-
console.error("❌ Environment argument is required");
88-
console.error(" Usage: npm run apply:dev | apply:prod");
32+
console.error("Usage: npm run apply:dev | apply:prod");
8933
console.error("");
90-
console.error(" This command performs a bidirectional sync:");
91-
console.error(" 1. Stashes your local changes");
92-
console.error(" 2. Pulls latest platform state");
93-
console.error(" 3. Reapplies your local changes on top");
94-
console.error(" 4. Pushes the merged result to the platform");
34+
console.error(" Pull → Merge → Push (safe bidirectional sync)");
9535
console.error("");
96-
console.error(" For one-way push only, use: npm run push:dev | push:prod");
36+
console.error(" Pulls latest platform state, merges with your local");
37+
console.error(" changes, and pushes the result back to the platform.");
38+
console.error(" Stops on merge conflicts for manual resolution.");
9739
process.exit(1);
9840
}
9941

10042
console.log("═══════════════════════════════════════════════════════════════");
101-
console.log(`🔄 Vapi GitOps Sync - Environment: ${env}`);
43+
console.log(`🔄 Vapi GitOps Apply - Environment: ${env}`);
44+
console.log(" Pull → Merge → Push");
10245
console.log("═══════════════════════════════════════════════════════════════\n");
10346

104-
// Step 0: Ensure we're in a git repo
105-
if (!isGitRepo()) {
106-
console.error("❌ Not a git repository. Bidirectional sync requires git.");
107-
console.error(" Initialize with: git init && git add . && git commit -m 'initial'");
108-
console.error(" Or use 'npm run push:<env>' for direct push without git.\n");
47+
// Step 1: Pull with merge
48+
const pullExit = runPassthrough(`npx tsx src/pull.ts ${env}`);
49+
if (pullExit !== 0) {
50+
console.error("\n❌ Pull had issues. Resolve conflicts before pushing.");
10951
process.exit(1);
11052
}
11153

112-
// Step 1: Check for local changes
113-
const hadLocalChanges = hasLocalChanges();
114-
115-
if (hadLocalChanges) {
116-
console.log("📦 Local changes detected, stashing...\n");
117-
run("git stash push -m \"gitops-sync: local changes before pull\"");
118-
console.log(" ✅ Local changes stashed\n");
119-
} else {
120-
console.log("📦 No local changes to stash\n");
121-
}
122-
123-
// Step 2: Pull platform state
124-
console.log("📥 Pulling platform state...\n");
125-
const pullExitCode = runPassthrough(`npx tsx src/pull.ts ${env}`);
126-
127-
if (pullExitCode !== 0) {
128-
console.error("\n❌ Pull failed!");
129-
if (hadLocalChanges) {
130-
console.log(" Restoring your local changes from stash...");
131-
run("git stash pop");
132-
}
133-
process.exit(1);
134-
}
135-
136-
// Step 3: Commit pulled state (if there are changes from the pull)
137-
if (hasLocalChanges()) {
138-
console.log("\n📝 Committing platform state...\n");
139-
run("git add -A");
140-
run(`git commit -m "sync: pull platform state (${env})"`);
141-
console.log(" ✅ Platform state committed\n");
142-
} else {
143-
console.log("\n📝 No platform changes to commit\n");
144-
}
145-
146-
// Step 4: Pop stash (reapply local changes)
147-
if (hadLocalChanges) {
148-
console.log("📦 Reapplying local changes...\n");
149-
try {
150-
run("git stash pop");
151-
console.log(" ✅ Local changes reapplied\n");
152-
} catch (error) {
153-
// Merge conflict during stash pop
154-
console.error("\n⚠️ Merge conflicts detected!\n");
155-
console.error(" Your local changes conflict with platform changes.");
156-
console.error(" Please resolve the conflicts manually, then run:");
157-
console.error(` git add . && npm run push:${env}\n`);
158-
console.error(" To see conflicted files:");
159-
console.error(" git diff --name-only --diff-filter=U\n");
160-
console.error(" To abort and restore your local changes:");
161-
console.error(" git checkout --theirs . && git stash pop\n");
162-
process.exit(1);
163-
}
164-
}
165-
166-
// Step 5: Push merged state to platform
167-
console.log("🚀 Pushing merged state to platform...\n");
168-
const pushExitCode = runPassthrough(`npx tsx src/push.ts ${env} ${extraArgs}`.trim());
169-
170-
if (pushExitCode !== 0) {
54+
// Step 2: Push merged state
55+
console.log("\n🚀 Pushing merged state to platform...\n");
56+
const pushCmd = `npx tsx src/push.ts ${env} ${extraArgs}`.trim();
57+
const pushExit = runPassthrough(pushCmd);
58+
if (pushExit !== 0) {
17159
console.error("\n❌ Push failed!");
17260
process.exit(1);
17361
}
17462

175-
// Step 6: Commit the final state after push (state file may have changed)
176-
if (hasLocalChanges()) {
177-
console.log("\n📝 Committing final state...\n");
178-
run("git add -A");
179-
run(`git commit -m "sync: apply local changes to platform (${env})"`);
180-
}
181-
18263
console.log("\n═══════════════════════════════════════════════════════════════");
183-
console.log("✅ Bidirectional sync complete!");
64+
console.log("✅ Apply complete! (Pull → Merge → Push)");
18465
console.log("═══════════════════════════════════════════════════════════════\n");
18566
}
18667

187-
// Run the sync engine
18868
main().catch((error) => {
189-
console.error("\n❌ Sync failed:", error);
69+
console.error("\n❌ Apply failed:", error);
19070
process.exit(1);
19171
});

0 commit comments

Comments
 (0)