| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
|
|
| import 'server-only'; |
|
|
| import { getSQLiteAdapter, getWorkspaceAdapter } from '@/lib/vfs/adapters/server'; |
| import { RuntimeDatabase } from '@/lib/vfs/adapters/runtime-database'; |
| import { ProjectDatabase } from '@/lib/vfs/adapters/project-database'; |
| import { projectDatabaseExists, getProjectDatabasePath } from '@/lib/vfs/adapters/sqlite-connection'; |
| import { encryptSecret, isEncryptionConfigured } from '@/lib/edge-functions/secrets-crypto'; |
|
|
| export interface ExtractionSummary { |
| edgeFunctions: number; |
| serverFunctions: number; |
| secrets: number; |
| scheduledFunctions: number; |
| databaseSchemaApplied: boolean; |
| errors: string[]; |
| } |
|
|
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| export async function extractBackendFeatures( |
| projectId: string, |
| deploymentId: string, |
| workspaceId?: string |
| ): Promise<ExtractionSummary> { |
| const summary: ExtractionSummary = { |
| edgeFunctions: 0, |
| serverFunctions: 0, |
| secrets: 0, |
| scheduledFunctions: 0, |
| databaseSchemaApplied: false, |
| errors: [], |
| }; |
|
|
| const adapter = workspaceId ? getWorkspaceAdapter(workspaceId) : getSQLiteAdapter(); |
| await adapter.init(); |
|
|
| |
| const edgeFunctions = adapter.listEdgeFunctions ? await adapter.listEdgeFunctions(projectId) : []; |
| const serverFunctions = adapter.listServerFunctions ? await adapter.listServerFunctions(projectId) : []; |
| const secrets = adapter.listSecrets ? await adapter.listSecrets(projectId) : []; |
| const scheduledFunctions = adapter.listScheduledFunctions ? await adapter.listScheduledFunctions(projectId) : []; |
|
|
| |
| if (edgeFunctions.length === 0 && serverFunctions.length === 0 && |
| secrets.length === 0 && scheduledFunctions.length === 0) { |
| return summary; |
| } |
|
|
| |
| const runtimeDb = new RuntimeDatabase(deploymentId); |
| runtimeDb.init(); |
|
|
| |
| |
| const existingFunctions = runtimeDb.listFunctions(); |
| for (const fn of existingFunctions) { |
| runtimeDb.deleteFunction(fn.id); |
| } |
|
|
| const existingServerFunctions = runtimeDb.listServerFunctions(); |
| for (const fn of existingServerFunctions) { |
| runtimeDb.deleteServerFunction(fn.id); |
| } |
|
|
| const existingSecrets = runtimeDb.listSecrets(); |
| for (const s of existingSecrets) { |
| runtimeDb.deleteSecret(s.id); |
| } |
|
|
| const existingScheduled = runtimeDb.listScheduledFunctions(); |
| for (const fn of existingScheduled) { |
| runtimeDb.deleteScheduledFunction(fn.id); |
| } |
|
|
| |
| const functionNameToId = new Map<string, string>(); |
| for (const fn of edgeFunctions) { |
| try { |
| const id = runtimeDb.createFunction({ |
| name: fn.name, |
| description: fn.description, |
| code: fn.code, |
| method: fn.method, |
| enabled: fn.enabled, |
| timeoutMs: fn.timeoutMs, |
| }); |
| functionNameToId.set(fn.name, id); |
| summary.edgeFunctions++; |
| } catch (err) { |
| summary.errors.push(`Edge function "${fn.name}": ${err instanceof Error ? err.message : String(err)}`); |
| } |
| } |
|
|
| |
| for (const fn of serverFunctions) { |
| try { |
| runtimeDb.createServerFunction({ |
| name: fn.name, |
| description: fn.description, |
| code: fn.code, |
| enabled: fn.enabled, |
| }); |
| summary.serverFunctions++; |
| } catch (err) { |
| summary.errors.push(`Server function "${fn.name}": ${err instanceof Error ? err.message : String(err)}`); |
| } |
| } |
|
|
| |
| for (const secret of secrets) { |
| try { |
| if (secret.value && isEncryptionConfigured()) { |
| |
| runtimeDb.createSecret(secret.name, secret.value, secret.description); |
| } else { |
| |
| runtimeDb.createSecretPlaceholder(secret.name, secret.description); |
| } |
| summary.secrets++; |
| } catch (err) { |
| summary.errors.push(`Secret "${secret.name}": ${err instanceof Error ? err.message : String(err)}`); |
| } |
| } |
|
|
| |
| for (const fn of scheduledFunctions) { |
| try { |
| |
| const runtimeFunctionId = functionNameToId.get(fn.functionId) || fn.functionId; |
|
|
| |
| if (!functionNameToId.has(fn.functionId)) { |
| |
| |
| const projectEdgeFn = edgeFunctions.find(ef => ef.id === fn.functionId); |
| if (projectEdgeFn) { |
| const resolvedId = functionNameToId.get(projectEdgeFn.name); |
| if (resolvedId) { |
| runtimeDb.createScheduledFunction({ |
| name: fn.name, |
| description: fn.description, |
| functionId: resolvedId, |
| cronExpression: fn.cronExpression, |
| timezone: fn.timezone, |
| config: fn.config, |
| enabled: fn.enabled, |
| lastRunAt: fn.lastRunAt, |
| nextRunAt: fn.nextRunAt, |
| lastStatus: fn.lastStatus, |
| lastError: fn.lastError, |
| lastDurationMs: fn.lastDurationMs, |
| }); |
| summary.scheduledFunctions++; |
| continue; |
| } |
| } |
| } |
|
|
| runtimeDb.createScheduledFunction({ |
| name: fn.name, |
| description: fn.description, |
| functionId: runtimeFunctionId, |
| cronExpression: fn.cronExpression, |
| timezone: fn.timezone, |
| config: fn.config, |
| enabled: fn.enabled, |
| lastRunAt: fn.lastRunAt, |
| nextRunAt: fn.nextRunAt, |
| lastStatus: fn.lastStatus, |
| lastError: fn.lastError, |
| lastDurationMs: fn.lastDurationMs, |
| }); |
| summary.scheduledFunctions++; |
| } catch (err) { |
| summary.errors.push(`Scheduled function "${fn.name}": ${err instanceof Error ? err.message : String(err)}`); |
| } |
| } |
|
|
| |
| if (projectDatabaseExists(projectId)) { |
| try { |
| |
| if (!/^[a-f0-9-]+$/i.test(projectId)) { |
| summary.errors.push('Project database: invalid project ID format'); |
| } else { |
| const projectDb = new ProjectDatabase(projectId); |
| projectDb.init(); |
| const tables = projectDb.getTableSchema(); |
|
|
| if (tables.length > 0) { |
| |
| const schemaSql = projectDb.getSchemaForExport(); |
| runtimeDb.executeDDL(schemaSql); |
|
|
| |
| const projectDbPath = getProjectDatabasePath(projectId); |
| runtimeDb.executeDDL(`ATTACH DATABASE '${projectDbPath.replace(/'/g, "''")}' AS project_db`); |
| |
| for (const table of tables) { |
| if (table.rowCount > 0) { |
| const escaped = `"${table.name.replace(/"/g, '""')}"`; |
| runtimeDb.executeDDL(`INSERT INTO ${escaped} SELECT * FROM project_db.${escaped}`); |
| } |
| } |
|
|
| runtimeDb.executeDDL(`DETACH DATABASE project_db`); |
| summary.databaseSchemaApplied = true; |
| } |
| } |
| } catch (err) { |
| summary.errors.push(`Project database: ${err instanceof Error ? err.message : String(err)}`); |
| } |
| } |
|
|
| return summary; |
| } |
|
|