pushscript
Version:
AI-powered Git workflow automation with conventional commits, vulnerability scanning, and multi-provider LLM support
149 lines (131 loc) • 4.41 kB
JavaScript
/**
* Git utility functions for PushScript
* Handles git operations and status checking
*/
import { execSync } from 'child_process';
import readline from 'readline';
import { displayFileChanges, displayPushSummary } from '../utils/formatting.js';
/**
* Get the git status in a structured format
* @returns {Array} Array of objects with status and file information
*/
export function getGitStatus() {
const status = execSync('git status --porcelain').toString();
return status
.split('\n')
.filter(line => line.length > 0)
.map(line => ({
status: line.slice(0, 2).trim(),
file: line.slice(3)
}));
}
/**
* Categorize changes by type and affected areas
* @param {Array} changes Array of change objects from getGitStatus
* @returns {Object} Categorized changes
*/
export function categorizeChanges(changes) {
const categories = {
added: [],
modified: [],
deleted: [],
renamed: [],
components: new Set(),
features: new Set()
};
changes.forEach(change => {
// Extract component/feature name from path
const pathParts = change.file.split('/');
if (pathParts.includes('components')) {
const componentIndex = pathParts.indexOf('components');
if (pathParts[componentIndex + 1]) {
categories.components.add(pathParts[componentIndex + 1]);
}
}
if (pathParts.includes('features')) {
const featureIndex = pathParts.indexOf('features');
if (pathParts[featureIndex + 1]) {
categories.features.add(pathParts[featureIndex + 1]);
}
}
// Categorize by change type
if (change.status.includes('A')) categories.added.push(change.file);
else if (change.status.includes('M')) categories.modified.push(change.file);
else if (change.status.includes('D')) categories.deleted.push(change.file);
else if (change.status.includes('R')) categories.renamed.push(change.file);
});
return categories;
}
/**
* Generate a simple commit message based on changes
* @param {Array} changes Array of change objects
* @returns {string} Generated commit message
*/
export function generateSimpleCommitMessage(changes) {
const categories = categorizeChanges(changes);
displayFileChanges(changes);
let type = 'chore';
let scope = '';
let description = '';
// Determine if this is primarily modifications or additions
const isMainlyModifications = categories.modified.length > categories.added.length;
if (isMainlyModifications) {
if (categories.modified.some(f => f.includes('style') || f.includes('css'))) {
type = 'style';
description = 'update styling';
} else if (categories.components.size > 0) {
type = 'fix';
scope = 'ui';
description = 'update components';
} else {
type = 'fix';
description = 'update implementation';
}
} else if (categories.added.length > 0) {
type = 'feat';
if (categories.components.size === 1) {
scope = 'ui';
description = `add ${Array.from(categories.components)[0]}`;
} else {
description = 'add new features';
}
}
const message = scope ? `${type}(${scope}): ${description}` : `${type}: ${description}`;
console.log('Commit:', message);
return message;
}
/**
* Get the current git branch
* @returns {string|null} Current branch name or null if not detected
*/
export async function getCurrentBranch() {
try {
const branch = execSync('git symbolic-ref --short HEAD').toString().trim();
return branch;
} catch (error) {
console.warn('\x1b[33mWarning: Unable to detect current branch.\x1b[0m');
return null;
}
}
/**
* Confirm push operation with the user
* @param {string} commitMessage Commit message to be used
* @param {string} branchName Branch to push to
* @returns {Promise<boolean>} True if user confirms, false otherwise
*/
export async function confirmPush(commitMessage, branchName) {
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
// Get file changes for display
const changes = getGitStatus();
// Show summary of what will happen using new formatting
displayPushSummary(commitMessage, branchName, changes);
return new Promise((resolve) => {
rl.question('\x1b[33mProceed with commit and push? (Y/n): \x1b[0m', (answer) => {
rl.close();
resolve(answer.toLowerCase() !== 'n');
});
});
}