Files
git-agent-sync/.src/config/index.ts

128 lines
3.9 KiB
TypeScript

import { z } from 'zod';
import * as fs from 'fs-extra';
import * as path from 'path';
import * as os from 'os';
import { GlobalConfig, WorkspaceConfig, CreateOptions } from '../types/index';
import { ConfigurationError, GitAgentSyncError } from '../utils/errors';
const GlobalConfigSchema = z.object({
workspacePath: z.string().default('./.agent-workspaces'),
defaultBranch: z.string().default('main'),
autoInstall: z.boolean().default(true),
templates: z.record(z.string()).default({ default: path.join(os.homedir(), '.git-agent-sync', 'templates', 'default') })
});
const WorkspaceConfigSchema = z.object({
agentName: z.string().min(1).max(100),
branch: z.string().min(1),
createdAt: z.string().datetime(),
mainBranch: z.string().default('main'),
environment: z.record(z.string()).default({}),
customSetupScript: z.string().optional()
});
export function validateGlobalConfig(config: unknown): GlobalConfig {
const result = GlobalConfigSchema.safeParse(config);
if (!result.success) {
throw new ConfigurationError('Invalid global configuration', {
errors: result.error.errors.map(e => ({
path: e.path.join('.'),
message: e.message
}))
});
}
return result.data;
}
export function validateWorkspaceConfig(config: unknown): WorkspaceConfig {
const result = WorkspaceConfigSchema.safeParse(config);
if (!result.success) {
throw new ConfigurationError('Invalid workspace configuration', {
errors: result.error.errors.map(e => ({
path: e.path.join('.'),
message: e.message
}))
});
}
return result.data;
}
export function validateCreateOptions(options: Partial<CreateOptions>): CreateOptions {
return {
path: options.path,
template: options.template,
installDeps: options.installDeps ?? true,
environment: options.environment || {}
};
}
export async function migrateConfig(
oldConfig: Record<string, unknown>,
version: string
): Promise<GlobalConfig> {
const migratedConfig: Record<string, unknown> = { ...oldConfig };
if (version === '1.0.0') {
if (migratedConfig.templates && typeof migratedConfig.templates === 'object') {
const templates = migratedConfig.templates as Record<string, unknown>;
if (templates['default'] === undefined) {
templates['default'] = path.join(os.homedir(), '.git-agent-sync', 'templates', 'default');
}
}
}
return validateGlobalConfig(migratedConfig);
}
export function getConfigVersion(): string {
return '1.0.0';
}
export async function validateWorkspacePath(
basePath: string,
workspacePath: string
): Promise<{ valid: boolean; error?: string }> {
try {
if (!path.isAbsolute(workspacePath)) {
return { valid: false, error: 'Workspace path must be absolute or relative to the git repository' };
}
if (!workspacePath.startsWith(basePath)) {
return {
valid: false,
error: 'Workspace path must be within the git repository'
};
}
if (await fs.pathExists(workspacePath)) {
const stat = await fs.stat(workspacePath);
if (!stat.isDirectory()) {
return { valid: false, error: 'Workspace path exists but is not a directory' };
}
}
return { valid: true };
} catch (error) {
return { valid: false, error: `Invalid workspace path: ${(error as Error).message}` };
}
}
export async function checkWorkspacePermissions(
workspacePath: string,
operations: ('read' | 'write' | 'execute')[]
): Promise<{ allowed: boolean; missing?: string }> {
try {
for (const op of operations) {
if (op === 'read' || op === 'execute') {
await fs.access(workspacePath, fs.constants.R_OK);
} else if (op === 'write') {
await fs.access(workspacePath, fs.constants.W_OK);
}
}
return { allowed: true };
} catch {
const missingOps = operations.join(', ');
return { allowed: false, missing: missingOps };
}
}