222 lines
6.9 KiB
TypeScript
222 lines
6.9 KiB
TypeScript
import * as plugins from './mod.plugins.js';
|
|
import { Project } from '../classes.project.js';
|
|
import { FormatContext } from './classes.formatcontext.js';
|
|
import { FormatPlanner } from './classes.formatplanner.js';
|
|
import { BaseFormatter } from './classes.baseformatter.js';
|
|
import { logger, setVerboseMode } from '../gitzone.logging.js';
|
|
|
|
import { CleanupFormatter } from './formatters/cleanup.formatter.js';
|
|
import { SmartconfigFormatter } from './formatters/smartconfig.formatter.js';
|
|
import { LicenseFormatter } from './formatters/license.formatter.js';
|
|
import { PackageJsonFormatter } from './formatters/packagejson.formatter.js';
|
|
import { TemplatesFormatter } from './formatters/templates.formatter.js';
|
|
import { GitignoreFormatter } from './formatters/gitignore.formatter.js';
|
|
import { TsconfigFormatter } from './formatters/tsconfig.formatter.js';
|
|
import { PrettierFormatter } from './formatters/prettier.formatter.js';
|
|
import { ReadmeFormatter } from './formatters/readme.formatter.js';
|
|
import { CopyFormatter } from './formatters/copy.formatter.js';
|
|
|
|
// Shared formatter class map used by both run() and runFormatter()
|
|
const formatterMap: Record<string, new (ctx: FormatContext, proj: Project) => BaseFormatter> = {
|
|
cleanup: CleanupFormatter,
|
|
smartconfig: SmartconfigFormatter,
|
|
license: LicenseFormatter,
|
|
packagejson: PackageJsonFormatter,
|
|
templates: TemplatesFormatter,
|
|
gitignore: GitignoreFormatter,
|
|
tsconfig: TsconfigFormatter,
|
|
prettier: PrettierFormatter,
|
|
readme: ReadmeFormatter,
|
|
copy: CopyFormatter,
|
|
};
|
|
|
|
// Formatters that don't require projectType to be set
|
|
const formattersNotRequiringProjectType = ['smartconfig', 'prettier', 'cleanup', 'packagejson'];
|
|
|
|
export let run = async (
|
|
options: {
|
|
write?: boolean;
|
|
dryRun?: boolean; // Deprecated, kept for compatibility
|
|
yes?: boolean;
|
|
planOnly?: boolean;
|
|
savePlan?: string;
|
|
fromPlan?: string;
|
|
detailed?: boolean;
|
|
interactive?: boolean;
|
|
verbose?: boolean;
|
|
diff?: boolean;
|
|
} = {},
|
|
): Promise<any> => {
|
|
if (options.verbose) {
|
|
setVerboseMode(true);
|
|
}
|
|
|
|
const shouldWrite = options.write ?? (options.dryRun === false);
|
|
|
|
const project = await Project.fromCwd({ requireProjectType: false });
|
|
const context = new FormatContext();
|
|
const planner = new FormatPlanner();
|
|
|
|
const smartconfigInstance = new plugins.smartconfig.Smartconfig();
|
|
const formatConfig = smartconfigInstance.dataFor<any>('@git.zone/cli.format', {
|
|
interactive: true,
|
|
showDiffs: false,
|
|
autoApprove: false,
|
|
modules: {
|
|
skip: [],
|
|
only: [],
|
|
},
|
|
});
|
|
|
|
const interactive = options.interactive ?? formatConfig.interactive;
|
|
const autoApprove = options.yes ?? formatConfig.autoApprove;
|
|
|
|
try {
|
|
// Initialize formatters in execution order
|
|
const formatters = Object.entries(formatterMap).map(
|
|
([, FormatterClass]) => new FormatterClass(context, project),
|
|
);
|
|
|
|
// Filter formatters based on configuration
|
|
const activeFormatters = formatters.filter((formatter) => {
|
|
if (formatConfig.modules.only.length > 0) {
|
|
return formatConfig.modules.only.includes(formatter.name);
|
|
}
|
|
if (formatConfig.modules.skip.includes(formatter.name)) {
|
|
return false;
|
|
}
|
|
return true;
|
|
});
|
|
|
|
// Plan phase
|
|
logger.log('info', 'Analyzing project for format operations...');
|
|
let plan = options.fromPlan
|
|
? JSON.parse(
|
|
(await plugins.smartfs
|
|
.file(options.fromPlan)
|
|
.encoding('utf8')
|
|
.read()) as string,
|
|
)
|
|
: await planner.planFormat(activeFormatters);
|
|
|
|
// Display plan
|
|
await planner.displayPlan(plan, options.detailed);
|
|
|
|
// Save plan if requested
|
|
if (options.savePlan) {
|
|
await plugins.smartfs
|
|
.file(options.savePlan)
|
|
.encoding('utf8')
|
|
.write(JSON.stringify(plan, null, 2));
|
|
logger.log('info', `Plan saved to ${options.savePlan}`);
|
|
}
|
|
|
|
if (options.planOnly) {
|
|
return;
|
|
}
|
|
|
|
// Show diffs if explicitly requested or before interactive write confirmation
|
|
const showDiffs = options.diff || (shouldWrite && interactive && !autoApprove);
|
|
if (showDiffs) {
|
|
logger.log('info', 'Showing file diffs:');
|
|
console.log('');
|
|
|
|
for (const formatter of activeFormatters) {
|
|
const checkResult = await formatter.check();
|
|
if (checkResult.hasDiff) {
|
|
logger.log('info', `[${formatter.name}]`);
|
|
formatter.displayAllDiffs(checkResult);
|
|
console.log('');
|
|
}
|
|
}
|
|
}
|
|
|
|
// Dry-run mode (default behavior)
|
|
if (!shouldWrite) {
|
|
logger.log('info', 'Dry-run mode - use --write (-w) to apply changes');
|
|
return;
|
|
}
|
|
|
|
// Interactive confirmation
|
|
if (interactive && !autoApprove) {
|
|
const interactInstance = new plugins.smartinteract.SmartInteract();
|
|
const response = await interactInstance.askQuestion({
|
|
type: 'confirm',
|
|
name: 'proceed',
|
|
message: 'Proceed with formatting?',
|
|
default: true,
|
|
});
|
|
|
|
if (!(response as any).value) {
|
|
logger.log('info', 'Format operation cancelled by user');
|
|
return;
|
|
}
|
|
}
|
|
|
|
// Execute phase
|
|
logger.log('info', 'Executing format operations...');
|
|
await planner.executePlan(plan, activeFormatters, context);
|
|
|
|
context.getFormatStats().finish();
|
|
|
|
const showStats = smartconfigInstance.dataFor('gitzone.format.showStats', true);
|
|
if (showStats) {
|
|
context.getFormatStats().displayStats();
|
|
}
|
|
|
|
if (options.detailed) {
|
|
const statsPath = `.nogit/format-stats-${Date.now()}.json`;
|
|
await context.getFormatStats().saveReport(statsPath);
|
|
}
|
|
|
|
logger.log('success', 'Format operations completed successfully!');
|
|
} catch (error) {
|
|
logger.log('error', `Format operation failed: ${error.message}`);
|
|
throw error;
|
|
}
|
|
};
|
|
|
|
import type { ICheckResult } from './interfaces.format.js';
|
|
export type { ICheckResult };
|
|
|
|
/**
|
|
* Run a single formatter by name (for use by other modules)
|
|
*/
|
|
export const runFormatter = async (
|
|
formatterName: string,
|
|
options: {
|
|
silent?: boolean;
|
|
checkOnly?: boolean;
|
|
showDiff?: boolean;
|
|
} = {}
|
|
): Promise<ICheckResult | void> => {
|
|
const requireProjectType = !formattersNotRequiringProjectType.includes(formatterName);
|
|
const project = await Project.fromCwd({ requireProjectType });
|
|
const context = new FormatContext();
|
|
|
|
const FormatterClass = formatterMap[formatterName];
|
|
if (!FormatterClass) {
|
|
throw new Error(`Unknown formatter: ${formatterName}`);
|
|
}
|
|
|
|
const formatter = new FormatterClass(context, project);
|
|
|
|
if (options.checkOnly) {
|
|
const result = await formatter.check();
|
|
if (result.hasDiff && options.showDiff) {
|
|
formatter.displayAllDiffs(result);
|
|
}
|
|
return result;
|
|
}
|
|
|
|
const changes = await formatter.analyze();
|
|
|
|
for (const change of changes) {
|
|
await formatter.applyChange(change);
|
|
}
|
|
|
|
if (!options.silent) {
|
|
logger.log('success', `Formatter '${formatterName}' completed`);
|
|
}
|
|
};
|