supamend
Version:
Pluggable DevSecOps Security Scanner with 10+ scanners and multiple reporting channels
222 lines • 7.89 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.GitManager = void 0;
const simple_git_1 = require("simple-git");
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const os = __importStar(require("os"));
const errors_1 = require("./errors");
class GitManager {
constructor() {
this.git = (0, simple_git_1.simpleGit)();
}
/**
* Clone a repository to a temporary directory
*/
async cloneRepository(repoUrl, token) {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'supamend-'));
try {
// Validate repository URL
if (!this.isValidRepoUrl(repoUrl)) {
throw new errors_1.GitError('Invalid repository URL format', 'clone', {
recoverable: false,
retryable: false,
context: { repo: repoUrl }
});
}
// Prepare clone URL with token if provided
let cloneUrl = repoUrl;
if (token && repoUrl.includes('github.com')) {
try {
const url = new URL(repoUrl);
url.username = token;
url.password = '';
cloneUrl = url.toString();
}
catch (urlError) {
throw new errors_1.GitError('Invalid repository URL', 'clone', {
recoverable: false,
retryable: false,
cause: urlError instanceof Error ? urlError : new Error(String(urlError)),
context: { repo: repoUrl }
});
}
}
// Clone repository with timeout
const clonePromise = this.git.clone(cloneUrl, tempDir, ['--depth', '1']);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Clone operation timed out')), 300000); // 5 minutes
});
await Promise.race([clonePromise, timeoutPromise]);
return tempDir;
}
catch (error) {
// Cleanup on error
await this.cleanup(tempDir);
const gitError = error instanceof Error ? error : new Error(String(error));
// Determine if this is a retryable error
const isRetryable = this.isRetryableCloneError(gitError);
throw new errors_1.GitError(`Failed to clone repository: ${gitError.message}`, 'clone', {
recoverable: true,
retryable: isRetryable,
cause: gitError,
context: { repo: repoUrl }
});
}
}
/**
* Check if repository URL is valid
*/
isValidRepoUrl(url) {
try {
const urlObj = new URL(url);
return urlObj.protocol === 'https:' || urlObj.protocol === 'http:';
}
catch {
return false;
}
}
/**
* Check if clone error is retryable
*/
isRetryableCloneError(error) {
const retryablePatterns = [
'network',
'timeout',
'temporary',
'connection refused',
'host unreachable',
'dns',
'rate limit'
];
return retryablePatterns.some(pattern => error.message.toLowerCase().includes(pattern));
}
/**
* Clean up temporary repository directory
*/
async cleanup(repoPath) {
// Don't cleanup current directory or relative paths
if (repoPath === '.' || repoPath === process.cwd() || !path.isAbsolute(repoPath)) {
return;
}
try {
if (await fs.pathExists(repoPath)) {
// Use a timeout for cleanup operations
const cleanupPromise = fs.remove(repoPath);
const timeoutPromise = new Promise((_, reject) => {
setTimeout(() => reject(new Error('Cleanup operation timed out')), 30000); // 30 seconds
});
await Promise.race([cleanupPromise, timeoutPromise]);
}
}
catch (error) {
const cleanupError = error instanceof Error ? error : new Error(String(error));
// Log cleanup errors but don't throw them
console.warn(`Failed to cleanup ${repoPath}:`, cleanupError.message);
// Try alternative cleanup method for stubborn files
try {
await this.forceCleanup(repoPath);
}
catch (forceError) {
console.warn(`Force cleanup also failed for ${repoPath}:`, forceError);
}
}
}
/**
* Force cleanup using alternative methods
*/
async forceCleanup(repoPath) {
try {
// Try to remove read-only files
await fs.chmod(repoPath, 0o777);
await fs.remove(repoPath);
}
catch {
// If all else fails, just log it
console.warn(`Could not force cleanup ${repoPath}`);
}
}
/**
* Get repository information
*/
async getRepoInfo(repoPath) {
const remote = await this.git.cwd(repoPath).getRemotes(true);
const branch = await this.git.cwd(repoPath).branch();
return {
remote: remote[0]?.refs?.fetch || '',
branch: branch.current
};
}
/**
* Check if current directory is a git repository
*/
async isGitRepository(cwd = process.cwd()) {
try {
await this.git.cwd(cwd).status();
return true;
}
catch {
return false;
}
}
/**
* Get current repository remote URL
*/
async getCurrentRepoRemote(cwd = process.cwd()) {
try {
const remotes = await this.git.cwd(cwd).getRemotes(true);
const originRemote = remotes.find(remote => remote.name === 'origin');
return originRemote?.refs?.fetch || originRemote?.refs?.push || null;
}
catch {
return null;
}
}
/**
* Get current repository branch
*/
async getCurrentRepoBranch(cwd = process.cwd()) {
try {
const branch = await this.git.cwd(cwd).branch();
return branch.current;
}
catch {
return null;
}
}
}
exports.GitManager = GitManager;
//# sourceMappingURL=git-manager.js.map