fix(format): Improve concurrency control in cache and rollback management with mutex locking and refine formatting details

This commit is contained in:
2025-08-08 06:25:40 +00:00
parent d32d47b706
commit 859cbc733d
38 changed files with 784 additions and 726 deletions

View File

@@ -7,13 +7,18 @@ export class CleanupFormatter extends BaseFormatter {
get name(): string {
return 'cleanup';
}
async analyze(): Promise<IPlannedChange[]> {
const changes: IPlannedChange[] = [];
// List of files to remove
const filesToRemove = ['yarn.lock', 'package-lock.json', 'tslint.json', 'defaults.yml'];
const filesToRemove = [
'yarn.lock',
'package-lock.json',
'tslint.json',
'defaults.yml',
];
for (const file of filesToRemove) {
const exists = await plugins.smartfile.fs.fileExists(file);
if (exists) {
@@ -21,14 +26,14 @@ export class CleanupFormatter extends BaseFormatter {
type: 'delete',
path: file,
module: this.name,
description: `Remove obsolete file`
description: `Remove obsolete file`,
});
}
}
return changes;
}
async applyChange(change: IPlannedChange): Promise<void> {
switch (change.type) {
case 'delete':
@@ -36,4 +41,4 @@ export class CleanupFormatter extends BaseFormatter {
break;
}
}
}
}

View File

@@ -5,4 +5,4 @@ export class CopyFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'copy', formatCopy);
}
}
}

View File

@@ -5,4 +5,4 @@ export class GitignoreFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'gitignore', formatGitignore);
}
}
}

View File

@@ -7,30 +7,37 @@ import * as plugins from '../mod.plugins.js';
export class LegacyFormatter extends BaseFormatter {
private moduleName: string;
private formatModule: any;
constructor(context: any, project: Project, moduleName: string, formatModule: any) {
constructor(
context: any,
project: Project,
moduleName: string,
formatModule: any,
) {
super(context, project);
this.moduleName = moduleName;
this.formatModule = formatModule;
}
get name(): string {
return this.moduleName;
}
async analyze(): Promise<IPlannedChange[]> {
// For legacy modules, we can't easily predict changes
// So we'll return a generic change that indicates the module will run
return [{
type: 'modify',
path: '<various files>',
module: this.name,
description: `Run ${this.name} formatter`
}];
return [
{
type: 'modify',
path: '<various files>',
module: this.name,
description: `Run ${this.name} formatter`,
},
];
}
async applyChange(change: IPlannedChange): Promise<void> {
// Run the legacy format module
await this.formatModule.run(this.project);
}
}
}

View File

@@ -5,4 +5,4 @@ export class LicenseFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'license', formatLicense);
}
}
}

View File

@@ -5,4 +5,4 @@ export class NpmextraFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'npmextra', formatNpmextra);
}
}
}

View File

@@ -5,4 +5,4 @@ export class PackageJsonFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'packagejson', formatPackageJson);
}
}
}

View File

@@ -7,21 +7,16 @@ export class PrettierFormatter extends BaseFormatter {
get name(): string {
return 'prettier';
}
async analyze(): Promise<IPlannedChange[]> {
const changes: IPlannedChange[] = [];
// Define directories to format (TypeScript directories by default)
const includeDirs = [
'ts',
'ts_*',
'test',
'tests'
];
const includeDirs = ['ts', 'ts_*', 'test', 'tests'];
// File extensions to format
const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
// Also format root-level config files
const rootConfigFiles = [
'package.json',
@@ -36,33 +31,36 @@ export class PrettierFormatter extends BaseFormatter {
'CHANGELOG.md',
'license',
'LICENSE',
'*.md'
'*.md',
];
// Collect all files to format
const allFiles: string[] = [];
// Add files from TypeScript directories
for (const dir of includeDirs) {
const globPattern = `${dir}/**/*.${extensions}`;
const dirFiles = await plugins.smartfile.fs.listFileTree('.', globPattern);
const dirFiles = await plugins.smartfile.fs.listFileTree(
'.',
globPattern,
);
allFiles.push(...dirFiles);
}
// Add root config files
for (const pattern of rootConfigFiles) {
const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern);
// Only include files at root level (no slashes in path)
const rootLevelFiles = rootFiles.filter(f => !f.includes('/'));
const rootLevelFiles = rootFiles.filter((f) => !f.includes('/'));
allFiles.push(...rootLevelFiles);
}
// Remove duplicates
const uniqueFiles = [...new Set(allFiles)];
// Get all files that match the pattern
const files = uniqueFiles;
// Ensure we only process actual files (not directories)
const validFiles: string[] = [];
for (const file of files) {
@@ -76,48 +74,52 @@ export class PrettierFormatter extends BaseFormatter {
logVerbose(`Skipping ${file} - cannot access: ${error.message}`);
}
}
// Check which files need formatting
for (const file of validFiles) {
// Skip files that haven't changed
if (!await this.shouldProcessFile(file)) {
if (!(await this.shouldProcessFile(file))) {
logVerbose(`Skipping ${file} - no changes detected`);
continue;
}
changes.push({
type: 'modify',
path: file,
module: this.name,
description: 'Format with Prettier'
description: 'Format with Prettier',
});
}
logger.log('info', `Found ${changes.length} files to format with Prettier`);
return changes;
}
async execute(changes: IPlannedChange[]): Promise<void> {
const startTime = this.stats.moduleStartTime(this.name);
this.stats.startModule(this.name);
try {
await this.preExecute();
// Batch process files
const batchSize = 10; // Process 10 files at a time
const batches: IPlannedChange[][] = [];
for (let i = 0; i < changes.length; i += batchSize) {
batches.push(changes.slice(i, i + batchSize));
}
logVerbose(`Processing ${changes.length} files in ${batches.length} batches`);
logVerbose(
`Processing ${changes.length} files in ${batches.length} batches`,
);
for (let i = 0; i < batches.length; i++) {
const batch = batches[i];
logVerbose(`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`);
logVerbose(
`Processing batch ${i + 1}/${batches.length} (${batch.length} files)`,
);
// Process batch in parallel
const promises = batch.map(async (change) => {
try {
@@ -125,44 +127,45 @@ export class PrettierFormatter extends BaseFormatter {
this.stats.recordFileOperation(this.name, change.type, true);
} catch (error) {
this.stats.recordFileOperation(this.name, change.type, false);
logger.log('error', `Failed to format ${change.path}: ${error.message}`);
logger.log(
'error',
`Failed to format ${change.path}: ${error.message}`,
);
// Don't throw - continue with other files
}
});
await Promise.all(promises);
}
await this.postExecute();
} catch (error) {
await this.context.rollbackOperation();
// Rollback removed - no longer tracking operations
throw error;
} finally {
this.stats.endModule(this.name, startTime);
}
}
async applyChange(change: IPlannedChange): Promise<void> {
if (change.type !== 'modify') return;
try {
// Read current content
const content = plugins.smartfile.fs.toStringSync(change.path);
// Format with prettier
const prettier = await import('prettier');
const formatted = await prettier.format(content, {
filepath: change.path,
...(await this.getPrettierConfig())
...(await this.getPrettierConfig()),
});
// Only write if content actually changed
if (formatted !== content) {
await this.modifyFile(change.path, formatted);
logVerbose(`Formatted ${change.path}`);
} else {
// Still update cache even if content didn't change
await this.cache.updateFileCache(change.path);
logVerbose(`No formatting changes for ${change.path}`);
}
} catch (error) {
@@ -170,7 +173,7 @@ export class PrettierFormatter extends BaseFormatter {
throw error;
}
}
private async getPrettierConfig(): Promise<any> {
// Try to load prettier config from the project
const prettierConfig = new plugins.npmextra.Npmextra();
@@ -181,7 +184,7 @@ export class PrettierFormatter extends BaseFormatter {
printWidth: 80,
tabWidth: 2,
semi: true,
arrowParens: 'always'
arrowParens: 'always',
});
}
}
}

View File

@@ -6,17 +6,19 @@ export class ReadmeFormatter extends BaseFormatter {
get name(): string {
return 'readme';
}
async analyze(): Promise<IPlannedChange[]> {
return [{
type: 'modify',
path: 'readme.md',
module: this.name,
description: 'Ensure readme files exist'
}];
return [
{
type: 'modify',
path: 'readme.md',
module: this.name,
description: 'Ensure readme files exist',
},
];
}
async applyChange(change: IPlannedChange): Promise<void> {
await formatReadme.run();
}
}
}

View File

@@ -5,4 +5,4 @@ export class TemplatesFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'templates', formatTemplates);
}
}
}

View File

@@ -5,4 +5,4 @@ export class TsconfigFormatter extends LegacyFormatter {
constructor(context: any, project: any) {
super(context, project, 'tsconfig', formatTsconfig);
}
}
}