/
arseniy985
/
QuantaAI
Обзор
Документация
Войти
/
arseniy985
/
QuantaAI
Код
Запросы
0
Задачи
Вики
Пакеты
0
Релизы
0
CI/CD
Аналитика
Безопасность
main
src/internal/modules/github/services/git-workspace.service.ts
196 строк
6 KB
Arseniy
feat: improve git push workflow and workspace metadata
28 янв 2026, 15:33
28 янв 2026, 15:33
48200e6
Код
Авторство
О чём код?
import fs from 'node:fs/promises'; import path from 'node:path'; import os from 'node:os'; import { injectable } from 'inversify'; import { runShellCommand } from '@/ai/tools/shell'; import { WorkspaceConfig } from '@/ai/tools/workspace'; type GitAuth = { token: string; }; type GitSetupResult = { initialized: boolean; committed: boolean; pushed: boolean; remoteSet: boolean; errors: string[]; }; const DEFAULT_ROOT = '.quanta-workspaces'; const DEFAULT_AUTHOR_NAME = 'Quanta Agent'; const DEFAULT_AUTHOR_EMAIL = 'agent@quanta.local'; const ASKPASS_PATH = path.join(os.tmpdir(), 'quanta-git-askpass.sh'); @injectable() export class GitWorkspaceService { constructor() {} async ensureWorkspaceRepo( projectId: string, remoteUrl: string | null, auth: GitAuth | null, config?: WorkspaceConfig, ): Promise<GitSetupResult> { const root = config?.root ?? DEFAULT_ROOT; const workspaceRoot = path.resolve(root, projectId); const errors: string[] = []; let initialized = false; let committed = false; let pushed = false; let remoteSet = false; try { const gitDir = path.join(workspaceRoot, '.git'); const exists = await this.pathExists(gitDir); if (!exists) { await runShellCommand(projectId, 'git init', { root }); await runShellCommand(projectId, 'git branch -M main', { root }); initialized = true; } await this.ensureAuthorConfig(projectId, root); } catch (error) { errors.push(`git init failed: ${(error as Error).message}`); return { initialized, committed, pushed, remoteSet, errors }; } if (remoteUrl) { try { const current = await this.getRemoteUrl(projectId, root); if (!current) { await runShellCommand(projectId, `git remote add origin ${remoteUrl}`, { root }); remoteSet = true; } else if (current.trim() !== remoteUrl.trim()) { await runShellCommand(projectId, `git remote set-url origin ${remoteUrl}`, { root }); remoteSet = true; } } catch (error) { errors.push(`git remote failed: ${(error as Error).message}`); } } if (initialized) { try { const status = await runShellCommand(projectId, 'git status --porcelain', { root }); if (status.trim()) { await runShellCommand(projectId, 'git add -A', { root }); try { await runShellCommand(projectId, 'git commit -m "chore: initialize workspace"', { root }); committed = true; } catch (error) { const message = (error as Error).message; if (!/nothing to commit/i.test(message)) { throw error; } } } } catch (error) { errors.push(`git commit failed: ${(error as Error).message}`); } if (remoteUrl && auth?.token) { try { const env = await this.buildAuthEnv(auth.token); await runShellCommand(projectId, 'git push -u origin main', { root, env, allowGitPush: true }); pushed = true; } catch (error) { errors.push(`git push failed: ${(error as Error).message}`); } } } return { initialized, committed, pushed, remoteSet, errors }; } async pushWorkspace( projectId: string, remoteUrl: string | null, auth: GitAuth | null, config?: WorkspaceConfig, ): Promise<GitSetupResult> { const root = config?.root ?? DEFAULT_ROOT; const errors: string[] = []; let pushed = false; let remoteSet = false; const initialized = false; const committed = false; if (!remoteUrl || !auth?.token) { return { initialized, committed, pushed, remoteSet, errors: ['Missing remote or auth token.'] }; } try { const current = await this.getRemoteUrl(projectId, root); if (!current) { await runShellCommand(projectId, `git remote add origin ${remoteUrl}`, { root }); remoteSet = true; } else if (current.trim() !== remoteUrl.trim()) { await runShellCommand(projectId, `git remote set-url origin ${remoteUrl}`, { root }); remoteSet = true; } } catch (error) { errors.push(`git remote failed: ${(error as Error).message}`); } try { const env = await this.buildAuthEnv(auth.token); await runShellCommand(projectId, 'git push -u origin main', { root, env, allowGitPush: true }); pushed = true; } catch (error) { errors.push(`git push failed: ${(error as Error).message}`); } return { initialized, committed, pushed, remoteSet, errors }; } private async ensureAuthorConfig(projectId: string, root: string) { const name = process.env.QUANTA_GIT_AUTHOR_NAME || DEFAULT_AUTHOR_NAME; const email = process.env.QUANTA_GIT_AUTHOR_EMAIL || DEFAULT_AUTHOR_EMAIL; await runShellCommand(projectId, `git config user.name "${name}"`, { root }); await runShellCommand(projectId, `git config user.email "${email}"`, { root }); } private async getRemoteUrl(projectId: string, root: string) { try { return await runShellCommand(projectId, 'git remote get-url origin', { root }); } catch { return null; } } private async buildAuthEnv(token: string) { await this.ensureAskPassScript(); return { GIT_ASKPASS: ASKPASS_PATH, GIT_AUTH_TOKEN: token, GIT_TERMINAL_PROMPT: '0', }; } private async ensureAskPassScript() { try { await fs.access(ASKPASS_PATH); return; } catch { // continue } const script = [ '#!/bin/sh', 'case "$1" in', '*Username*) echo "x-access-token" ;;', '*Password*) echo "$GIT_AUTH_TOKEN" ;;', '*) echo "$GIT_AUTH_TOKEN" ;;', 'esac', ].join('\n'); await fs.writeFile(ASKPASS_PATH, script, { encoding: 'utf8', mode: 0o700 }); } private async pathExists(target: string) { try { await fs.access(target); return true; } catch { return false; } } }