/**
* Gitignore Management
*
* Provides utilities for managing .gitignore files in WP Navigator projects:
* - Generate comprehensive .gitignore patterns
* - Check for sensitive files already tracked in git
*
* @package WP_Navigator_MCP
* @since 2.1.0
*/
import { spawn } from 'child_process';
// =============================================================================
// Types
// =============================================================================
/**
* Sensitive file patterns that should never be committed
*/
export const SENSITIVE_PATTERNS = ['.wpnav.env', 'wpnav.config.json', '.env'] as const;
// =============================================================================
// Gitignore Template
// =============================================================================
/**
* Generate comprehensive .gitignore content for WP Navigator projects
*
* Includes patterns for:
* - WordPress credentials (Application Passwords)
* - Environment files
* - Snapshots (large, regeneratable)
* - Logs
* - IDE/Editor files
* - OS files
* - Temporary files
*/
export function generateGitignore(): string {
return `# WP Navigator - Ignore sensitive and generated files
# Generated by wpnav init
# ─────────────────────────────────────────────────────────────────────────────
# WP Navigator credentials (never commit - contains Application Password)
# ─────────────────────────────────────────────────────────────────────────────
.wpnav.env
wpnav.config.json
# ─────────────────────────────────────────────────────────────────────────────
# Environment files
# ─────────────────────────────────────────────────────────────────────────────
.env
.env.local
.env.*.local
# ─────────────────────────────────────────────────────────────────────────────
# Snapshots (large, regeneratable via wpnav snapshot)
# ─────────────────────────────────────────────────────────────────────────────
.wpnav/snapshots/
# ─────────────────────────────────────────────────────────────────────────────
# Logs
# ─────────────────────────────────────────────────────────────────────────────
*.log
# ─────────────────────────────────────────────────────────────────────────────
# IDE/Editor
# ─────────────────────────────────────────────────────────────────────────────
.idea/
.vscode/
*.swp
*.swo
*~
# ─────────────────────────────────────────────────────────────────────────────
# OS files
# ─────────────────────────────────────────────────────────────────────────────
.DS_Store
Thumbs.db
# ─────────────────────────────────────────────────────────────────────────────
# Temporary files
# ─────────────────────────────────────────────────────────────────────────────
*.tmp
*.temp
`;
}
// =============================================================================
// Tracked File Detection
// =============================================================================
/**
* Check for sensitive files already tracked in git
*
* Uses spawn (not exec) to prevent shell injection vulnerabilities.
* Returns empty array if:
* - Directory is not a git repository
* - Git is not available
* - Command fails for any reason
*
* @param cwd - Working directory to check
* @returns Array of sensitive file names that are tracked in git
*/
export async function checkTrackedSensitiveFiles(cwd: string): Promise<string[]> {
return new Promise((resolve) => {
try {
const proc = spawn('git', ['ls-files'], {
cwd,
stdio: ['ignore', 'pipe', 'ignore'],
});
let stdout = '';
proc.stdout?.on('data', (data: Buffer) => {
stdout += data.toString();
});
proc.on('error', () => {
// Not a git repo or git unavailable - silently return empty
resolve([]);
});
proc.on('close', (code) => {
if (code !== 0) {
resolve([]);
return;
}
const files = stdout.split('\n').filter(Boolean);
// Check each sensitive pattern against tracked files
const tracked = SENSITIVE_PATTERNS.filter((pattern) =>
files.some((f) => f === pattern || f.endsWith(`/${pattern}`))
);
resolve(tracked);
});
} catch {
// Any unexpected error - return empty
resolve([]);
}
});
}
/**
* Check if a .gitignore file contains a specific pattern
*
* @param gitignoreContent - Content of the .gitignore file
* @param pattern - Pattern to check for
* @returns true if the pattern is present
*/
export function hasGitignorePattern(gitignoreContent: string, pattern: string): boolean {
const lines = gitignoreContent.split('\n').map((line) => line.trim());
return lines.includes(pattern);
}
/**
* Generate gitignore append content for updating existing files
*
* Used when a .gitignore exists but is missing required patterns.
* Only includes patterns that are not already present.
*
* @param existingContent - Current content of .gitignore
* @returns Content to append, or empty string if all patterns present
*/
export function generateGitignoreAppend(existingContent: string): string {
const missingPatterns: string[] = [];
for (const pattern of SENSITIVE_PATTERNS) {
if (!hasGitignorePattern(existingContent, pattern)) {
missingPatterns.push(pattern);
}
}
if (missingPatterns.length === 0) {
return '';
}
return `
# WP Navigator credentials (added by wpnav init)
${missingPatterns.join('\n')}
`;
}