fake-it-til-you-git
Version:
A modern CLI tool to generate fake Git commit history for your GitHub/GitLab profile
277 lines • 10.8 kB
JavaScript
import { existsSync } from 'node:fs';
import { readFile } from 'node:fs/promises';
import { resolve } from 'node:path';
import { validateConfig } from './utils/validation.js';
/**
* Default configuration values
*/
const DEFAULT_CONFIG = {
author: {
name: 'Fake Git User',
email: 'fake@example.com',
},
dateRange: {
startDate: new Date(Date.now() - 365 * 24 * 60 * 60 * 1000).toISOString().split('T')[0],
endDate: new Date().toISOString().split('T')[0],
},
commits: {
maxPerDay: 10,
distribution: 'random',
messageStyle: 'default',
},
options: {
preview: false,
push: false,
verbose: false,
dev: false,
repositoryPath: '.',
yes: false,
},
};
/**
* Load configuration from a JSON file
* @param configPath - Path to the configuration file
* @returns Parsed configuration object or null if file doesn't exist
*/
export async function loadConfigFromFile(configPath) {
try {
const resolvedPath = resolve(configPath);
if (!existsSync(resolvedPath)) {
return null;
}
const fileContent = await readFile(resolvedPath, 'utf-8');
const parsedConfig = JSON.parse(fileContent);
// Return the configuration as-is (should already be in correct nested format)
return parsedConfig;
}
catch (error) {
if (error instanceof SyntaxError) {
throw new Error(`Invalid JSON in config file: ${configPath}. ${error.message}`);
}
throw new Error(`Failed to load config file: ${configPath}. ${error instanceof Error ? error.message : 'Unknown error'}`);
}
}
/**
* Convert CLI options to partial config format
* @param cliOptions - Options from CLI
* @returns Partial configuration object
*/
export function cliOptionsToConfig(cliOptions) {
const config = {};
// Handle author configuration
if (cliOptions.authorName || cliOptions.authorEmail) {
config.author = {};
if (cliOptions.authorName) {
config.author.name = cliOptions.authorName;
}
if (cliOptions.authorEmail) {
config.author.email = cliOptions.authorEmail;
}
}
// Handle date range
if (cliOptions.startDate || cliOptions.endDate || cliOptions.days) {
config.dateRange = {};
if (cliOptions.startDate) {
config.dateRange.startDate = cliOptions.startDate;
}
if (cliOptions.endDate) {
config.dateRange.endDate = cliOptions.endDate;
}
else if (cliOptions.days && !cliOptions.startDate && !cliOptions.endDate) {
// If only days is provided, calculate date range
const daysBack = Number.parseInt(cliOptions.days, 10);
const startDate = new Date(Date.now() - (daysBack - 1) * 24 * 60 * 60 * 1000);
config.dateRange.startDate = startDate.toISOString().split('T')[0];
config.dateRange.endDate = new Date().toISOString().split('T')[0];
}
}
// Handle commits configuration
if (cliOptions.commits || cliOptions.distribution || cliOptions.messageStyle ||
cliOptions.pattern || cliOptions.customPattern || cliOptions.patternText ||
cliOptions.patternScale || cliOptions.patternIntensity || cliOptions.patternRepeat) {
config.commits = {};
if (cliOptions.commits) {
config.commits.maxPerDay = Number.parseInt(cliOptions.commits, 10);
}
if (cliOptions.distribution) {
config.commits.distribution = cliOptions.distribution;
}
if (cliOptions.messageStyle) {
config.commits.messageStyle = cliOptions.messageStyle;
}
// Handle pattern configuration
if (cliOptions.pattern || cliOptions.customPattern || cliOptions.patternText ||
cliOptions.patternScale || cliOptions.patternIntensity || cliOptions.patternRepeat) {
config.commits.pattern = {};
if (cliOptions.pattern) {
config.commits.pattern.type = 'preset';
config.commits.pattern.preset = cliOptions.pattern;
}
else if (cliOptions.customPattern) {
config.commits.pattern.type = 'custom';
config.commits.pattern.custom = cliOptions.customPattern;
}
else if (cliOptions.patternText) {
config.commits.pattern.type = 'text';
config.commits.pattern.text = cliOptions.patternText;
}
if (cliOptions.patternScale && config.commits.pattern) {
config.commits.pattern.scale = Number.parseInt(cliOptions.patternScale, 10);
}
if (cliOptions.patternIntensity && config.commits.pattern) {
config.commits.pattern.intensity = cliOptions.patternIntensity;
}
if (cliOptions.patternRepeat && config.commits.pattern) {
config.commits.pattern.repeat = Number.parseInt(cliOptions.patternRepeat, 10);
}
// If pattern options are specified, automatically set distribution to 'pattern'
if (!cliOptions.distribution) {
config.commits.distribution = 'pattern';
}
}
}
// Handle options
if (cliOptions.preview !== undefined ||
cliOptions.push !== undefined ||
cliOptions.verbose !== undefined ||
cliOptions.dev !== undefined ||
cliOptions.repoPath !== undefined ||
cliOptions.yes !== undefined) {
config.options = {};
if (cliOptions.preview !== undefined) {
config.options.preview = cliOptions.preview;
}
if (cliOptions.push !== undefined) {
config.options.push = cliOptions.push;
}
if (cliOptions.verbose !== undefined) {
config.options.verbose = cliOptions.verbose;
}
if (cliOptions.dev !== undefined) {
config.options.dev = cliOptions.dev;
}
if (cliOptions.repoPath !== undefined) {
config.options.repositoryPath = cliOptions.repoPath;
}
if (cliOptions.yes !== undefined) {
config.options.yes = cliOptions.yes;
}
}
// Handle seed
if (cliOptions.seed) {
config.seed = cliOptions.seed;
}
return config;
}
/**
* Deep merge configuration objects
* @param target - Target configuration object
* @param source - Source configuration object to merge
* @returns Merged configuration object
*/
function deepMergeConfig(target, source) {
const result = { ...target };
for (const key in source) {
const sourceValue = source[key];
const targetValue = result[key];
if (sourceValue !== undefined) {
if (typeof sourceValue === 'object' && sourceValue !== null && !Array.isArray(sourceValue)) {
result[key] = {
...targetValue,
...sourceValue,
};
}
else {
result[key] = sourceValue;
}
}
}
return result;
}
/**
* Apply default values to configuration
* @param config - Partial configuration object
* @returns Complete configuration with defaults applied
*/
export function applyDefaults(config) {
// Deep merge with defaults
const mergedConfig = deepMergeConfig({
author: DEFAULT_CONFIG.author,
dateRange: DEFAULT_CONFIG.dateRange,
commits: DEFAULT_CONFIG.commits,
options: DEFAULT_CONFIG.options,
seed: DEFAULT_CONFIG.seed,
}, config);
return {
author: {
name: mergedConfig.author?.name ?? DEFAULT_CONFIG.author.name,
email: mergedConfig.author?.email ?? DEFAULT_CONFIG.author.email,
},
dateRange: {
startDate: mergedConfig.dateRange?.startDate ?? DEFAULT_CONFIG.dateRange.startDate,
endDate: mergedConfig.dateRange?.endDate ?? DEFAULT_CONFIG.dateRange.endDate,
},
commits: {
maxPerDay: mergedConfig.commits?.maxPerDay ?? DEFAULT_CONFIG.commits.maxPerDay,
distribution: mergedConfig.commits?.distribution ?? DEFAULT_CONFIG.commits.distribution,
messageStyle: mergedConfig.commits?.messageStyle ?? DEFAULT_CONFIG.commits.messageStyle,
...(mergedConfig.commits?.pattern && { pattern: mergedConfig.commits.pattern }),
},
options: {
preview: mergedConfig.options?.preview ?? DEFAULT_CONFIG.options.preview,
push: mergedConfig.options?.push ?? DEFAULT_CONFIG.options.push,
verbose: mergedConfig.options?.verbose ?? DEFAULT_CONFIG.options.verbose,
dev: mergedConfig.options?.dev ?? DEFAULT_CONFIG.options.dev,
repositoryPath: mergedConfig.options?.repositoryPath ?? DEFAULT_CONFIG.options.repositoryPath,
yes: mergedConfig.options?.yes ?? DEFAULT_CONFIG.options.yes,
},
seed: mergedConfig.seed,
};
}
/**
* Load and merge configuration from file and CLI options
* @param cliOptions - CLI options
* @returns Complete validated configuration
*/
export async function loadConfig(cliOptions) {
let fileConfig = {};
// Load from config file if specified
const configPath = cliOptions.config || './test-configs/fake-git.config.json';
try {
const loadedConfig = await loadConfigFromFile(configPath);
if (loadedConfig) {
fileConfig = loadedConfig;
}
else if (cliOptions.config) {
// If config file was explicitly specified but doesn't exist, throw error
throw new Error(`Configuration file not found: ${configPath}`);
}
}
catch (error) {
// If config file was explicitly specified but failed to load, throw error
if (cliOptions.config) {
throw error;
}
// Otherwise, silently continue with empty config (default behavior)
}
// Convert CLI options to config format
const cliConfig = cliOptionsToConfig(cliOptions);
// Merge configurations: defaults < file < CLI
const mergedConfig = deepMergeConfig(fileConfig, cliConfig);
// Apply defaults to any missing values
const finalConfig = applyDefaults(mergedConfig);
// Validate the final configuration
const validation = validateConfig(finalConfig);
if (!validation.valid) {
throw new Error(`Configuration validation failed: ${validation.errors.join(', ')}`);
}
return finalConfig;
}
/**
* Get default configuration
* @returns Default configuration object
*/
export function getDefaultConfig() {
return { ...DEFAULT_CONFIG };
}
//# sourceMappingURL=config.js.map