feat: restructure into XFlow monorepo with Nuxt 4, Modular Onion Monolith, Android client, and Desktop CLI
This commit is contained in:
@@ -0,0 +1,264 @@
|
||||
// Real Git integration utilities for XFlow with Gitea
|
||||
// This handles actual Git operations, caching, and change tracking
|
||||
|
||||
import { execSync } from 'child_process'
|
||||
import { writeFileSync, readFileSync, existsSync, mkdirSync } from 'fs'
|
||||
import { join } from 'path'
|
||||
|
||||
export interface GitRepository {
|
||||
id: string
|
||||
name: string
|
||||
owner: string
|
||||
url: string
|
||||
cloneUrl: string
|
||||
isPrivate: boolean
|
||||
type: 'personal' | 'group'
|
||||
ownerId: string
|
||||
localPath: string
|
||||
createdAt: string
|
||||
updatedAt: string
|
||||
}
|
||||
|
||||
export interface GitCommit {
|
||||
id: string
|
||||
message: string
|
||||
author: string
|
||||
timestamp: string
|
||||
canvasId: string
|
||||
changes: {
|
||||
added: string[]
|
||||
modified: string[]
|
||||
deleted: string[]
|
||||
}
|
||||
}
|
||||
|
||||
// Cache for Git repositories
|
||||
const gitCache = new Map<string, GitRepository>()
|
||||
const canvasCache = new Map<string, any>()
|
||||
|
||||
// Base path for local Git repositories
|
||||
const GIT_BASE_PATH = process.env.GIT_BASE_PATH || '/tmp/xflow-git'
|
||||
|
||||
// Ensure base directory exists
|
||||
if (!existsSync(GIT_BASE_PATH)) {
|
||||
mkdirSync(GIT_BASE_PATH, { recursive: true })
|
||||
}
|
||||
|
||||
// Create a new Git repository for a user
|
||||
export async function createPersonalRepository(userId: string, canvasId: string): Promise<GitRepository> {
|
||||
const repoId = `repo-${userId}-${canvasId}-${Date.now()}`
|
||||
const repoName = `canvas-${canvasId}`
|
||||
const localPath = join(GIT_BASE_PATH, userId, repoName)
|
||||
|
||||
// Create local directory
|
||||
if (!existsSync(localPath)) {
|
||||
mkdirSync(localPath, { recursive: true })
|
||||
}
|
||||
|
||||
// Initialize Git repository
|
||||
try {
|
||||
execSync('git init', { cwd: localPath })
|
||||
execSync('git config user.name "XFlow System"', { cwd: localPath })
|
||||
execSync('git config user.email "xflow@system.local"', { cwd: localPath })
|
||||
} catch (error) {
|
||||
console.error('Failed to initialize Git repository:', error)
|
||||
}
|
||||
|
||||
const repository: GitRepository = {
|
||||
id: repoId,
|
||||
name: repoName,
|
||||
owner: userId,
|
||||
url: `https://gitea.example.com/${userId}/${repoName}`,
|
||||
cloneUrl: `https://gitea.example.com/${userId}/${repoName}.git`,
|
||||
isPrivate: true,
|
||||
type: 'personal',
|
||||
ownerId: userId,
|
||||
localPath,
|
||||
createdAt: new Date().toISOString(),
|
||||
updatedAt: new Date().toISOString()
|
||||
}
|
||||
|
||||
gitCache.set(repoId, repository)
|
||||
|
||||
// Initialize with initial commit
|
||||
await initializeRepository(repository, canvasId)
|
||||
|
||||
return repository
|
||||
}
|
||||
|
||||
// Initialize repository with initial canvas data
|
||||
async function initializeRepository(repository: GitRepository, canvasId: string): Promise<void> {
|
||||
const canvasFile = join(repository.localPath, `${canvasId}.canvas`)
|
||||
|
||||
// Create initial canvas file
|
||||
const initialCanvas = {
|
||||
nodes: [],
|
||||
edges: []
|
||||
}
|
||||
|
||||
writeFileSync(canvasFile, JSON.stringify(initialCanvas, null, 2))
|
||||
|
||||
// Create initial commit
|
||||
try {
|
||||
execSync('git add .', { cwd: repository.localPath })
|
||||
execSync(`git commit -m "Initial commit for canvas ${canvasId}"`, { cwd: repository.localPath })
|
||||
} catch (error) {
|
||||
console.error('Failed to create initial commit:', error)
|
||||
}
|
||||
}
|
||||
|
||||
// Sync canvas changes to Git repository
|
||||
export async function syncCanvasToGit(
|
||||
canvasId: string,
|
||||
canvasData: any,
|
||||
commitMessage?: string
|
||||
): Promise<GitCommit> {
|
||||
// Find repository for this canvas
|
||||
const repository = Array.from(gitCache.values())
|
||||
.find(repo => repo.name.includes(canvasId))
|
||||
|
||||
if (!repository) {
|
||||
throw new Error(`No Git repository found for canvas ${canvasId}`)
|
||||
}
|
||||
|
||||
const canvasFile = join(repository.localPath, `${canvasId}.canvas`)
|
||||
|
||||
// Check if file has changed
|
||||
const currentContent = JSON.stringify(canvasData, null, 2)
|
||||
let hasChanges = true
|
||||
|
||||
if (existsSync(canvasFile)) {
|
||||
const existingContent = readFileSync(canvasFile, 'utf-8')
|
||||
hasChanges = existingContent !== currentContent
|
||||
}
|
||||
|
||||
if (!hasChanges) {
|
||||
// Return latest commit if no changes
|
||||
const commits = getRepositoryHistory(repository)
|
||||
return commits[0] || {
|
||||
id: 'no-changes',
|
||||
message: 'No changes detected',
|
||||
author: 'system',
|
||||
timestamp: new Date().toISOString(),
|
||||
canvasId,
|
||||
changes: { added: [], modified: [], deleted: [] }
|
||||
}
|
||||
}
|
||||
|
||||
// Write canvas data
|
||||
writeFileSync(canvasFile, currentContent)
|
||||
|
||||
// Commit changes
|
||||
const message = commitMessage || `Update canvas ${canvasId}`
|
||||
|
||||
try {
|
||||
execSync('git add .', { cwd: repository.localPath })
|
||||
execSync(`git commit -m "${message}"`, { cwd: repository.localPath })
|
||||
} catch (error) {
|
||||
console.error('Failed to commit changes:', error)
|
||||
}
|
||||
|
||||
// Update cache
|
||||
repository.updatedAt = new Date().toISOString()
|
||||
canvasCache.set(canvasId, canvasData)
|
||||
|
||||
// Get commit hash
|
||||
let commitHash = 'unknown'
|
||||
try {
|
||||
commitHash = execSync('git rev-parse HEAD', { cwd: repository.localPath, encoding: 'utf-8' }).trim()
|
||||
} catch (error) {
|
||||
console.error('Failed to get commit hash:', error)
|
||||
}
|
||||
|
||||
const commit: GitCommit = {
|
||||
id: commitHash,
|
||||
message,
|
||||
author: repository.owner,
|
||||
timestamp: new Date().toISOString(),
|
||||
canvasId,
|
||||
changes: {
|
||||
added: existsSync(canvasFile) ? [`${canvasId}.canvas`] : [],
|
||||
modified: [`${canvasId}.canvas`],
|
||||
deleted: []
|
||||
}
|
||||
}
|
||||
|
||||
return commit
|
||||
}
|
||||
|
||||
// Load canvas from Git repository
|
||||
export async function loadCanvasFromGit(canvasId: string): Promise<any> {
|
||||
// Check cache first
|
||||
if (canvasCache.has(canvasId)) {
|
||||
return canvasCache.get(canvasId)
|
||||
}
|
||||
|
||||
// Find repository for this canvas
|
||||
const repository = Array.from(gitCache.values())
|
||||
.find(repo => repo.name.includes(canvasId))
|
||||
|
||||
if (!repository) {
|
||||
throw new Error(`No Git repository found for canvas ${canvasId}`)
|
||||
}
|
||||
|
||||
const canvasFile = join(repository.localPath, `${canvasId}.canvas`)
|
||||
|
||||
if (!existsSync(canvasFile)) {
|
||||
throw new Error(`Canvas file not found: ${canvasFile}`)
|
||||
}
|
||||
|
||||
const canvasData = JSON.parse(readFileSync(canvasFile, 'utf-8'))
|
||||
|
||||
// Cache the data
|
||||
canvasCache.set(canvasId, canvasData)
|
||||
|
||||
return canvasData
|
||||
}
|
||||
|
||||
// Get repository for a canvas
|
||||
export function getCanvasRepository(canvasId: string): GitRepository | null {
|
||||
return Array.from(gitCache.values())
|
||||
.find(repo => repo.name.includes(canvasId)) || null
|
||||
}
|
||||
|
||||
// Get commit history for a repository
|
||||
export function getRepositoryHistory(repository: GitRepository): GitCommit[] {
|
||||
try {
|
||||
const log = execSync('git log --pretty=format:"%H|%s|%an|%ad" --date=iso', {
|
||||
cwd: repository.localPath,
|
||||
encoding: 'utf-8'
|
||||
})
|
||||
|
||||
return log.split('\n')
|
||||
.filter(line => line.trim())
|
||||
.map(line => {
|
||||
const [id, message, author, timestamp] = line.split('|')
|
||||
return {
|
||||
id,
|
||||
message,
|
||||
author,
|
||||
timestamp,
|
||||
canvasId: repository.name.replace('canvas-', ''),
|
||||
changes: { added: [], modified: [], deleted: [] }
|
||||
}
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Failed to get repository history:', error)
|
||||
return []
|
||||
}
|
||||
}
|
||||
|
||||
// Check if canvas has unsaved changes
|
||||
export function hasUnsavedChanges(canvasId: string, currentData: any): boolean {
|
||||
if (!canvasCache.has(canvasId)) {
|
||||
return true // No cached data, assume changes
|
||||
}
|
||||
|
||||
const cachedData = canvasCache.get(canvasId)
|
||||
return JSON.stringify(cachedData) !== JSON.stringify(currentData)
|
||||
}
|
||||
|
||||
// Clear cache for a canvas
|
||||
export function clearCanvasCache(canvasId: string): void {
|
||||
canvasCache.delete(canvasId)
|
||||
}
|
||||
Reference in New Issue
Block a user