import { ToolResult } from '../utils/operation-error-handler';
export interface ToolParams {
projectPath: string;
provider?: 'github' | 'gitea' | 'both';
[key: string]: any;
}
import { GitCommandExecutor } from '../utils/git-command-executor';
import { OperationErrorHandler } from '../utils/operation-error-handler';
import { ParameterValidator } from '../utils/parameter-validator';
import { ProviderOperationHandler } from '../providers/provider-operation-handler';
import { Logger } from '../utils/logger';
import { configManager } from '../config.js';
export interface GitUpdateParams extends ToolParams {
projectPath: string;
action: 'update' | 'history' | 'changelog' | 'track' | 'sync-providers' | 'status' | 'rollback' | 'compare';
// Update parameters
updateType?: 'all' | 'dependencies' | 'code' | 'docs' | 'config';
autoCommit?: boolean;
commitMessage?: string;
createTag?: boolean;
tagName?: string;
// History parameters
since?: string;
until?: string;
author?: string;
format?: 'json' | 'markdown' | 'text';
// Changelog parameters
changelogPath?: string;
version?: string;
includeCommits?: boolean;
groupByType?: boolean;
// Track parameters
trackFile?: string;
trackPattern?: string;
watchMode?: boolean;
// Sync providers parameters
providers?: string[];
forceSync?: boolean;
// Rollback parameters
rollbackTo?: string;
rollbackType?: 'commit' | 'tag' | 'version';
// Compare parameters
compareWith?: string;
compareType?: 'commit' | 'tag' | 'branch' | 'provider';
}
export interface UpdateHistory {
timestamp: string;
action: string;
details: string;
files: string[];
commitHash?: string;
tagName?: string;
provider?: string;
}
export interface ChangelogEntry {
version: string;
date: string;
type: 'feature' | 'fix' | 'breaking' | 'docs' | 'refactor' | 'perf' | 'test' | 'chore';
description: string;
commits: string[];
author: string;
files: string[];
}
export interface ProjectStatus {
localStatus: {
branch: string;
lastCommit: string;
uncommittedChanges: number;
untrackedFiles: number;
};
providerStatus: {
[provider: string]: {
lastSync: string;
ahead: number;
behind: number;
conflicts: boolean;
};
};
updateHistory: UpdateHistory[];
}
export class GitUpdateTool {
private gitExecutor: GitCommandExecutor;
private providerHandler?: ProviderOperationHandler;
private logger: Logger;
private updateHistory: UpdateHistory[] = [];
constructor(providerConfig?: any) {
this.gitExecutor = new GitCommandExecutor();
this.logger = new Logger();
if (providerConfig) {
this.providerHandler = new ProviderOperationHandler(providerConfig);
}
}
static getToolSchema() {
return {
name: 'git-update',
description: 'Advanced project update tool with history tracking, changelog generation, and multi-provider synchronization',
inputSchema: {
type: 'object',
properties: {
action: {
type: 'string',
enum: ['update', 'history', 'changelog', 'track', 'sync-providers', 'status', 'rollback', 'compare'],
description: 'Update operation to perform'
},
projectPath: {
type: 'string',
description: 'Path to the Git repository'
},
provider: {
type: 'string',
enum: ['github', 'gitea', 'both'],
description: 'Provider for remote operations'
},
updateType: {
type: 'string',
enum: ['all', 'dependencies', 'code', 'docs', 'config'],
description: 'Type of update to perform'
},
autoCommit: {
type: 'boolean',
description: 'Automatically commit changes'
},
commitMessage: {
type: 'string',
description: 'Custom commit message'
},
createTag: {
type: 'boolean',
description: 'Create a tag after update'
},
tagName: {
type: 'string',
description: 'Name for the tag'
},
since: {
type: 'string',
description: 'Start date for history (ISO format or relative)'
},
until: {
type: 'string',
description: 'End date for history (ISO format or relative)'
},
author: {
type: 'string',
description: 'Filter by author'
},
format: {
type: 'string',
enum: ['json', 'markdown', 'text'],
description: 'Output format for history'
},
changelogPath: {
type: 'string',
description: 'Path to changelog file'
},
version: {
type: 'string',
description: 'Version for changelog entry'
},
includeCommits: {
type: 'boolean',
description: 'Include commit details in changelog'
},
groupByType: {
type: 'boolean',
description: 'Group changelog entries by type'
},
trackFile: {
type: 'string',
description: 'File to track for changes'
},
trackPattern: {
type: 'string',
description: 'Pattern to track for changes'
},
watchMode: {
type: 'boolean',
description: 'Enable watch mode for tracking'
},
providers: {
type: 'array',
items: { type: 'string' },
description: 'Providers to sync with'
},
forceSync: {
type: 'boolean',
description: 'Force synchronization'
},
rollbackTo: {
type: 'string',
description: 'Target for rollback (commit, tag, or version)'
},
rollbackType: {
type: 'string',
enum: ['commit', 'tag', 'version'],
description: 'Type of rollback target'
},
compareWith: {
type: 'string',
description: 'Target to compare with'
},
compareType: {
type: 'string',
enum: ['commit', 'tag', 'branch', 'provider'],
description: 'Type of comparison target'
}
},
required: ['action', 'projectPath']
}
};
}
async execute(params: GitUpdateParams): Promise<ToolResult> {
const startTime = Date.now();
this.logger.info(`Executing git-update ${params.action}`);
try {
// Validate parameters
const validation = ParameterValidator.validateToolParams('git-update', params);
if (!validation.isValid) {
return OperationErrorHandler.createToolError(
'VALIDATION_ERROR',
'Parameter validation failed',
params.action,
validation,
validation.suggestions
);
}
// Route to appropriate handler
switch (params.action) {
case 'update':
return await this.handleUpdate(params, startTime);
case 'history':
return await this.handleHistory(params, startTime);
case 'changelog':
return await this.handleChangelog(params, startTime);
case 'track':
return await this.handleTrack(params, startTime);
case 'sync-providers':
return await this.handleSyncProviders(params, startTime);
case 'status':
return await this.handleStatus(params, startTime);
case 'rollback':
return await this.handleRollback(params, startTime);
case 'compare':
return await this.handleCompare(params, startTime);
default:
return OperationErrorHandler.createToolError(
'UNSUPPORTED_OPERATION',
`Update operation '${params.action}' is not supported`,
params.action,
{},
['Use one of: update, history, changelog, track, sync-providers, status, rollback, compare']
);
}
} catch (error) {
this.logger.error('Error executing git-update');
return OperationErrorHandler.createToolError(
'EXECUTION_ERROR',
`Failed to execute git-update ${params.action}: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ error: error instanceof Error ? error.message : String(error) },
['Check project path and Git repository status']
);
}
}
private async handleUpdate(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const updateType = params.updateType || 'all';
const changes: string[] = [];
const files: string[] = [];
this.logger.info(`Performing ${updateType} update`);
// Perform update based on type
switch (updateType) {
case 'dependencies':
await this.updateDependencies(params.projectPath);
changes.push('Updated dependencies');
break;
case 'code':
await this.updateCode(params.projectPath);
changes.push('Updated code files');
break;
case 'docs':
await this.updateDocs(params.projectPath);
changes.push('Updated documentation');
break;
case 'config':
await this.updateConfig(params.projectPath);
changes.push('Updated configuration');
break;
case 'all':
default:
await this.updateAll(params.projectPath);
changes.push('Updated all project components');
break;
}
// Get changed files
const statusResult = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], params.projectPath);
if (statusResult.success) {
const changedFiles = statusResult.stdout
.split('\n')
.filter(line => line.trim())
.map(line => line.substring(3).trim());
files.push(...changedFiles);
}
// Auto commit if requested
let commitHash: string | undefined;
if (params.autoCommit !== false && files.length > 0) {
const commitMessage = params.commitMessage || `Update: ${changes.join(', ')}`;
const addResult = await this.gitExecutor.executeGitCommand('add', ['.'], params.projectPath);
if (addResult.success) {
const commitResult = await this.gitExecutor.executeGitCommand('commit', ['-m', commitMessage], params.projectPath);
if (commitResult.success) {
commitHash = await this.getCurrentCommitHash(params.projectPath);
}
}
}
// Create tag if requested
let tagName: string | undefined;
if (params.createTag && commitHash) {
tagName = params.tagName || `update-${Date.now()}`;
await this.gitExecutor.executeGitCommand('tag', [tagName], params.projectPath);
}
// Record in history
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'update',
details: changes.join(', '),
files,
commitHash,
tagName
};
this.updateHistory.push(historyEntry);
const executionTime = Date.now() - startTime;
return {
success: true,
data: {
updateType,
changes,
files,
commitHash,
tagName,
executionTime
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'UPDATE_ERROR',
`Failed to perform update: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ updateType: params.updateType },
['Check repository status and permissions']
);
}
}
private async handleHistory(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const since = params.since || '1 week ago';
const until = params.until || 'now';
const author = params.author;
const format = params.format || 'json';
this.logger.info(`Getting update history since ${since} until ${until}`);
// Get Git log
const logArgs = ['--since', since, '--until', until];
if (author) {
logArgs.push('--author', author);
}
logArgs.push('--pretty=format:%H|%an|%ad|%s', '--date=iso');
const logResult = await this.gitExecutor.executeGitCommand('log', logArgs, params.projectPath);
if (!logResult.success) {
throw new Error('Failed to get Git history');
}
const commits = logResult.stdout
.split('\n')
.filter(line => line.trim())
.map(line => {
const [hash, author, date, message] = line.split('|');
return {
hash: hash.substring(0, 8),
fullHash: hash,
author,
date,
message
};
});
// Get update history
const filteredHistory = this.updateHistory.filter(entry => {
const entryDate = new Date(entry.timestamp);
const sinceDate = new Date(since);
const untilDate = new Date(until);
return entryDate >= sinceDate && entryDate <= untilDate;
});
let output: any;
switch (format) {
case 'markdown':
output = this.formatHistoryMarkdown(commits, filteredHistory);
break;
case 'text':
output = this.formatHistoryText(commits, filteredHistory);
break;
case 'json':
default:
output = {
commits,
updateHistory: filteredHistory,
summary: {
totalCommits: commits.length,
totalUpdates: filteredHistory.length,
period: { since, until }
}
};
break;
}
const executionTime = Date.now() - startTime;
return {
success: true,
data: output,
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'HISTORY_ERROR',
`Failed to get history: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ since: params.since, until: params.until },
['Check date format and repository access']
);
}
}
private async handleChangelog(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const changelogPath = params.changelogPath || 'CHANGELOG.md';
const version = params.version || await this.getCurrentVersion(params.projectPath);
const includeCommits = params.includeCommits !== false;
const groupByType = params.groupByType !== false;
this.logger.info(`Generating changelog for version ${version}`);
// Get commits since last tag
const lastTag = await this.getLastTag(params.projectPath);
const sinceRef = lastTag || 'HEAD~10';
const logResult = await this.gitExecutor.executeGitCommand('log', [
`${sinceRef}..HEAD`,
'--pretty=format:%H|%an|%ad|%s',
'--date=iso'
], params.projectPath);
const commits = logResult.stdout
.split('\n')
.filter(line => line.trim())
.map(line => {
const [hash, author, date, message] = line.split('|');
return {
hash: hash.substring(0, 8),
fullHash: hash,
author,
date,
message,
type: this.parseCommitType(message)
};
});
// Generate changelog entry
const changelogEntry: ChangelogEntry = {
version,
date: new Date().toISOString().split('T')[0],
type: this.determineReleaseType(commits) as 'feature' | 'fix' | 'breaking' | 'docs' | 'refactor' | 'perf' | 'test' | 'chore',
description: this.generateReleaseDescription(commits),
commits: includeCommits ? commits.map(c => c.message) : [],
author: commits.length > 0 ? commits[0].author : 'Unknown',
files: await this.getChangedFiles(sinceRef, params.projectPath)
};
// Generate changelog content
const changelogContent = this.generateChangelogContent(changelogEntry, groupByType);
// Write to file
const fs = require('fs');
const path = require('path');
const fullPath = path.join(params.projectPath, changelogPath);
let existingContent = '';
if (fs.existsSync(fullPath)) {
existingContent = fs.readFileSync(fullPath, 'utf8');
}
const newContent = this.mergeChangelogContent(changelogContent, existingContent);
fs.writeFileSync(fullPath, newContent);
const executionTime = Date.now() - startTime;
return {
success: true,
data: {
version,
changelogPath,
entry: changelogEntry,
content: changelogContent
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'CHANGELOG_ERROR',
`Failed to generate changelog: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ version: params.version, changelogPath: params.changelogPath },
['Check repository status and file permissions']
);
}
}
private async handleTrack(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const trackFile = params.trackFile;
const trackPattern = params.trackPattern;
const watchMode = params.watchMode || false;
this.logger.info(`Tracking changes${trackFile ? ` for file: ${trackFile}` : ''}${trackPattern ? ` with pattern: ${trackPattern}` : ''}`);
if (trackFile) {
// Track specific file
const fileStatus = await this.trackFile(trackFile, params.projectPath);
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'track',
details: `Tracked file: ${trackFile}`,
files: [trackFile],
provider: params.provider || 'both'
};
this.updateHistory.push(historyEntry);
return {
success: true,
data: {
trackedFile: trackFile,
status: fileStatus,
watchMode
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else if (trackPattern) {
// Track files matching pattern
const matchingFiles = await this.trackPattern(trackPattern, params.projectPath);
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'track',
details: `Tracked pattern: ${trackPattern}`,
files: matchingFiles,
provider: params.provider || 'both'
};
this.updateHistory.push(historyEntry);
return {
success: true,
data: {
pattern: trackPattern,
matchingFiles,
count: matchingFiles.length,
watchMode
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
} else {
// Track all changes
const allChanges = await this.trackAllChanges(params.projectPath);
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'track',
details: 'Tracked all changes',
files: allChanges,
provider: params.provider || 'both'
};
this.updateHistory.push(historyEntry);
return {
success: true,
data: {
allChanges,
count: allChanges.length,
watchMode
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime: Date.now() - startTime
}
};
}
} catch (error) {
return OperationErrorHandler.createToolError(
'TRACK_ERROR',
`Failed to track changes: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ trackFile: params.trackFile, trackPattern: params.trackPattern },
['Check file paths and repository status']
);
}
}
private async handleSyncProviders(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const providers = params.providers || ['github', 'gitea'];
const forceSync = params.forceSync || false;
this.logger.info(`Syncing with providers: ${providers.join(', ')}`);
const syncResults: any = {};
for (const provider of providers) {
try {
if (this.providerHandler) {
// Sync with provider
const syncResult = await this.providerHandler.executeOperation({
provider: provider as 'github' | 'gitea' | 'both',
operation: 'sync',
parameters: {
projectPath: params.projectPath,
force: forceSync
},
requiresAuth: true,
isRemoteOperation: true
});
syncResults[provider] = {
success: true,
result: syncResult
};
} else {
syncResults[provider] = {
success: false,
error: 'Provider handler not configured'
};
}
} catch (error) {
syncResults[provider] = {
success: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'sync-providers',
details: `Synced with providers: ${providers.join(', ')}`,
files: [],
provider: providers.join(',')
};
this.updateHistory.push(historyEntry);
const executionTime = Date.now() - startTime;
return {
success: true,
data: {
providers,
results: syncResults,
summary: {
total: providers.length,
successful: Object.values(syncResults).filter((r: any) => r.success).length,
failed: Object.values(syncResults).filter((r: any) => !r.success).length
}
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'SYNC_ERROR',
`Failed to sync providers: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ providers: params.providers },
['Check provider configuration and network connectivity']
);
}
}
private async handleStatus(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
this.logger.info('Getting project status');
// Get local status
const branchResult = await this.gitExecutor.executeGitCommand('branch', ['--show-current'], params.projectPath);
const lastCommitResult = await this.gitExecutor.executeGitCommand('log', ['-1', '--format=%H|%an|%ad|%s', '--date=iso'], params.projectPath);
const statusResult = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], params.projectPath);
const localStatus = {
branch: branchResult.success ? branchResult.stdout.trim() : 'unknown',
lastCommit: lastCommitResult.success ? lastCommitResult.stdout.split('|')[0].substring(0, 8) : 'unknown',
uncommittedChanges: statusResult.success ? statusResult.stdout.split('\n').filter(line => line.trim()).length : 0,
untrackedFiles: statusResult.success ? statusResult.stdout.split('\n').filter(line => line.startsWith('??')).length : 0
};
// Get provider status
const providerStatus: any = {};
if (this.providerHandler) {
const providers = ['github', 'gitea'];
for (const provider of providers) {
try {
const statusResult = await this.providerHandler.executeOperation({
provider: provider as 'github' | 'gitea' | 'both',
operation: 'status',
parameters: {
projectPath: params.projectPath
},
requiresAuth: true,
isRemoteOperation: true
});
providerStatus[provider] = {
lastSync: new Date().toISOString(),
ahead: 0,
behind: 0,
conflicts: false,
...statusResult
};
} catch (error) {
providerStatus[provider] = {
lastSync: 'never',
ahead: 0,
behind: 0,
conflicts: false,
error: error instanceof Error ? error.message : 'Unknown error'
};
}
}
}
const projectStatus: ProjectStatus = {
localStatus,
providerStatus,
updateHistory: this.updateHistory.slice(-10) // Last 10 entries
};
const executionTime = Date.now() - startTime;
return {
success: true,
data: projectStatus,
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'STATUS_ERROR',
`Failed to get status: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{},
['Check repository status and provider configuration']
);
}
}
private async handleRollback(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const rollbackTo = params.rollbackTo;
const rollbackType = params.rollbackType || 'commit';
if (!rollbackTo) {
throw new Error('rollbackTo parameter is required');
}
this.logger.info(`Rolling back to ${rollbackType}: ${rollbackTo}`);
let targetRef: string;
switch (rollbackType) {
case 'tag':
targetRef = rollbackTo;
break;
case 'version':
targetRef = `v${rollbackTo}`;
break;
case 'commit':
default:
targetRef = rollbackTo;
break;
}
// Perform rollback
const resetResult = await this.gitExecutor.executeGitCommand('reset', ['--hard', targetRef], params.projectPath);
if (!resetResult.success) {
throw new Error(`Failed to reset to ${targetRef}`);
}
const historyEntry: UpdateHistory = {
timestamp: new Date().toISOString(),
action: 'rollback',
details: `Rolled back to ${rollbackType}: ${rollbackTo}`,
files: [],
commitHash: targetRef
};
this.updateHistory.push(historyEntry);
const executionTime = Date.now() - startTime;
return {
success: true,
data: {
rollbackTo,
rollbackType,
targetRef,
resetResult: resetResult.stdout
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'ROLLBACK_ERROR',
`Failed to rollback: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ rollbackTo: params.rollbackTo, rollbackType: params.rollbackType },
['Check target reference and repository status']
);
}
}
private async handleCompare(params: GitUpdateParams, startTime: number): Promise<ToolResult> {
try {
const compareWith = params.compareWith;
const compareType = params.compareType || 'commit';
if (!compareWith) {
throw new Error('compareWith parameter is required');
}
this.logger.info(`Comparing with ${compareType}: ${compareWith}`);
// Get diff
const diffResult = await this.gitExecutor.executeGitCommand('diff', [compareWith, 'HEAD'], params.projectPath);
if (!diffResult.success) {
throw new Error('Failed to get diff');
}
// Get commit comparison
const logResult = await this.gitExecutor.executeGitCommand('log', [
`${compareWith}..HEAD`,
'--pretty=format:%H|%an|%ad|%s',
'--date=iso'
], params.projectPath);
const commits = logResult.stdout
.split('\n')
.filter(line => line.trim())
.map(line => {
const [hash, author, date, message] = line.split('|');
return {
hash: hash.substring(0, 8),
fullHash: hash,
author,
date,
message
};
});
// Get file changes
const filesResult = await this.gitExecutor.executeGitCommand('diff', [
'--name-only',
compareWith,
'HEAD'
], params.projectPath);
const changedFiles = filesResult.stdout
.split('\n')
.filter(line => line.trim());
const executionTime = Date.now() - startTime;
return {
success: true,
data: {
compareWith,
compareType,
commits,
changedFiles,
diffStats: this.parseDiffStats(diffResult.stdout),
summary: {
commitsAhead: commits.length,
filesChanged: changedFiles.length
}
},
metadata: {
operation: params.action,
timestamp: new Date().toISOString(),
executionTime
}
};
} catch (error) {
return OperationErrorHandler.createToolError(
'COMPARE_ERROR',
`Failed to compare: ${error instanceof Error ? error.message : 'Unknown error'}`,
params.action,
{ compareWith: params.compareWith, compareType: params.compareType },
['Check target reference and repository status']
);
}
}
// Helper methods
private async updateDependencies(projectPath: string): Promise<void> {
// Update package.json dependencies
const packageJsonPath = `${projectPath}/package.json`;
const fs = require('fs');
if (fs.existsSync(packageJsonPath)) {
// This is a placeholder - in a real implementation, you would:
// 1. Parse package.json
// 2. Check for outdated dependencies
// 3. Update to latest versions
// 4. Run npm update or similar
this.logger.info('Updating dependencies');
}
}
private async updateCode(projectPath: string): Promise<void> {
// Update code files based on patterns or rules
this.logger.info('Updating code files');
}
private async updateDocs(projectPath: string): Promise<void> {
// Update documentation files
this.logger.info('Updating documentation');
}
private async updateConfig(projectPath: string): Promise<void> {
// Update configuration files
this.logger.info('Updating configuration');
}
private async updateAll(projectPath: string): Promise<void> {
await this.updateDependencies(projectPath);
await this.updateCode(projectPath);
await this.updateDocs(projectPath);
await this.updateConfig(projectPath);
}
private async getCurrentCommitHash(projectPath: string): Promise<string> {
const result = await this.gitExecutor.executeGitCommand('rev-parse', ['HEAD'], projectPath);
return result.success ? result.stdout.trim() : '';
}
private async getCurrentVersion(projectPath: string): Promise<string> {
const packageJsonPath = `${projectPath}/package.json`;
const fs = require('fs');
if (fs.existsSync(packageJsonPath)) {
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
return packageJson.version || '1.0.0';
}
return '1.0.0';
}
private async getLastTag(projectPath: string): Promise<string | null> {
const result = await this.gitExecutor.executeGitCommand('describe', ['--tags', '--abbrev=0'], projectPath);
return result.success ? result.stdout.trim() : null;
}
private async getChangedFiles(sinceRef: string, projectPath: string): Promise<string[]> {
const result = await this.gitExecutor.executeGitCommand('diff', [
'--name-only',
sinceRef,
'HEAD'
], projectPath);
return result.success
? result.stdout.split('\n').filter(line => line.trim())
: [];
}
private parseCommitType(message: string): string {
const match = message.match(/^(feat|fix|docs|style|refactor|test|chore|perf|ci|build|revert)(\(.+\))?:/);
return match ? match[1] : 'other';
}
private determineReleaseType(commits: any[]): string {
if (commits.some(c => c.type === 'breaking' || c.message.includes('BREAKING CHANGE'))) {
return 'breaking';
}
if (commits.some(c => c.type === 'feat')) {
return 'feature';
}
if (commits.some(c => c.type === 'fix')) {
return 'fix';
}
return 'chore';
}
private generateReleaseDescription(commits: any[]): string {
const types = commits.reduce((acc, commit) => {
acc[commit.type] = (acc[commit.type] || 0) + 1;
return acc;
}, {} as any);
const descriptions = [];
if (types.feat) descriptions.push(`${types.feat} new feature${types.feat > 1 ? 's' : ''}`);
if (types.fix) descriptions.push(`${types.fix} bug fix${types.fix > 1 ? 'es' : ''}`);
if (types.docs) descriptions.push(`${types.docs} documentation update${types.docs > 1 ? 's' : ''}`);
if (types.refactor) descriptions.push(`${types.refactor} refactoring${types.refactor > 1 ? 's' : ''}`);
return descriptions.length > 0 ? descriptions.join(', ') : 'Various improvements and fixes';
}
private generateChangelogContent(entry: ChangelogEntry, groupByType: boolean): string {
const date = entry.date;
const version = entry.version;
const description = entry.description;
let content = `## [${version}] - ${date}\n\n`;
content += `### ${this.capitalizeFirst(entry.type)}\n`;
content += `${description}\n\n`;
if (entry.commits.length > 0) {
content += `#### Commits\n`;
entry.commits.forEach(commit => {
content += `- ${commit}\n`;
});
content += '\n';
}
if (entry.files.length > 0) {
content += `#### Files Changed\n`;
entry.files.forEach(file => {
content += `- ${file}\n`;
});
content += '\n';
}
return content;
}
private mergeChangelogContent(newContent: string, existingContent: string): string {
if (!existingContent) {
return `# Changelog\n\nAll notable changes to this project will be documented in this file.\n\n${newContent}`;
}
const lines = existingContent.split('\n');
const insertIndex = lines.findIndex(line => line.startsWith('## ['));
if (insertIndex === -1) {
return `${existingContent}\n\n${newContent}`;
}
lines.splice(insertIndex, 0, newContent);
return lines.join('\n');
}
private async trackFile(filePath: string, projectPath: string): Promise<any> {
const result = await this.gitExecutor.executeGitCommand('status', ['--porcelain', filePath], projectPath);
return {
file: filePath,
status: result.success ? result.stdout.trim() : 'unknown',
exists: require('fs').existsSync(`${projectPath}/${filePath}`)
};
}
private async trackPattern(pattern: string, projectPath: string): Promise<string[]> {
const result = await this.gitExecutor.executeGitCommand('ls-files', [pattern], projectPath);
return result.success
? result.stdout.split('\n').filter(line => line.trim())
: [];
}
private async trackAllChanges(projectPath: string): Promise<string[]> {
const result = await this.gitExecutor.executeGitCommand('status', ['--porcelain'], projectPath);
return result.success
? result.stdout.split('\n').filter(line => line.trim()).map(line => line.substring(3).trim())
: [];
}
private formatHistoryMarkdown(commits: any[], history: UpdateHistory[]): string {
let content = '# Update History\n\n';
content += '## Recent Commits\n\n';
commits.slice(0, 10).forEach(commit => {
content += `- **${commit.hash}** - ${commit.message} (${commit.author}, ${commit.date})\n`;
});
content += '\n## Update Operations\n\n';
history.slice(-10).forEach(entry => {
content += `- **${entry.timestamp}** - ${entry.action}: ${entry.details}\n`;
});
return content;
}
private formatHistoryText(commits: any[], history: UpdateHistory[]): string {
let content = 'UPDATE HISTORY\n';
content += '==============\n\n';
content += 'RECENT COMMITS:\n';
commits.slice(0, 10).forEach(commit => {
content += `${commit.hash} - ${commit.message} (${commit.author}, ${commit.date})\n`;
});
content += '\nUPDATE OPERATIONS:\n';
history.slice(-10).forEach(entry => {
content += `${entry.timestamp} - ${entry.action}: ${entry.details}\n`;
});
return content;
}
private parseDiffStats(diffOutput: string): any {
const lines = diffOutput.split('\n');
let insertions = 0;
let deletions = 0;
let files = 0;
lines.forEach(line => {
if (line.startsWith('+++') || line.startsWith('---')) {
if (line.includes('b/')) files++;
} else if (line.startsWith('+') && !line.startsWith('+++')) {
insertions++;
} else if (line.startsWith('-') && !line.startsWith('---')) {
deletions++;
}
});
return { insertions, deletions, files };
}
private capitalizeFirst(str: string): string {
return str.charAt(0).toUpperCase() + str.slice(1);
}
}