UNPKG

@workspace-fs/core

Version:

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

194 lines (174 loc) 5.13 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; } }