This commit is contained in:
127
src/config/index.ts
Normal file
127
src/config/index.ts
Normal file
@@ -0,0 +1,127 @@
|
||||
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 };
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user