From 8ce87dd61f807ca7ec05f0b598ed60d9bd3ab63d Mon Sep 17 00:00:00 2001 From: Will Chen Date: Sat, 15 Nov 2025 01:40:52 -0800 Subject: [PATCH] Re-deploy all supabase edge function when revert version (#1787) #1784 > [!NOTE] > After reverting to a previous version, automatically deploy all Supabase edge functions; failures are logged as warnings without blocking the revert. > > - **Version Revert Flow (`src/ipc/handlers/version_handlers.ts`)**: > - After revert, re-deploy all Supabase edge functions when `app.supabaseProjectId` is present via `deployAllSupabaseFunctions`. > - Aggregate deployment errors into `warningMessage` (non-blocking); logs successes/failures. > - Initialize `warningMessage` to an empty string. > - **Supabase Utilities (`src/supabase_admin/supabase_utils.ts`)**: > - Add `deployAllSupabaseFunctions`: scans `supabase/functions/*`, ensures `index.ts`, reads content, calls `deploySupabaseFunctions`, logs, collects errors; skips if directory missing. > > Written by [Cursor Bugbot](https://cursor.com/dashboard?tab=bugbot) for commit c29333c0f951b60b4e80da0b46cf0287d03bdae1. This will update automatically on new commits. Configure [here](https://cursor.com/dashboard?tab=bugbot). --- ## Summary by cubic Automatically re-deploys Supabase edge functions after restoring a previous app version to keep functions in sync with reverted code. Deployment failures surface as warnings and do not block the revert. - **New Features** - Re-deploy all Supabase edge functions on version revert when an app has a Supabase project ID. - Added deployAllSupabaseFunctions: scans supabase/functions, deploys each index.ts sequentially, skips missing files, collects errors, and logs warnings. Written for commit c29333c0f951b60b4e80da0b46cf0287d03bdae1. Summary will update automatically on new commits. --- src/ipc/handlers/version_handlers.ts | 30 +++++++++- src/supabase_admin/supabase_utils.ts | 82 ++++++++++++++++++++++++++++ 2 files changed, 111 insertions(+), 1 deletion(-) diff --git a/src/ipc/handlers/version_handlers.ts b/src/ipc/handlers/version_handlers.ts index 679a604..dcf1029 100644 --- a/src/ipc/handlers/version_handlers.ts +++ b/src/ipc/handlers/version_handlers.ts @@ -15,6 +15,7 @@ import { withLock } from "../utils/lock_utils"; import log from "electron-log"; import { createLoggedHandler } from "./safe_handle"; import { gitCheckout, gitCommit, gitStageToRevert } from "../utils/git_utils"; +import { deployAllSupabaseFunctions } from "../../supabase_admin/supabase_utils"; import { getNeonClient, @@ -157,7 +158,7 @@ export function registerVersionHandlers() { ): Promise => { return withLock(appId, async () => { let successMessage = "Restored version"; - let warningMessage: string | undefined = undefined; + let warningMessage = ""; const app = await db.query.apps.findFirst({ where: eq(apps.id, appId), }); @@ -311,6 +312,33 @@ export function registerVersionHandlers() { appPath: app.path, }); } + // Re-deploy all Supabase edge functions after reverting + if (app.supabaseProjectId) { + try { + logger.info( + `Re-deploying all Supabase edge functions for app ${appId} after revert`, + ); + const deployErrors = await deployAllSupabaseFunctions({ + appPath, + supabaseProjectId: app.supabaseProjectId, + }); + + if (deployErrors.length > 0) { + warningMessage += `Some Supabase functions failed to deploy after revert: ${deployErrors.join(", ")}`; + logger.warn(warningMessage); + // Note: We don't fail the revert operation if function deployment fails + // The code has been successfully reverted, but functions may be out of sync + } else { + logger.info( + `Successfully re-deployed all Supabase edge functions for app ${appId}`, + ); + } + } catch (error) { + warningMessage += `Error re-deploying Supabase edge functions after revert: ${error}`; + logger.warn(warningMessage); + // Continue with the revert operation even if function deployment fails + } + } if (warningMessage) { return { warningMessage }; } diff --git a/src/supabase_admin/supabase_utils.ts b/src/supabase_admin/supabase_utils.ts index 198dcb5..9f0fd7c 100644 --- a/src/supabase_admin/supabase_utils.ts +++ b/src/supabase_admin/supabase_utils.ts @@ -1,3 +1,85 @@ +import fs from "node:fs/promises"; +import path from "node:path"; +import log from "electron-log"; +import { deploySupabaseFunctions } from "./supabase_management_client"; + +const logger = log.scope("supabase_utils"); + export function isServerFunction(filePath: string) { return filePath.startsWith("supabase/functions/"); } + +/** + * Deploys all Supabase edge functions found in the app's supabase/functions directory + * @param appPath - The absolute path to the app directory + * @param supabaseProjectId - The Supabase project ID + * @returns An array of error messages for functions that failed to deploy (empty if all succeeded) + */ +export async function deployAllSupabaseFunctions({ + appPath, + supabaseProjectId, +}: { + appPath: string; + supabaseProjectId: string; +}): Promise { + const functionsDir = path.join(appPath, "supabase", "functions"); + + // Check if supabase/functions directory exists + try { + await fs.access(functionsDir); + } catch { + logger.info(`No supabase/functions directory found at ${functionsDir}`); + return []; + } + + const errors: string[] = []; + + try { + // Read all directories in supabase/functions + const entries = await fs.readdir(functionsDir, { withFileTypes: true }); + const functionDirs = entries.filter((entry) => entry.isDirectory()); + + logger.info( + `Found ${functionDirs.length} functions to deploy in ${functionsDir}`, + ); + + // Deploy each function + for (const functionDir of functionDirs) { + const functionName = functionDir.name; + const indexPath = path.join(functionsDir, functionName, "index.ts"); + + // Check if index.ts exists + try { + await fs.access(indexPath); + } catch { + logger.warn( + `Skipping ${functionName}: index.ts not found at ${indexPath}`, + ); + continue; + } + + try { + const content = await fs.readFile(indexPath, "utf-8"); + logger.info(`Deploying function: ${functionName}`); + + await deploySupabaseFunctions({ + supabaseProjectId, + functionName, + content, + }); + + logger.info(`Successfully deployed function: ${functionName}`); + } catch (error: any) { + const errorMessage = `Failed to deploy ${functionName}: ${error.message}`; + logger.error(errorMessage, error); + errors.push(errorMessage); + } + } + } catch (error: any) { + const errorMessage = `Error reading functions directory: ${error.message}`; + logger.error(errorMessage, error); + errors.push(errorMessage); + } + + return errors; +}