Add utility modules (git, file, env, diff utils)

This commit is contained in:
2026-02-03 07:55:21 +00:00
parent e152dc135d
commit 01a38eb824

239
.src/utils/file-utils.ts Normal file
View File

@@ -0,0 +1,239 @@
import * as fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
import { GlobalConfig, WorkspaceConfig, WorkspaceInfo } from '../types/index';
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);
}
export function getGlobalConfigPath(): string {
return process.env.GIT_AGENT_SYNC_CONFIG || path.join(getGlobalConfigDir(), GLOBAL_CONFIG_FILE);
}
export async function loadGlobalConfig(): Promise<GlobalConfig> {
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')
}
};
}
export async function saveGlobalConfig(config: GlobalConfig): Promise<void> {
const configDir = getGlobalConfigDir();
await fs.ensureDir(configDir);
await fs.writeJson(getGlobalConfigPath(), config, { spaces: 2 });
}
export function getWorkspaceConfigPath(workspacePath: string): string {
return path.join(workspacePath, WORKSPACE_CONFIG_FILE);
}
export async function loadWorkspaceConfig(workspacePath: string): Promise<WorkspaceConfig | null> {
const configPath = getWorkspaceConfigPath(workspacePath);
if (await fs.pathExists(configPath)) {
return fs.readJson(configPath);
}
return null;
}
export async function saveWorkspaceConfig(workspacePath: string, config: WorkspaceConfig): Promise<void> {
await fs.writeJson(getWorkspaceConfigPath(workspacePath), config, { spaces: 2 });
}
export async function loadChangeTracking(workspacePath: string): Promise<Record<string, any>> {
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<string, any>
): Promise<void> {
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<WorkspaceInfo[]> {
const globalConfig = await loadGlobalConfig();
const workspaceRoot = path.resolve(basePath, globalConfig.workspacePath);
if (!(await fs.pathExists(workspaceRoot))) {
return [];
}
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
});
}
}
}
return workspaces;
}
export async function createEnvironmentFile(
workspacePath: string,
variables: Record<string, string>
): Promise<void> {
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 async function readEnvironmentFile(workspacePath: string): Promise<Record<string, string>> {
const envPath = path.join(workspacePath, '.env');
if (!(await fs.pathExists(envPath))) {
return {};
}
const content = await fs.readFile(envPath, 'utf-8');
const result: Record<string, string> = {};
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 async function detectPackageJson(workspacePath: string): Promise<boolean> {
return fs.pathExists(path.join(workspacePath, 'package.json'));
}
export async function readPackageJson(workspacePath: string): Promise<{ scripts?: Record<string, string> } | 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<void> {
if (await fs.pathExists(templatePath)) {
await fs.copy(templatePath, workspacePath);
}
}
export async function getTemplateDirectories(): Promise<string[]> {
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<void> {
await fs.ensureDir(dirPath);
}
export async function removeDirectory(dirPath: string): Promise<void> {
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<string> {
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<void> {
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';