aios-core
Version:
Synkra AIOS: AI-Orchestrated System for Full Stack Development - Core Framework
313 lines (268 loc) • 8.07 kB
JavaScript
/**
* Gitignore Generator Module
*
* Generates .gitignore files based on detected tech stack.
* Supports brownfield merge mode to preserve existing ignores.
*
* @module documentation-integrity/gitignore-generator
* @version 1.0.0
* @story 6.9
*/
const fs = require('fs');
const path = require('path');
// Template directory
const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates', 'gitignore');
/**
* Template file names
* @enum {string}
*/
const GitignoreTemplates = {
AIOS_BASE: 'gitignore-aios-base.tmpl',
NODE: 'gitignore-node.tmpl',
PYTHON: 'gitignore-python.tmpl',
BROWNFIELD_MERGE: 'gitignore-brownfield-merge.tmpl',
};
/**
* Tech stack identifiers
* @enum {string}
*/
const TechStack = {
NODE: 'node',
PYTHON: 'python',
GO: 'go',
RUST: 'rust',
};
/**
* Loads a gitignore template
*
* @param {string} templateName - Template file name
* @returns {string} Template content
* @throws {Error} If template not found
*/
function loadGitignoreTemplate(templateName) {
const templatePath = path.join(TEMPLATES_DIR, templateName);
if (!fs.existsSync(templatePath)) {
throw new Error(`Gitignore template not found: ${templatePath}`);
}
return fs.readFileSync(templatePath, 'utf8');
}
/**
* Detects tech stack from project markers
*
* @param {Object} markers - Detected project markers
* @returns {string[]} List of detected tech stacks
*/
function detectTechStacks(markers) {
const stacks = [];
if (markers.hasPackageJson) stacks.push(TechStack.NODE);
if (markers.hasPythonProject) stacks.push(TechStack.PYTHON);
if (markers.hasGoMod) stacks.push(TechStack.GO);
if (markers.hasCargoToml) stacks.push(TechStack.RUST);
return stacks;
}
/**
* Gets gitignore templates for detected tech stacks
*
* @param {string[]} techStacks - Detected tech stacks
* @returns {string[]} List of template names to use
*/
function getTemplatesForStacks(techStacks) {
const templates = [GitignoreTemplates.AIOS_BASE];
for (const stack of techStacks) {
switch (stack) {
case TechStack.NODE:
templates.push(GitignoreTemplates.NODE);
break;
case TechStack.PYTHON:
templates.push(GitignoreTemplates.PYTHON);
break;
// Go and Rust would have their own templates
// Add when implemented
}
}
return templates;
}
/**
* Generates a complete .gitignore file
*
* @param {Object} markers - Detected project markers
* @param {Object} [options] - Generation options
* @param {string} [options.projectName] - Project name for header
* @returns {string} Generated gitignore content
*/
function generateGitignore(markers, options = {}) {
const techStacks = detectTechStacks(markers);
const templates = getTemplatesForStacks(techStacks);
const sections = [];
// Add project header
const projectName = options.projectName || 'Project';
sections.push(`# ${projectName} .gitignore`);
sections.push('# Generated by AIOS Documentation Integrity System');
sections.push(`# Date: ${new Date().toISOString().split('T')[0]}`);
sections.push(`# Tech Stack: ${techStacks.join(', ') || 'Generic'}`);
sections.push('');
// Load and append each template
for (const templateName of templates) {
try {
const content = loadGitignoreTemplate(templateName);
sections.push(content);
sections.push(''); // Add blank line between sections
} catch (error) {
console.warn(`Warning: Could not load template ${templateName}: ${error.message}`);
}
}
return sections.join('\n').trim() + '\n';
}
/**
* Merges AIOS ignores with existing .gitignore
*
* @param {string} existingContent - Existing .gitignore content
* @param {Object} [options] - Merge options
* @returns {string} Merged gitignore content
*/
function mergeGitignore(existingContent, _options = {}) {
// Check if AIOS section already exists
if (existingContent.includes('AIOS Integration Section')) {
console.log('AIOS section already exists in .gitignore, skipping merge');
return existingContent;
}
// Load merge template
let mergeSection;
try {
mergeSection = loadGitignoreTemplate(GitignoreTemplates.BROWNFIELD_MERGE);
} catch {
// Fallback to minimal section
mergeSection = `
# ========================================
# AIOS Integration Section
# ========================================
# AIOS Local Configuration
.aios-core/local/
.aios-core/*.local.yaml
.aios-core/logs/
.aios-core/cache/
# ========================================
# End of AIOS Integration Section
# ========================================
`;
}
// Replace date placeholder
mergeSection = mergeSection.replace(
'{{GENERATED_DATE}}',
new Date().toISOString().split('T')[0],
);
// Append to existing content
const merged = existingContent.trimEnd() + '\n\n' + mergeSection.trim() + '\n';
return merged;
}
/**
* Generates or merges .gitignore for a project
*
* @param {string} targetDir - Target directory
* @param {Object} markers - Detected project markers
* @param {Object} [options] - Generation options
* @param {boolean} [options.dryRun] - Don't write file, just return content
* @param {boolean} [options.merge] - Merge with existing instead of replace
* @param {string} [options.projectName] - Project name for header
* @returns {Object} Generation result
*/
function generateGitignoreFile(targetDir, markers, options = {}) {
const gitignorePath = path.join(targetDir, '.gitignore');
const existingPath = fs.existsSync(gitignorePath);
let content;
let mode;
if (existingPath && options.merge !== false) {
// Brownfield mode - merge with existing
const existing = fs.readFileSync(gitignorePath, 'utf8');
content = mergeGitignore(existing, options);
mode = 'merged';
} else {
// Greenfield mode - generate new
content = generateGitignore(markers, options);
mode = existingPath ? 'replaced' : 'created';
}
const result = {
success: true,
path: gitignorePath,
content,
mode,
techStacks: detectTechStacks(markers),
};
if (!options.dryRun) {
try {
fs.writeFileSync(gitignorePath, content, 'utf8');
} catch (error) {
result.success = false;
result.error = error.message;
}
}
return result;
}
/**
* Checks if a .gitignore file has AIOS integration
*
* @param {string} targetDir - Target directory
* @returns {boolean} True if AIOS section exists
*/
function hasAiosIntegration(targetDir) {
const gitignorePath = path.join(targetDir, '.gitignore');
if (!fs.existsSync(gitignorePath)) {
return false;
}
const content = fs.readFileSync(gitignorePath, 'utf8');
return content.includes('AIOS Integration Section') || content.includes('.aios-core/');
}
/**
* Parses an existing .gitignore into sections
*
* @param {string} content - .gitignore content
* @returns {Object} Parsed sections
*/
function parseGitignore(content) {
const lines = content.split('\n');
const sections = {
header: [],
patterns: [],
aiosSection: null,
comments: [],
};
let aiosStart = -1;
let aiosEnd = -1;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.includes('AIOS Integration Section')) {
if (aiosStart === -1) {
aiosStart = i;
}
}
if (line.includes('End of AIOS Integration Section')) {
aiosEnd = i;
}
if (line.startsWith('#') && !line.includes('AIOS')) {
sections.comments.push({ line: i, content: line });
}
if (!line.startsWith('#') && line.trim()) {
sections.patterns.push({ line: i, pattern: line.trim() });
}
}
if (aiosStart !== -1) {
sections.aiosSection = {
start: aiosStart,
end: aiosEnd !== -1 ? aiosEnd : lines.length - 1,
};
}
return sections;
}
module.exports = {
loadGitignoreTemplate,
detectTechStacks,
getTemplatesForStacks,
generateGitignore,
mergeGitignore,
generateGitignoreFile,
hasAiosIntegration,
parseGitignore,
GitignoreTemplates,
TechStack,
TEMPLATES_DIR,
};