@workspace-fs/core
Version:
Multi-project workspace manager for Firesystem with support for multiple sources
194 lines (174 loc) • 5.13 kB
text/typescript
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;
}
}