Initial commit: git-agent-sync CLI tool
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