doctool
Version:
AI-powered documentation validation and management system
199 lines • 6.74 kB
JavaScript
import { execSync } from 'child_process';
import * as fs from 'fs';
import * as path from 'path';
/**
* Checks if git is available and the directory is a git repository
*/
export function getRepositoryInfo(basePath) {
try {
// Check if git command is available
execSync('git --version', { stdio: 'ignore' });
// Check if current directory is a git repository
execSync('git rev-parse --git-dir', {
cwd: basePath,
stdio: 'ignore'
});
// Get last commit date
const lastCommitOutput = execSync('git log -1 --format=%ci', {
cwd: basePath,
encoding: 'utf8'
});
const lastCommitDate = new Date(lastCommitOutput.trim());
return {
hasGit: true,
lastCommitDate,
workingDirectory: basePath
};
}
catch (error) {
return {
hasGit: false,
workingDirectory: basePath
};
}
}
/**
* Gets changes since a specific date using git
*/
export function getChangesSinceDate(basePath, sinceDate) {
const repoInfo = getRepositoryInfo(basePath);
if (!repoInfo.hasGit) {
console.warn('⚠️ Git not available - using file system timestamps for change detection');
return getChangesFromFileSystem(basePath, sinceDate);
}
try {
const dateString = sinceDate.toISOString().split('T')[0]; // YYYY-MM-DD format
// Get files changed since the date
const changedFilesOutput = execSync(`git log --since="${dateString}" --name-status --pretty=format: --diff-filter=AMR`, { cwd: basePath, encoding: 'utf8' });
const changes = {
newFiles: [],
modifiedFiles: [],
deletedFiles: [],
lastCommitDate: repoInfo.lastCommitDate
};
// Parse git output
const lines = changedFilesOutput.split('\n').filter(line => line.trim());
for (const line of lines) {
const parts = line.trim().split('\t');
if (parts.length >= 2) {
const status = parts[0];
const filePath = parts[1];
// Only track relevant files (code, docs, config)
if (isRelevantFile(filePath)) {
switch (status) {
case 'A':
changes.newFiles.push(filePath);
break;
case 'M':
changes.modifiedFiles.push(filePath);
break;
case 'D':
changes.deletedFiles.push(filePath);
break;
case 'R':
// For renames, treat as modified
changes.modifiedFiles.push(filePath);
break;
}
}
}
}
return changes;
}
catch (error) {
console.warn('⚠️ Git command failed - falling back to file system timestamps');
return getChangesFromFileSystem(basePath, sinceDate);
}
}
/**
* Fallback method to detect changes using file system timestamps
*/
function getChangesFromFileSystem(basePath, sinceDate) {
const changes = {
newFiles: [],
modifiedFiles: [],
deletedFiles: []
};
function scanDirectory(dirPath) {
try {
const items = fs.readdirSync(dirPath, { withFileTypes: true });
for (const item of items) {
const fullPath = path.join(dirPath, item.name);
const relativePath = path.relative(basePath, fullPath);
if (item.isFile() && isRelevantFile(relativePath)) {
const stats = fs.statSync(fullPath);
if (stats.mtime > sinceDate) {
// File was modified since the date
changes.modifiedFiles.push(relativePath);
}
}
else if (item.isDirectory() && !shouldSkipDirectory(item.name)) {
scanDirectory(fullPath);
}
}
}
catch (error) {
// Skip directories we can't read
}
}
scanDirectory(basePath);
return changes;
}
/**
* Checks if a file is relevant for documentation updates
*/
function isRelevantFile(filePath) {
const relevantExtensions = ['.ts', '.js', '.tsx', '.jsx', '.md', '.json', '.yaml', '.yml'];
const extension = path.extname(filePath).toLowerCase();
// Skip certain directories and files
if (shouldSkipFile(filePath)) {
return false;
}
return relevantExtensions.includes(extension);
}
/**
* Checks if a directory should be skipped
*/
function shouldSkipDirectory(dirName) {
const skipDirs = [
'node_modules', '.git', '.vscode', '.idea',
'dist', 'build', 'coverage', '.nyc_output',
'.next', '.nuxt', 'out', 'temp', 'tmp', '.cache'
];
return skipDirs.includes(dirName) || dirName.startsWith('.');
}
/**
* Checks if a file should be skipped
*/
function shouldSkipFile(filePath) {
const pathParts = filePath.split(path.sep);
// Skip if any part of the path is a directory we should skip
if (pathParts.some(part => shouldSkipDirectory(part))) {
return true;
}
// Skip test files from git tracking (they don't affect docs much)
if (filePath.includes('.test.') || filePath.includes('.spec.')) {
return true;
}
return false;
}
/**
* Gets the last update timestamp from a knowledge file
*/
export function getLastUpdateTimestamp(knowledgeFilePath) {
try {
const content = fs.readFileSync(knowledgeFilePath, 'utf8');
// Look for timestamp patterns
const timestampPattern = /\*Last updated: (\d{4}-\d{2}-\d{2})\*/;
const match = content.match(timestampPattern);
if (match) {
return new Date(match[1]);
}
// Fallback to file modification time
const stats = fs.statSync(knowledgeFilePath);
return stats.mtime;
}
catch (error) {
return null;
}
}
/**
* Checks if there are uncommitted changes in the repository
*/
export function hasUncommittedChanges(basePath) {
const repoInfo = getRepositoryInfo(basePath);
if (!repoInfo.hasGit) {
return false;
}
try {
const statusOutput = execSync('git status --porcelain', {
cwd: basePath,
encoding: 'utf8'
});
return statusOutput.trim().length > 0;
}
catch (error) {
return false;
}
}
//# sourceMappingURL=gitUtils.js.map