Re-deploy all supabase edge function when revert version (#1787)

#1784 

<!-- CURSOR_SUMMARY -->
> [!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.
> 
> <sup>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).</sup>
<!-- /CURSOR_SUMMARY -->





<!-- This is an auto-generated description by cubic. -->
---
## 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.

<sup>Written for commit c29333c0f951b60b4e80da0b46cf0287d03bdae1.
Summary will update automatically on new commits.</sup>

<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Will Chen
2025-11-15 01:40:52 -08:00
committed by GitHub
parent 2a7f5a8909
commit 8ce87dd61f
2 changed files with 111 additions and 1 deletions

View File

@@ -15,6 +15,7 @@ import { withLock } from "../utils/lock_utils";
import log from "electron-log"; import log from "electron-log";
import { createLoggedHandler } from "./safe_handle"; import { createLoggedHandler } from "./safe_handle";
import { gitCheckout, gitCommit, gitStageToRevert } from "../utils/git_utils"; import { gitCheckout, gitCommit, gitStageToRevert } from "../utils/git_utils";
import { deployAllSupabaseFunctions } from "../../supabase_admin/supabase_utils";
import { import {
getNeonClient, getNeonClient,
@@ -157,7 +158,7 @@ export function registerVersionHandlers() {
): Promise<RevertVersionResponse> => { ): Promise<RevertVersionResponse> => {
return withLock(appId, async () => { return withLock(appId, async () => {
let successMessage = "Restored version"; let successMessage = "Restored version";
let warningMessage: string | undefined = undefined; let warningMessage = "";
const app = await db.query.apps.findFirst({ const app = await db.query.apps.findFirst({
where: eq(apps.id, appId), where: eq(apps.id, appId),
}); });
@@ -311,6 +312,33 @@ export function registerVersionHandlers() {
appPath: app.path, 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) { if (warningMessage) {
return { warningMessage }; return { warningMessage };
} }

View File

@@ -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) { export function isServerFunction(filePath: string) {
return filePath.startsWith("supabase/functions/"); 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<string[]> {
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;
}