UNPKG

@firesystem/workspace

Version:

Multi-project workspace manager for Firesystem with support for multiple sources

189 lines (170 loc) 5.09 kB
import type { IReactiveFileSystem } from "@firesystem/core"; import type { Project, WorkspaceSettings } from "../types"; import type { WorkspaceExport } from "./WorkspaceImporter"; import { SourceConfigBuilder } from "../credentials/SourceConfigBuilder"; export interface ExportOptions { includeFiles?: boolean; includeCredentials?: boolean; projectIds?: string[]; } /** * Handles exporting workspace configurations */ export class WorkspaceExporter { /** * Export workspace to JSON */ static async toJson( projects: Project[], activeProjectId: string | null, settings: WorkspaceSettings, options: ExportOptions = {} ): Promise<WorkspaceExport> { const exportedProjects = await Promise.all( projects .filter(p => !options.projectIds || options.projectIds.includes(p.id)) .map(async p => { const exported: any = { id: p.id, name: p.name, type: p.source.type as any }; // Include config based on credentials option if (p.source.type === "s3") { if (options.includeCredentials) { // Include full config with credentials exported.config = { ...p.source.config }; } else { // Strip credentials from S3 config exported.config = { bucket: p.source.config.bucket, prefix: p.source.config.prefix }; } } else if (p.source.type === "indexeddb") { exported.config = { dbName: p.source.config.dbName }; } else { // For other types, include full config unless it's sensitive exported.config = options.includeCredentials ? { ...p.source.config } : SourceConfigBuilder.sanitize(p.source).config; } // Include files if requested if (options.includeFiles && p.fs) { exported.files = await this.extractFiles(p.fs); } return exported; }) ); return { version: "1.0", exportedAt: new Date(), workspace: { settings, activeProjectId: activeProjectId || undefined }, projects: exportedProjects }; } /** * Export to GitHub Gist */ static async toGitHubGist( exportData: WorkspaceExport, options: { token: string; description?: string; public?: boolean; } ): Promise<string> { const response = await fetch("https://api.github.com/gists", { method: "POST", headers: { "Authorization": `token ${options.token}`, "Accept": "application/vnd.github.v3+json", "Content-Type": "application/json" }, body: JSON.stringify({ description: options.description || "Firesystem Workspace Export", public: options.public ?? false, files: { "workspace.json": { content: JSON.stringify(exportData, null, 2) } } }) }); if (!response.ok) { throw new Error(`Failed to create gist: ${response.statusText}`); } const gist = await response.json(); return gist.id; } /** * Export to API endpoint */ static async toApi( exportData: WorkspaceExport, url: string, options?: { headers?: Record<string, string>; method?: string; } ): Promise<void> { const response = await fetch(url, { method: options?.method || "POST", headers: { "Content-Type": "application/json", ...options?.headers }, body: JSON.stringify(exportData) }); if (!response.ok) { throw new Error(`Failed to export to API: ${response.statusText}`); } } /** * Extract all files from a file system */ private static async extractFiles(fs: IReactiveFileSystem): Promise<Array<{ path: string; content: string; metadata?: Record<string, any>; }>> { const files: any[] = []; try { // Check if file system supports glob operations if (!fs.capabilities?.supportsGlob) { console.warn("File system does not support glob operations, skipping file extraction"); return files; } // Get all file paths const paths = await fs.glob("**/*"); for (const path of paths) { try { const stat = await fs.stat(path); if (stat.type === "file") { const file = await fs.readFile(path); // Convert content to string if needed let content = file.content; if (typeof content !== "string") { content = JSON.stringify(content); } files.push({ path, content, metadata: file.metadata }); } } catch (error) { console.warn(`Failed to export file ${path}:`, error); } } } catch (error) { console.warn("Failed to glob files:", error); } return files; } }