@workspace-fs/core
Version:
Multi-project workspace manager for Firesystem with support for multiple sources
153 lines (131 loc) • 4.1 kB
text/typescript
import { TypedEventEmitter } from "@firesystem/core";
import type {
Project,
SyncOptions,
ProjectDiff,
WorkspaceEventPayloads,
} from "../types";
export class ProjectOperations {
constructor(
private getProject: (projectId: string) => Project | null,
private events: TypedEventEmitter<WorkspaceEventPayloads>,
) {}
/**
* Copy files between projects
*/
async copyFiles(
sourceId: string,
pattern: string,
targetId: string,
targetPath: string,
): Promise<void> {
const sourceProject = this.getProject(sourceId);
const targetProject = this.getProject(targetId);
if (!sourceProject) throw new Error(`Source project ${sourceId} not found`);
if (!targetProject) throw new Error(`Target project ${targetId} not found`);
const files = await sourceProject.fs.glob(pattern);
let copied = 0;
for (const file of files) {
const stat = await sourceProject.fs.stat(file);
if (stat.type === "file") {
const content = await sourceProject.fs.readFile(file);
const targetFilePath = (targetPath + file).replace(/\/+/g, "/");
await targetProject.fs.writeFile(
targetFilePath,
content.content,
content.metadata,
);
copied++;
}
}
this.events.emit("project:files-copied", {
sourceId,
targetId,
fileCount: copied,
});
}
/**
* Sync projects
*/
async syncProjects(
sourceId: string,
targetId: string,
options: SyncOptions = {},
): Promise<void> {
const sourceProject = this.getProject(sourceId);
const targetProject = this.getProject(targetId);
if (!sourceProject) throw new Error(`Source project ${sourceId} not found`);
if (!targetProject) throw new Error(`Target project ${targetId} not found`);
const files = await sourceProject.fs.glob("**/*");
const total = files.length;
let copied = 0;
for (const file of files) {
if (options.filter && !options.filter(file)) continue;
const stat = await sourceProject.fs.stat(file);
if (stat.type === "file") {
const exists = await targetProject.fs.exists(file);
if (!exists || options.overwrite) {
const content = await sourceProject.fs.readFile(file);
await targetProject.fs.writeFile(
file,
content.content,
content.metadata,
);
copied++;
if (options.progress) {
options.progress(copied, total);
}
}
}
}
}
/**
* Compare projects
*/
async compareProjects(
projectId1: string,
projectId2: string,
): Promise<ProjectDiff> {
const project1 = this.getProject(projectId1);
const project2 = this.getProject(projectId2);
if (!project1) throw new Error(`Project ${projectId1} not found`);
if (!project2) throw new Error(`Project ${projectId2} not found`);
const files1 = new Set(await project1.fs.glob("**/*"));
const files2 = new Set(await project2.fs.glob("**/*"));
const diff: ProjectDiff = {
added: [],
modified: [],
deleted: [],
unchanged: [],
};
// Files in project2 but not in project1 (added)
for (const file of files2) {
if (!files1.has(file)) {
diff.added.push(file);
}
}
// Files in project1 but not in project2 (deleted)
for (const file of files1) {
if (!files2.has(file)) {
diff.deleted.push(file);
}
}
// Files in both - check if modified
for (const file of files1) {
if (files2.has(file)) {
const stat1 = await project1.fs.stat(file);
const stat2 = await project2.fs.stat(file);
if (stat1.type === "file" && stat2.type === "file") {
const content1 = await project1.fs.readFile(file);
const content2 = await project2.fs.readFile(file);
if (content1.content !== content2.content) {
diff.modified.push(file);
} else {
diff.unchanged.push(file);
}
}
}
}
return diff;
}
}