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:
@@ -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 };
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user