Files
cli/ts/mod_format/index.ts
2026-03-24 16:10:51 +00:00

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`);
}
};