cfn-forge
Version:
CloudFormation deployment automation tool with git-based workflows
228 lines (204 loc) • 6 kB
JavaScript
/**
* Git Manager Implementation
* Cross-platform git operations using simple-git
*/
const simpleGit = require('simple-git');
const path = require('path');
const IGitManager = require('../interfaces/IGitManager');
class GitManager extends IGitManager {
constructor(workingDir = process.cwd()) {
super();
this.git = simpleGit(workingDir);
this.workingDir = workingDir;
}
/**
* Get the current commit hash (short version)
* @returns {Promise<string>} Short commit hash (8 chars)
*/
async getCommitHash() {
try {
const log = await this.git.log(['-1', '--format=%H']);
return log.latest.hash.substring(0, 8);
} catch (error) {
throw new Error(`Failed to get commit hash: ${error.message}`);
}
}
/**
* Get the current branch name
* @returns {Promise<string>} Current branch name
*/
async getCurrentBranch() {
try {
const branch = await this.git.branch();
return branch.current || 'main';
} catch (error) {
throw new Error(`Failed to get current branch: ${error.message}`);
}
}
/**
* Get the latest tag
* @returns {Promise<string|null>} Latest tag or null if none
*/
async getLatestTag() {
try {
const tags = await this.git.tags();
return tags.latest || null;
} catch (error) {
// Return null if no tags exist instead of throwing
return null;
}
}
/**
* Check if current commit is tagged
* @returns {Promise<boolean>} True if on a tagged commit
*/
async isOnTaggedCommit() {
try {
const latestTag = await this.getLatestTag();
if (!latestTag) {
return false;
}
const tagCommit = await this.git.revparse([latestTag]);
const currentCommit = await this.git.revparse(['HEAD']);
return tagCommit === currentCommit;
} catch (error) {
return false;
}
}
/**
* Check if there are uncommitted changes
* @returns {Promise<boolean>} True if working directory has changes
*/
async hasUncommittedChanges() {
try {
const status = await this.git.status();
return !status.isClean();
} catch (error) {
throw new Error(`Failed to check for uncommitted changes: ${error.message}`);
}
}
/**
* Get list of changed files
* @returns {Promise<string[]>} Array of changed file paths
*/
async getChangedFiles() {
try {
const status = await this.git.status();
const changed = [
...status.modified,
...status.not_added,
...status.created,
...status.deleted,
...status.renamed.map((r) => r.to),
];
return changed;
} catch (error) {
throw new Error(`Failed to get changed files: ${error.message}`);
}
}
/**
* Get repository root directory
* @returns {Promise<string>} Absolute path to repo root
*/
async getRepoRoot() {
try {
const root = await this.git.revparse(['--show-toplevel']);
return path.resolve(root);
} catch (error) {
throw new Error(`Failed to get repository root: ${error.message}`);
}
}
/**
* Check if path is inside a git repository
* @returns {Promise<boolean>} True if inside a git repo
*/
async isGitRepository() {
try {
await this.git.revparse(['--git-dir']);
return true;
} catch (error) {
return false;
}
}
/**
* Get commit message for a specific commit
* @param {string} commitHash - Commit hash
* @returns {Promise<string>} Commit message
*/
async getCommitMessage(commitHash) {
try {
const log = await this.git.show([commitHash, '--format=%B', '--no-patch']);
return log.trim();
} catch (error) {
throw new Error(`Failed to get commit message: ${error.message}`);
}
}
/**
* Get list of commits between two refs
* @param {string} fromRef - Starting reference
* @param {string} toRef - Ending reference
* @returns {Promise<Array>} Array of commit objects
*/
async getCommitsBetween(fromRef, toRef) {
try {
const log = await this.git.log({
from: fromRef,
to: toRef,
});
return log.all.map((commit) => ({
hash: commit.hash,
shortHash: commit.hash.substring(0, 8),
message: commit.message,
author: commit.author_name,
email: commit.author_email,
date: commit.date,
}));
} catch (error) {
throw new Error(`Failed to get commits between refs: ${error.message}`);
}
}
/**
* Check if deployment should be triggered based on configuration
* @param {Object} config - Deployment configuration
* @returns {Promise<boolean>} True if deployment should proceed
*/
async shouldDeploy(config = {}) {
const deployOnTags = config.deployOnTags || false;
if (deployOnTags) {
const isTagged = await this.isOnTaggedCommit();
return isTagged;
}
// Default: deploy on any commit
return true;
}
/**
* Get deployment trigger information
* @returns {Promise<Object>} Trigger information
*/
async getDeploymentTrigger() {
const isTagged = await this.isOnTaggedCommit();
const branch = await this.getCurrentBranch();
const commitHash = await this.getCommitHash();
const latestTag = await this.getLatestTag();
return {
type: isTagged ? 'tag' : 'commit',
branch,
commitHash,
tag: isTagged ? latestTag : null,
timestamp: new Date().toISOString(),
};
}
/**
* Create a deployment hash for state tracking
* @returns {Promise<string>} Deployment hash
*/
async createDeploymentHash() {
const commitHash = await this.getCommitHash();
const branch = await this.getCurrentBranch();
const timestamp = Date.now();
// Create a simple hash for deployment state tracking
const deploymentId = `${branch}-${commitHash}-${timestamp}`;
return Buffer.from(deploymentId).toString('base64').substring(0, 16);
}
}
module.exports = GitManager;