From 1e706b7d93649a1ff81e45d220525ad2ef5ee964 Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Tue, 3 Feb 2026 09:03:12 +0000 Subject: [PATCH] feat: add file utilities module --- src/utils/file-utils.ts | 227 ++++++++-------------------------------- 1 file changed, 46 insertions(+), 181 deletions(-) diff --git a/src/utils/file-utils.ts b/src/utils/file-utils.ts index efdfdc8..c5e6282 100644 --- a/src/utils/file-utils.ts +++ b/src/utils/file-utils.ts @@ -7,233 +7,98 @@ import { GitAgentSyncError } from './errors'; const GLOBAL_CONFIG_DIR = '.git-agent-sync'; const GLOBAL_CONFIG_FILE = 'config.json'; const WORKSPACE_CONFIG_FILE = '.agent-workspace.json'; -const CHANGE_TRACKING_FILE = '.agent-changes.json'; export function getGlobalConfigDir(): string { - return process.env.GIT_AGENT_SYNC_CONFIG - ? path.dirname(process.env.GIT_AGENT_SYNC_CONFIG) - : path.join(os.homedir(), GLOBAL_CONFIG_DIR); + return process.env.GIT_AGENT_SYNC_CONFIG ? path.dirname(process.env.GIT_AGENT_SYNC_CONFIG) : path.join(os.homedir(), GLOBAL_CONFIG_DIR); } - export function getGlobalConfigPath(): string { return process.env.GIT_AGENT_SYNC_CONFIG || path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE); } - export async function loadGlobalConfig(): Promise { const configPath = getGlobalConfigPath(); - if (await fs.pathExists(configPath)) { const config = await fs.readJson(configPath); return { ...getDefaultGlobalConfig(), ...config }; } - return getDefaultGlobalConfig(); } - export function getDefaultGlobalConfig(): GlobalConfig { - return { - workspacePath: process.env.GIT_AGENT_SYNC_PATH || './.agent-workspaces', - defaultBranch: 'main', - autoInstall: process.env.GIT_AGENT_SYNC_AUTO_INSTALL !== 'false', - templates: { - default: process.env.GIT_AGENT_SYNC_TEMPLATE || path.join(os.homedir(), GLOBAL_CONFIG_DIR, 'templates', 'default') - } - }; + return { workspacePath: process.env.GIT_AGENT_SYNC_PATH || './.agent-workspaces', defaultBranch: 'main', autoInstall: process.env.GIT_AGENT_SYNC_AUTO_INSTALL !== 'false', templates: { default: process.env.GIT_AGENT_SYNC_TEMPLATE || path.join(os.homedir(), GLOBAL_CONFIG_DIR, 'templates', 'default') } }; } - export async function saveGlobalConfig(config: GlobalConfig): Promise { - const configDir = getGlobalConfigDir(); - await fs.ensureDir(configDir); + await fs.ensureDir(getGlobalConfigDir()); await fs.writeJson(getGlobalConfigPath(), config, { spaces: 2 }); } - -export function getWorkspaceConfigPath(workspacePath: string): string { - return path.join(workspacePath, WORKSPACE_CONFIG_FILE); +export function getWorkspacePath(basePath: string, agentName: string): string { + return path.join(basePath, '.agent-workspaces', `agent-${agentName}`); } - export async function loadWorkspaceConfig(workspacePath: string): Promise { - const configPath = getWorkspaceConfigPath(workspacePath); - + const configPath = path.join(workspacePath, WORKSPACE_CONFIG_FILE); if (await fs.pathExists(configPath)) { return fs.readJson(configPath); } - return null; } - export async function saveWorkspaceConfig(workspacePath: string, config: WorkspaceConfig): Promise { - await fs.writeJson(getWorkspaceConfigPath(workspacePath), config, { spaces: 2 }); + const configPath = path.join(workspacePath, WORKSPACE_CONFIG_FILE); + await fs.writeJson(configPath, config, { spaces: 2 }); } - -export async function loadChangeTracking(workspacePath: string): Promise> { - const trackingPath = path.join(workspacePath, CHANGE_TRACKING_FILE); - - if (await fs.pathExists(trackingPath)) { - return fs.readJson(trackingPath); - } - - return {}; -} - -export async function saveChangeTracking( - workspacePath: string, - data: Record -): Promise { - await fs.writeJson(path.join(workspacePath, CHANGE_TRACKING_FILE), data, { spaces: 2 }); -} - -export function getWorkspacePath(basePath: string, agentName: string): string { - const workspacePath = process.env.GIT_AGENT_SYNC_PATH || './.agent-workspaces'; - return path.resolve(basePath, workspacePath, `agent-${agentName}`); -} - export async function getAllWorkspaces(basePath: string): Promise { - const globalConfig = await loadGlobalConfig(); - const workspaceRoot = path.resolve(basePath, globalConfig.workspacePath); - - if (!(await fs.pathExists(workspaceRoot))) { - return []; - } - + const workspaceRoot = path.join(basePath, '.agent-workspaces'); + if (!await fs.pathExists(workspaceRoot)) return []; + const entries = await fs.readdir(workspaceRoot); const workspaces: WorkspaceInfo[] = []; - const entries = await fs.readdir(workspaceRoot, { withFileTypes: true }); - for (const entry of entries) { - if (entry.isDirectory() && entry.name.startsWith('agent-')) { - const workspacePath = path.join(workspaceRoot, entry.name); - const config = await loadWorkspaceConfig(workspacePath); - - if (config) { - workspaces.push({ - name: config.agentName, - path: workspacePath, - branch: config.branch, - exists: await fs.pathExists(workspacePath), - hasChanges: false, - changeCount: 0 - }); + if (entry.startsWith('agent-')) { + const workspacePath = path.join(workspaceRoot, entry); + const stat = await fs.stat(workspacePath); + if (stat.isDirectory()) { + workspaces.push({ name: entry, path: workspacePath, branch: entry, exists: true, hasChanges: false, changeCount: 0 }); } } } - return workspaces; } - -export async function createEnvironmentFile( - workspacePath: string, - variables: Record -): Promise { - const lines: string[] = []; - - for (const [key, value] of Object.entries(variables)) { - const safeValue = value.includes(' ') ? `"${value}"` : value; - lines.push(`${key}=${safeValue}`); - } - - const envContent = lines.join('\n'); - await fs.writeFile(path.join(workspacePath, '.env'), envContent, 'utf-8'); +export function sanitizeAgentName(name: string): string { + return name.toLowerCase().replace(/[^a-z0-9-]/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, ''); } - -export async function readEnvironmentFile(workspacePath: string): Promise> { - const envPath = path.join(workspacePath, '.env'); - - if (!(await fs.pathExists(envPath))) { - return {}; - } - - const content = await fs.readFile(envPath, 'utf-8'); - const result: Record = {}; - - for (const line of content.split('\n')) { - const trimmed = line.trim(); - if (trimmed && !trimmed.startsWith('#')) { - const eqIndex = trimmed.indexOf('='); - if (eqIndex > 0) { - let value = trimmed.substring(eqIndex + 1); - if (value.startsWith('"') && value.endsWith('"')) { - value = value.slice(1, -1); - } - result[trimmed.substring(0, eqIndex)] = value; - } - } - } - - return result; +export function isValidAgentName(name: string): boolean { + return /^[a-z0-9][a-z0-9-]*[a-z0-9]$|^[a-z0-9]$/.test(name) && name.length >= 1 && name.length <= 100; +} +export async function isGitRepository(basePath: string): Promise { + try { + await fs.access(path.join(basePath, '.git'), fs.constants.R_OK); + return true; + } catch { return false; } +} +export async function hasUncommittedChanges(workspacePath: string): Promise { + const { execa } = await import('execa'); + try { + const result = await execa('git', ['status', '--porcelain'], { cwd: workspacePath }); + return result.stdout.trim().length > 0; + } catch { return false; } } - export async function detectPackageJson(workspacePath: string): Promise { return fs.pathExists(path.join(workspacePath, 'package.json')); } - +export async function installDependencies(workspacePath: string): Promise { + const { execa } = await import('execa'); + await execa('npm', ['install'], { cwd: workspacePath }); +} export async function readPackageJson(workspacePath: string): Promise<{ scripts?: Record } | null> { const pkgPath = path.join(workspacePath, 'package.json'); - if (await fs.pathExists(pkgPath)) { return fs.readJson(pkgPath); } - return null; } - -export async function copyTemplate( - templatePath: string, - workspacePath: string -): Promise { - if (await fs.pathExists(templatePath)) { - await fs.copy(templatePath, workspacePath); +export async function getGitUserInfo(): Promise<{ name: string; email: string }> { + const { execa } = await import('execa'); + try { + const nameResult = await execa('git', ['config', 'user.name']); + const emailResult = await execa('git', ['config', 'user.email']); + return { name: nameResult.stdout.trim(), email: emailResult.stdout.trim() }; + } catch { + return { name: 'Unknown', email: 'unknown@example.com' }; } -} - -export async function getTemplateDirectories(): Promise { - const configDir = getGlobalConfigDir(); - const templatesDir = path.join(configDir, 'templates'); - - if (!(await fs.pathExists(templatesDir))) { - return []; - } - - const entries = await fs.readdir(templatesDir, { withFileTypes: true }); - return entries - .filter(e => e.isDirectory()) - .map(e => e.name); -} - -export async function ensureDirectory(dirPath: string): Promise { - await fs.ensureDir(dirPath); -} - -export async function removeDirectory(dirPath: string): Promise { - if (await fs.pathExists(dirPath)) { - await fs.remove(dirPath); - } -} - -export function sanitizeAgentName(name: string): string { - return name - .toLowerCase() - .replace(/[^a-z0-9]/g, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, ''); -} - -export function isValidAgentName(name: string): boolean { - const sanitized = sanitizeAgentName(name); - return sanitized.length > 0 && sanitized.length <= 100 && /^[a-z][a-z0-9-]*[a-z0-9]$/.test(sanitized); -} - -export async function readFileContent(filePath: string): Promise { - if (!(await fs.pathExists(filePath))) { - throw new GitAgentSyncError(`File not found: ${filePath}`); - } - return fs.readFile(filePath, 'utf-8'); -} - -export async function writeFileContent(filePath: string, content: string): Promise { - await fs.ensureDir(path.dirname(filePath)); - await fs.writeFile(filePath, content, 'utf-8'); -} - -export { hasUncommittedChanges } from './git-utils'; -export { exportChangesAsPatch } from './git-utils'; -export { installDependencies } from './env-utils'; -export { getGitUserInfo } from './env-utils'; +} \ No newline at end of file