Initial commit: restructure to flat layout with ui/ and web/ at root
This commit is contained in:
76
web/server/lib/skills-catalog/git.js
Normal file
76
web/server/lib/skills-catalog/git.js
Normal file
@@ -0,0 +1,76 @@
|
||||
import { execFile } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execFileAsync = promisify(execFile);
|
||||
|
||||
const DEFAULT_TIMEOUT_MS = 60_000;
|
||||
const DEFAULT_MAX_BUFFER = 4 * 1024 * 1024;
|
||||
|
||||
export function looksLikeAuthError(message) {
|
||||
const text = String(message || '');
|
||||
return (
|
||||
/permission denied/i.test(text) ||
|
||||
/publickey/i.test(text) ||
|
||||
/could not read from remote repository/i.test(text) ||
|
||||
/authentication failed/i.test(text) ||
|
||||
/fatal: could not/i.test(text)
|
||||
);
|
||||
}
|
||||
|
||||
export async function runGit(args, options = {}) {
|
||||
const cwd = options.cwd;
|
||||
const timeoutMs = Number.isFinite(options.timeoutMs) ? options.timeoutMs : DEFAULT_TIMEOUT_MS;
|
||||
const maxBuffer = Number.isFinite(options.maxBuffer) ? options.maxBuffer : DEFAULT_MAX_BUFFER;
|
||||
|
||||
const identity = options.identity || null;
|
||||
const normalizedArgs = Array.isArray(args) ? args.slice() : [];
|
||||
|
||||
// Non-interactive git (avoid prompts / hangs)
|
||||
const env = {
|
||||
...process.env,
|
||||
GIT_TERMINAL_PROMPT: '0',
|
||||
};
|
||||
|
||||
if (identity?.sshKey) {
|
||||
const sshKeyPath = String(identity.sshKey).trim();
|
||||
if (sshKeyPath) {
|
||||
// Avoid interactive host key prompts; still safe against changed keys.
|
||||
const sshCommand = `ssh -i ${sshKeyPath} -o BatchMode=yes -o StrictHostKeyChecking=accept-new`;
|
||||
normalizedArgs.unshift(`core.sshCommand=${sshCommand}`);
|
||||
normalizedArgs.unshift('-c');
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const { stdout, stderr } = await execFileAsync('git', normalizedArgs, {
|
||||
cwd,
|
||||
env,
|
||||
timeout: timeoutMs,
|
||||
maxBuffer,
|
||||
});
|
||||
|
||||
return { ok: true, stdout: stdout || '', stderr: stderr || '' };
|
||||
} catch (error) {
|
||||
const err = error;
|
||||
const stdout = typeof err?.stdout === 'string' ? err.stdout : '';
|
||||
const stderr = typeof err?.stderr === 'string' ? err.stderr : '';
|
||||
const message = err instanceof Error ? err.message : String(err);
|
||||
|
||||
return {
|
||||
ok: false,
|
||||
stdout,
|
||||
stderr,
|
||||
message,
|
||||
code: typeof err?.code === 'number' ? err.code : null,
|
||||
signal: typeof err?.signal === 'string' ? err.signal : null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
export async function assertGitAvailable() {
|
||||
const result = await runGit(['--version'], { timeoutMs: 5_000 });
|
||||
if (!result.ok) {
|
||||
return { ok: false, error: { kind: 'gitUnavailable', message: 'Git is not available in PATH' } };
|
||||
}
|
||||
return { ok: true };
|
||||
}
|
||||
Reference in New Issue
Block a user