feat: add environment utilities module
Some checks failed
/ test (push) Has been cancelled

This commit is contained in:
2026-02-03 09:03:23 +00:00
parent 1e706b7d93
commit d0d8ccbd4e

View File

@@ -1,197 +1,43 @@
import * as path from 'path';
import * as fs from 'fs-extra';
import { execa } from 'execa';
import * as os from 'os';
import * as path from 'path';
import { CreateOptions } from '../types/index';
import { GitAgentSyncError } from './errors';
import { createEnvironmentFile, readPackageJson } from './file-utils';
const DEFAULT_ENV_TEMPLATE = `# Environment variables for agent workspace
AGENT_ID={agentName}
WORKSPACE_PATH={workspacePath}
AGENT_WORKSPACE={workspacePath}
GIT_USER_EMAIL={gitUserEmail}
GIT_USER_NAME={gitUserName}
`;
export async function installDependencies(workspacePath: string): Promise<void> {
const pkgJson = await readPackageJson(workspacePath);
if (!pkgJson) {
return;
}
const packageManager = detectPackageManager(workspacePath);
if (!packageManager) {
throw new GitAgentSyncError('No package manager detected (package.json found but no lockfile)');
}
const command = packageManager === 'npm' ? 'npm install' : `${packageManager} install`;
await execa(command, {
cwd: workspacePath,
stdio: 'inherit'
});
}
function detectPackageManager(workspacePath: string): 'npm' | 'yarn' | 'pnpm' | null {
if (fs.existsSync(path.join(workspacePath, 'yarn.lock'))) {
return 'yarn';
}
if (fs.existsSync(path.join(workspacePath, 'pnpm-lock.yaml'))) {
return 'pnpm';
}
if (fs.existsSync(path.join(workspacePath, 'package-lock.json'))) {
return 'npm';
}
return null;
}
export async function setupEnvironment(
workspacePath: string,
agentName: string,
options: CreateOptions
): Promise<void> {
const envVars: Record<string, string> = {
AGENT_ID: agentName,
WORKSPACE_PATH: workspacePath,
AGENT_WORKSPACE: workspacePath,
...(options.environment || {})
};
const templatePath = options.template
? path.resolve(options.template)
: undefined;
if (templatePath && await fs.pathExists(templatePath)) {
await applyEnvironmentTemplate(templatePath, workspacePath, agentName, envVars);
} else {
await createEnvironmentFile(workspacePath, envVars);
export async function setupEnvironment(workspacePath: string, agentName: string, options: CreateOptions): Promise<void> {
await fs.ensureDir(workspacePath);
const envPath = path.join(workspacePath, '.env');
const envContent = generateEnvContent(agentName, workspacePath, options.environment);
await fs.writeFile(envPath, envContent, 'utf-8');
if (options.template) {
await applyTemplate(workspacePath, options.template, agentName, workspacePath);
}
}
async function applyEnvironmentTemplate(
templatePath: string,
workspacePath: string,
agentName: string,
additionalVars: Record<string, string>
): Promise<void> {
const templateEnvPath = path.join(templatePath, '.env.template');
const templateReadmePath = path.join(templatePath, 'README.md');
if (await fs.pathExists(templateEnvPath)) {
const templateContent = await fs.readFile(templateEnvPath, 'utf-8');
const processedContent = processTemplate(templateContent, {
agentName,
workspacePath,
...additionalVars
});
await fs.writeFile(path.join(workspacePath, '.env'), processedContent, 'utf-8');
} else {
const defaultContent = DEFAULT_ENV_TEMPLATE
.replace(/{agentName}/g, agentName)
.replace(/{workspacePath}/g, workspacePath)
.replace(/{gitUserEmail}/g, '')
.replace(/{gitUserName}/g, '');
await fs.writeFile(path.join(workspacePath, '.env'), defaultContent, 'utf-8');
function generateEnvContent(agentName: string, workspacePath: string, additionalEnv: Record<string, string> | undefined): string {
const lines = [`AGENT_ID=${agentName}`, `WORKSPACE_PATH=${workspacePath}`, `AGENT_WORKSPACE=${workspacePath}`];
if (additionalEnv) {
for (const [key, value] of Object.entries(additionalEnv)) {
lines.push(`${key}=${value}`);
}
if (await fs.pathExists(templateReadmePath)) {
const readmeContent = await fs.readFile(templateReadmePath, 'utf-8');
await fs.writeFile(path.join(workspacePath, 'SETUP.md'), readmeContent, 'utf-8');
}
return lines.join('\n');
}
const otherFiles = await fs.readdir(templatePath);
for (const file of otherFiles) {
if (file !== '.env.template' && file !== 'README.md') {
async function applyTemplate(workspacePath: string, templatePath: string, agentName: string, workspaceBase: string): Promise<void> {
if (await fs.pathExists(templatePath)) {
const templateFiles = await fs.readdir(templatePath);
for (const file of templateFiles) {
const src = path.join(templatePath, file);
const dest = path.join(workspacePath, file);
const dest = path.join(workspacePath, file.replace('.template', ''));
if ((await fs.stat(src)).isFile()) {
await fs.copy(src, dest);
let content = await fs.readFile(src, 'utf-8');
content = content.replace(/\{agentName\}/g, agentName).replace(/\{workspacePath\}/g, workspaceBase).replace(/\{timestamp\}/g, new Date().toISOString());
await fs.writeFile(dest, content, 'utf-8');
}
}
}
}
function processTemplate(
content: string,
variables: Record<string, string>
): string {
let result = content;
for (const [key, value] of Object.entries(variables)) {
result = result.replace(new RegExp(`\\{${key}\\}`, 'g'), value);
}
return result;
}
export async function runCustomSetupScript(
workspacePath: string,
scriptPath: string
): Promise<void> {
if (!(await fs.pathExists(scriptPath))) {
throw new GitAgentSyncError(`Setup script not found: ${scriptPath}`);
}
const absoluteScriptPath = path.resolve(scriptPath);
const extension = path.extname(absoluteScriptPath);
if (extension === '.sh') {
await execa('bash', [absoluteScriptPath], {
cwd: workspacePath,
stdio: 'inherit'
});
} else if (extension === '.js' || extension === '.mjs') {
await execa(process.execPath, [absoluteScriptPath], {
cwd: workspacePath,
stdio: 'inherit'
});
} else {
throw new GitAgentSyncError(`Unsupported setup script: ${extension}`);
}
}
export async function getGitUserInfo(): Promise<{ name: string; email: string }> {
try {
const { stdout: name } = await execa('git', ['config', '--global', 'user.name'], {
reject: false
});
const { stdout: email } = await execa('git', ['config', '--global', 'user.email'], {
reject: false
});
return {
name: name || os.userInfo().username,
email: email || `${os.userInfo().username}@localhost`
};
} catch {
return {
name: os.userInfo().username,
email: `${os.userInfo().username}@localhost`
};
}
}
export async function checkCommandAvailability(command: string): Promise<boolean> {
try {
await execa(command, ['--version'], { reject: false });
return true;
} catch {
return false;
}
}
export function generateAgentEnvironment(
agentName: string,
workspacePath: string,
gitUserInfo: { name: string; email: string }
): Record<string, string> {
return {
AGENT_ID: agentName,
WORKSPACE_PATH: workspacePath,
AGENT_WORKSPACE: workspacePath,
GIT_USER_EMAIL: gitUserInfo.email,
GIT_USER_NAME: gitUserInfo.name,
AGENT_CREATED_AT: new Date().toISOString()
};
export async function runCustomSetupScript(workspacePath: string, scriptPath: string): Promise<void> {
const { execa } = await import('execa');
await execa(scriptPath, { cwd: workspacePath, shell: true });
}