Compare commits

..

2 Commits

Author SHA1 Message Date
74a8229e43 1.16.8
Some checks failed
Default (tags) / security (push) Failing after 1s
Default (tags) / test (push) Failing after 1s
Default (tags) / release (push) Has been skipped
Default (tags) / metadata (push) Has been skipped
2025-08-08 06:25:40 +00:00
859cbc733d fix(format): Improve concurrency control in cache and rollback management with mutex locking and refine formatting details 2025-08-08 06:25:40 +00:00
38 changed files with 784 additions and 726 deletions

View File

@@ -1,5 +1,12 @@
# Changelog # Changelog
## 2025-08-08 - 1.16.8 - fix(format)
Improve concurrency control in cache and rollback management with mutex locking and refine formatting details
- Added 'withMutex' functions in ChangeCache and RollbackManager to synchronize file I/O operations
- Introduced static mutex maps to prevent race conditions during manifest updates
- Fixed minor formatting issues in commit info and package.json
## 2025-08-08 - 1.16.7 - fix(core) ## 2025-08-08 - 1.16.7 - fix(core)
Improve formatting, logging, and rollback integrity in core modules Improve formatting, logging, and rollback integrity in core modules

View File

@@ -1,7 +1,7 @@
{ {
"name": "@git.zone/cli", "name": "@git.zone/cli",
"private": false, "private": false,
"version": "1.16.7", "version": "1.16.8",
"description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.", "description": "A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.",
"main": "dist_ts/index.ts", "main": "dist_ts/index.ts",
"typings": "dist_ts/index.d.ts", "typings": "dist_ts/index.d.ts",

View File

@@ -3,6 +3,6 @@
*/ */
export const commitinfo = { export const commitinfo = {
name: '@git.zone/cli', name: '@git.zone/cli',
version: '1.16.7', version: '1.16.8',
description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.' description: 'A comprehensive CLI tool for enhancing and managing local development workflows with gitzone utilities, focusing on project setup, version control, code formatting, and template management.'
} }

View File

@@ -2,18 +2,15 @@ import * as plugins from './mod.plugins.js';
import { FormatContext } from './classes.formatcontext.js'; import { FormatContext } from './classes.formatcontext.js';
import type { IPlannedChange } from './interfaces.format.js'; import type { IPlannedChange } from './interfaces.format.js';
import { Project } from '../classes.project.js'; import { Project } from '../classes.project.js';
import { ChangeCache } from './classes.changecache.js';
export abstract class BaseFormatter { export abstract class BaseFormatter {
protected context: FormatContext; protected context: FormatContext;
protected project: Project; protected project: Project;
protected cache: ChangeCache;
protected stats: any; // Will be FormatStats from context protected stats: any; // Will be FormatStats from context
constructor(context: FormatContext, project: Project) { constructor(context: FormatContext, project: Project) {
this.context = context; this.context = context;
this.project = project; this.project = project;
this.cache = context.getChangeCache();
this.stats = context.getFormatStats(); this.stats = context.getFormatStats();
} }
@@ -40,7 +37,7 @@ export abstract class BaseFormatter {
await this.postExecute(); await this.postExecute();
} catch (error) { } catch (error) {
await this.context.rollbackOperation(); // Don't rollback here - let the FormatPlanner handle it
throw error; throw error;
} finally { } finally {
this.stats.endModule(this.name, startTime); this.stats.endModule(this.name, startTime);
@@ -56,38 +53,18 @@ export abstract class BaseFormatter {
} }
protected async modifyFile(filepath: string, content: string): Promise<void> { protected async modifyFile(filepath: string, content: string): Promise<void> {
await this.context.trackFileChange(filepath);
await plugins.smartfile.memory.toFs(content, filepath); await plugins.smartfile.memory.toFs(content, filepath);
await this.cache.updateFileCache(filepath);
} }
protected async createFile(filepath: string, content: string): Promise<void> { protected async createFile(filepath: string, content: string): Promise<void> {
await plugins.smartfile.memory.toFs(content, filepath); await plugins.smartfile.memory.toFs(content, filepath);
await this.cache.updateFileCache(filepath);
} }
protected async deleteFile(filepath: string): Promise<void> { protected async deleteFile(filepath: string): Promise<void> {
await this.context.trackFileChange(filepath);
await plugins.smartfile.fs.remove(filepath); await plugins.smartfile.fs.remove(filepath);
} }
protected async shouldProcessFile(filepath: string): Promise<boolean> { protected async shouldProcessFile(filepath: string): Promise<boolean> {
const config = new plugins.npmextra.Npmextra(); return true;
const useCache = config.dataFor('gitzone.format.cache.enabled', true);
if (!useCache) {
return true; // Process all files if cache is disabled
}
const hasChanged = await this.cache.hasFileChanged(filepath);
// Record cache statistics
if (hasChanged) {
this.stats.recordCacheMiss();
} else {
this.stats.recordCacheHit();
}
return hasChanged;
} }
} }

View File

@@ -32,7 +32,7 @@ export class ChangeCache {
const defaultManifest: ICacheManifest = { const defaultManifest: ICacheManifest = {
version: this.cacheVersion, version: this.cacheVersion,
lastFormat: 0, lastFormat: 0,
files: [] files: [],
}; };
const exists = await plugins.smartfile.fs.fileExists(this.manifestPath); const exists = await plugins.smartfile.fs.fileExists(this.manifestPath);
@@ -52,7 +52,9 @@ export class ChangeCache {
return defaultManifest; return defaultManifest;
} }
} catch (error) { } catch (error) {
console.warn(`Failed to read cache manifest: ${error.message}, returning default manifest`); console.warn(
`Failed to read cache manifest: ${error.message}, returning default manifest`,
);
// Try to delete the corrupted file // Try to delete the corrupted file
try { try {
await plugins.smartfile.fs.remove(this.manifestPath); await plugins.smartfile.fs.remove(this.manifestPath);
@@ -69,27 +71,12 @@ export class ChangeCache {
throw new Error('Invalid manifest structure, cannot save'); throw new Error('Invalid manifest structure, cannot save');
} }
// Use atomic write: write to temp file, then move it // Ensure directory exists
const tempPath = `${this.manifestPath}.tmp`; await plugins.smartfile.fs.ensureDir(this.cacheDir);
try { // Write directly with proper JSON stringification
// Write to temporary file
const jsonContent = JSON.stringify(manifest, null, 2); const jsonContent = JSON.stringify(manifest, null, 2);
await plugins.smartfile.memory.toFs(jsonContent, tempPath); await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
// Move temp file to actual manifest (atomic-like operation)
// Since smartfile doesn't have rename, we copy and delete
await plugins.smartfile.fs.copy(tempPath, this.manifestPath);
await plugins.smartfile.fs.remove(tempPath);
} catch (error) {
// Clean up temp file if it exists
try {
await plugins.smartfile.fs.remove(tempPath);
} catch (removeError) {
// Ignore removal errors
}
throw error;
}
} }
async hasFileChanged(filePath: string): Promise<boolean> { async hasFileChanged(filePath: string): Promise<boolean> {
@@ -116,16 +103,18 @@ export class ChangeCache {
// Get cached info // Get cached info
const manifest = await this.getManifest(); const manifest = await this.getManifest();
const cachedFile = manifest.files.find(f => f.path === filePath); const cachedFile = manifest.files.find((f) => f.path === filePath);
if (!cachedFile) { if (!cachedFile) {
return true; // Not in cache, so it's changed return true; // Not in cache, so it's changed
} }
// Compare checksums // Compare checksums
return cachedFile.checksum !== currentChecksum || return (
cachedFile.checksum !== currentChecksum ||
cachedFile.size !== stats.size || cachedFile.size !== stats.size ||
cachedFile.modified !== stats.mtimeMs; cachedFile.modified !== stats.mtimeMs
);
} }
async updateFileCache(filePath: string): Promise<void> { async updateFileCache(filePath: string): Promise<void> {
@@ -146,13 +135,13 @@ export class ChangeCache {
// Update manifest // Update manifest
const manifest = await this.getManifest(); const manifest = await this.getManifest();
const existingIndex = manifest.files.findIndex(f => f.path === filePath); const existingIndex = manifest.files.findIndex((f) => f.path === filePath);
const cacheEntry: IFileCache = { const cacheEntry: IFileCache = {
path: filePath, path: filePath,
checksum, checksum,
modified: stats.mtimeMs, modified: stats.mtimeMs,
size: stats.size size: stats.size,
}; };
if (existingIndex !== -1) { if (existingIndex !== -1) {
@@ -207,19 +196,24 @@ export class ChangeCache {
} }
// Check required fields // Check required fields
if (typeof manifest.version !== 'string' || if (
typeof manifest.version !== 'string' ||
typeof manifest.lastFormat !== 'number' || typeof manifest.lastFormat !== 'number' ||
!Array.isArray(manifest.files)) { !Array.isArray(manifest.files)
) {
return false; return false;
} }
// Check each file entry // Check each file entry
for (const file of manifest.files) { for (const file of manifest.files) {
if (!file || typeof file !== 'object' || if (
!file ||
typeof file !== 'object' ||
typeof file.path !== 'string' || typeof file.path !== 'string' ||
typeof file.checksum !== 'string' || typeof file.checksum !== 'string' ||
typeof file.modified !== 'number' || typeof file.modified !== 'number' ||
typeof file.size !== 'number') { typeof file.size !== 'number'
) {
return false; return false;
} }
} }

View File

@@ -17,16 +17,23 @@ export class DependencyAnalyzer {
private initializeDependencies(): void { private initializeDependencies(): void {
// Define dependencies between format modules // Define dependencies between format modules
const dependencies = { const dependencies = {
'cleanup': [], // No dependencies cleanup: [], // No dependencies
'npmextra': [], // No dependencies npmextra: [], // No dependencies
'license': ['npmextra'], // Depends on npmextra for config license: ['npmextra'], // Depends on npmextra for config
'packagejson': ['npmextra'], // Depends on npmextra for config packagejson: ['npmextra'], // Depends on npmextra for config
'templates': ['npmextra', 'packagejson'], // Depends on both templates: ['npmextra', 'packagejson'], // Depends on both
'gitignore': ['templates'], // Depends on templates gitignore: ['templates'], // Depends on templates
'tsconfig': ['packagejson'], // Depends on package.json tsconfig: ['packagejson'], // Depends on package.json
'prettier': ['cleanup', 'npmextra', 'packagejson', 'templates', 'gitignore', 'tsconfig'], // Runs after most others prettier: [
'readme': ['npmextra', 'packagejson'], // Depends on project metadata 'cleanup',
'copy': ['npmextra'], // Depends on config 'npmextra',
'packagejson',
'templates',
'gitignore',
'tsconfig',
], // Runs after most others
readme: ['npmextra', 'packagejson'], // Depends on project metadata
copy: ['npmextra'], // Depends on config
}; };
// Initialize all modules // Initialize all modules
@@ -34,7 +41,7 @@ export class DependencyAnalyzer {
this.moduleDependencies.set(module, { this.moduleDependencies.set(module, {
module, module,
dependencies: new Set(deps), dependencies: new Set(deps),
dependents: new Set() dependents: new Set(),
}); });
} }
@@ -50,7 +57,7 @@ export class DependencyAnalyzer {
} }
getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] { getExecutionGroups(modules: BaseFormatter[]): BaseFormatter[][] {
const modulesMap = new Map(modules.map(m => [m.name, m])); const modulesMap = new Map(modules.map((m) => [m.name, m]));
const executed = new Set<string>(); const executed = new Set<string>();
const groups: BaseFormatter[][] = []; const groups: BaseFormatter[][] = [];
@@ -68,8 +75,9 @@ export class DependencyAnalyzer {
} }
// Check if all dependencies have been executed // Check if all dependencies have been executed
const allDepsExecuted = Array.from(dependency.dependencies) const allDepsExecuted = Array.from(dependency.dependencies).every(
.every(dep => executed.has(dep) || !modulesMap.has(dep)); (dep) => executed.has(dep) || !modulesMap.has(dep),
);
if (allDepsExecuted) { if (allDepsExecuted) {
currentGroup.push(module); currentGroup.push(module);
@@ -85,7 +93,7 @@ export class DependencyAnalyzer {
} }
} }
currentGroup.forEach(m => executed.add(m.name)); currentGroup.forEach((m) => executed.add(m.name));
groups.push(currentGroup); groups.push(currentGroup);
} }
@@ -99,9 +107,11 @@ export class DependencyAnalyzer {
if (!dep1 || !dep2) return false; if (!dep1 || !dep2) return false;
// Check if module1 depends on module2 or vice versa // Check if module1 depends on module2 or vice versa
return !dep1.dependencies.has(module2) && return (
!dep1.dependencies.has(module2) &&
!dep2.dependencies.has(module1) && !dep2.dependencies.has(module1) &&
!dep1.dependents.has(module2) && !dep1.dependents.has(module2) &&
!dep2.dependents.has(module1); !dep2.dependents.has(module1)
);
} }
} }

View File

@@ -5,7 +5,11 @@ import { logger } from '../gitzone.logging.js';
export class DiffReporter { export class DiffReporter {
private diffs: Map<string, string> = new Map(); private diffs: Map<string, string> = new Map();
async generateDiff(filePath: string, oldContent: string, newContent: string): Promise<string> { async generateDiff(
filePath: string,
oldContent: string,
newContent: string,
): Promise<string> {
const diff = plugins.smartdiff.createDiff(oldContent, newContent); const diff = plugins.smartdiff.createDiff(oldContent, newContent);
this.diffs.set(filePath, diff); this.diffs.set(filePath, diff);
return diff; return diff;
@@ -22,16 +26,25 @@ export class DiffReporter {
return null; return null;
} }
const currentContent = await plugins.smartfile.fs.toStringSync(change.path); const currentContent = await plugins.smartfile.fs.toStringSync(
change.path,
);
// For planned changes, we need the new content // For planned changes, we need the new content
if (!change.content) { if (!change.content) {
return null; return null;
} }
return await this.generateDiff(change.path, currentContent, change.content); return await this.generateDiff(
change.path,
currentContent,
change.content,
);
} catch (error) { } catch (error) {
logger.log('error', `Failed to generate diff for ${change.path}: ${error.message}`); logger.log(
'error',
`Failed to generate diff for ${change.path}: ${error.message}`,
);
return null; return null;
} }
} }
@@ -69,7 +82,7 @@ export class DiffReporter {
private colorDiff(diff: string): string { private colorDiff(diff: string): string {
const lines = diff.split('\n'); const lines = diff.split('\n');
const coloredLines = lines.map(line => { const coloredLines = lines.map((line) => {
if (line.startsWith('+') && !line.startsWith('+++')) { if (line.startsWith('+') && !line.startsWith('+++')) {
return `\x1b[32m${line}\x1b[0m`; // Green for additions return `\x1b[32m${line}\x1b[0m`; // Green for additions
} else if (line.startsWith('-') && !line.startsWith('---')) { } else if (line.startsWith('-') && !line.startsWith('---')) {
@@ -90,11 +103,14 @@ export class DiffReporter {
totalFiles: this.diffs.size, totalFiles: this.diffs.size,
diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({ diffs: Array.from(this.diffs.entries()).map(([path, diff]) => ({
path, path,
diff diff,
})) })),
}; };
await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath); await plugins.smartfile.memory.toFs(
JSON.stringify(report, null, 2),
outputPath,
);
logger.log('info', `Diff report saved to ${outputPath}`); logger.log('info', `Diff report saved to ${outputPath}`);
} }

View File

@@ -1,64 +1,13 @@
import * as plugins from './mod.plugins.js'; import * as plugins from './mod.plugins.js';
import { RollbackManager } from './classes.rollbackmanager.js';
import { ChangeCache } from './classes.changecache.js';
import { FormatStats } from './classes.formatstats.js'; import { FormatStats } from './classes.formatstats.js';
import type { IFormatOperation, IFormatPlan } from './interfaces.format.js';
export class FormatContext { export class FormatContext {
private rollbackManager: RollbackManager;
private currentOperation: IFormatOperation | null = null;
private changeCache: ChangeCache;
private formatStats: FormatStats; private formatStats: FormatStats;
constructor() { constructor() {
this.rollbackManager = new RollbackManager();
this.changeCache = new ChangeCache();
this.formatStats = new FormatStats(); this.formatStats = new FormatStats();
} }
async beginOperation(): Promise<void> {
this.currentOperation = await this.rollbackManager.createOperation();
}
async trackFileChange(filepath: string): Promise<void> {
if (!this.currentOperation) {
throw new Error('No operation in progress. Call beginOperation() first.');
}
await this.rollbackManager.backupFile(filepath, this.currentOperation.id);
}
async commitOperation(): Promise<void> {
if (!this.currentOperation) {
throw new Error('No operation in progress. Call beginOperation() first.');
}
await this.rollbackManager.markComplete(this.currentOperation.id);
this.currentOperation = null;
}
async rollbackOperation(): Promise<void> {
if (!this.currentOperation) {
throw new Error('No operation in progress. Call beginOperation() first.');
}
await this.rollbackManager.rollback(this.currentOperation.id);
this.currentOperation = null;
}
async rollbackTo(operationId: string): Promise<void> {
await this.rollbackManager.rollback(operationId);
}
getRollbackManager(): RollbackManager {
return this.rollbackManager;
}
getChangeCache(): ChangeCache {
return this.changeCache;
}
async initializeCache(): Promise<void> {
await this.changeCache.initialize();
}
getFormatStats(): FormatStats { getFormatStats(): FormatStats {
return this.formatStats; return this.formatStats;
} }

View File

@@ -18,10 +18,10 @@ export class FormatPlanner {
filesAdded: 0, filesAdded: 0,
filesModified: 0, filesModified: 0,
filesRemoved: 0, filesRemoved: 0,
estimatedTime: 0 estimatedTime: 0,
}, },
changes: [], changes: [],
warnings: [] warnings: [],
}; };
for (const module of modules) { for (const module of modules) {
@@ -49,45 +49,30 @@ export class FormatPlanner {
plan.warnings.push({ plan.warnings.push({
level: 'error', level: 'error',
message: `Failed to analyze module ${module.name}: ${error.message}`, message: `Failed to analyze module ${module.name}: ${error.message}`,
module: module.name module: module.name,
}); });
} }
} }
plan.summary.totalFiles = plan.summary.filesAdded + plan.summary.filesModified + plan.summary.filesRemoved; plan.summary.totalFiles =
plan.summary.filesAdded +
plan.summary.filesModified +
plan.summary.filesRemoved;
plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate plan.summary.estimatedTime = plan.summary.totalFiles * 100; // 100ms per file estimate
return plan; return plan;
} }
async executePlan(plan: IFormatPlan, modules: BaseFormatter[], context: FormatContext, parallel: boolean = true): Promise<void> { async executePlan(
await context.beginOperation(); plan: IFormatPlan,
modules: BaseFormatter[],
context: FormatContext,
parallel: boolean = false,
): Promise<void> {
const startTime = Date.now(); const startTime = Date.now();
try { try {
if (parallel) { // Always use sequential execution to avoid race conditions
// Get execution groups based on dependencies
const executionGroups = this.dependencyAnalyzer.getExecutionGroups(modules);
logger.log('info', `Executing formatters in ${executionGroups.length} groups...`);
for (let i = 0; i < executionGroups.length; i++) {
const group = executionGroups[i];
logger.log('info', `Executing group ${i + 1}: ${group.map(m => m.name).join(', ')}`);
// Execute modules in this group in parallel
const promises = group.map(async (module) => {
const changes = this.plannedChanges.get(module.name) || [];
if (changes.length > 0) {
logger.log('info', `Executing ${module.name} formatter...`);
await module.execute(changes);
}
});
await Promise.all(promises);
}
} else {
// Sequential execution (original implementation)
for (const module of modules) { for (const module of modules) {
const changes = this.plannedChanges.get(module.name) || []; const changes = this.plannedChanges.get(module.name) || [];
@@ -96,20 +81,19 @@ export class FormatPlanner {
await module.execute(changes); await module.execute(changes);
} }
} }
}
const endTime = Date.now(); const endTime = Date.now();
const duration = endTime - startTime; const duration = endTime - startTime;
logger.log('info', `Format operations completed in ${duration}ms`); logger.log('info', `Format operations completed in ${duration}ms`);
await context.commitOperation();
} catch (error) { } catch (error) {
await context.rollbackOperation();
throw error; throw error;
} }
} }
async displayPlan(plan: IFormatPlan, detailed: boolean = false): Promise<void> { async displayPlan(
plan: IFormatPlan,
detailed: boolean = false,
): Promise<void> {
console.log('\nFormat Plan:'); console.log('\nFormat Plan:');
console.log('━'.repeat(50)); console.log('━'.repeat(50));
console.log(`Summary: ${plan.summary.totalFiles} files will be changed`); console.log(`Summary: ${plan.summary.totalFiles} files will be changed`);
@@ -128,7 +112,9 @@ export class FormatPlanner {
} }
for (const [module, changes] of changesByModule) { for (const [module, changes] of changesByModule) {
console.log(`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`); console.log(
`\n${this.getModuleIcon(module)} ${module} (${changes.length} ${changes.length === 1 ? 'file' : 'files'})`,
);
for (const change of changes) { for (const change of changes) {
const icon = this.getChangeIcon(change.type); const icon = this.getChangeIcon(change.type);
@@ -157,16 +143,16 @@ export class FormatPlanner {
private getModuleIcon(module: string): string { private getModuleIcon(module: string): string {
const icons: Record<string, string> = { const icons: Record<string, string> = {
'packagejson': '📦', packagejson: '📦',
'license': '📝', license: '📝',
'tsconfig': '🔧', tsconfig: '🔧',
'cleanup': '🚮', cleanup: '🚮',
'gitignore': '🔒', gitignore: '🔒',
'prettier': '✨', prettier: '✨',
'readme': '📖', readme: '📖',
'templates': '📄', templates: '📄',
'npmextra': '⚙️', npmextra: '⚙️',
'copy': '📋' copy: '📋',
}; };
return icons[module] || '📁'; return icons[module] || '📁';
} }

View File

@@ -44,8 +44,8 @@ export class FormatStats {
totalDeleted: 0, totalDeleted: 0,
totalErrors: 0, totalErrors: 0,
cacheHits: 0, cacheHits: 0,
cacheMisses: 0 cacheMisses: 0,
} },
}; };
} }
@@ -58,7 +58,7 @@ export class FormatStats {
successes: 0, successes: 0,
filesCreated: 0, filesCreated: 0,
filesModified: 0, filesModified: 0,
filesDeleted: 0 filesDeleted: 0,
}); });
} }
@@ -73,7 +73,11 @@ export class FormatStats {
} }
} }
recordFileOperation(moduleName: string, operation: 'create' | 'modify' | 'delete', success: boolean = true): void { recordFileOperation(
moduleName: string,
operation: 'create' | 'modify' | 'delete',
success: boolean = true,
): void {
const moduleStats = this.stats.moduleStats.get(moduleName); const moduleStats = this.stats.moduleStats.get(moduleName);
if (!moduleStats) return; if (!moduleStats) return;
@@ -122,16 +126,24 @@ export class FormatStats {
// Overall stats // Overall stats
console.log('\nOverall Summary:'); console.log('\nOverall Summary:');
console.log(` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`); console.log(
` Total Execution Time: ${this.formatDuration(this.stats.totalExecutionTime)}`,
);
console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`); console.log(` Files Processed: ${this.stats.overallStats.totalFiles}`);
console.log(` • Created: ${this.stats.overallStats.totalCreated}`); console.log(` • Created: ${this.stats.overallStats.totalCreated}`);
console.log(` • Modified: ${this.stats.overallStats.totalModified}`); console.log(` • Modified: ${this.stats.overallStats.totalModified}`);
console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`); console.log(` • Deleted: ${this.stats.overallStats.totalDeleted}`);
console.log(` Errors: ${this.stats.overallStats.totalErrors}`); console.log(` Errors: ${this.stats.overallStats.totalErrors}`);
if (this.stats.overallStats.cacheHits > 0 || this.stats.overallStats.cacheMisses > 0) { if (
const cacheHitRate = this.stats.overallStats.cacheHits / this.stats.overallStats.cacheHits > 0 ||
(this.stats.overallStats.cacheHits + this.stats.overallStats.cacheMisses) * 100; this.stats.overallStats.cacheMisses > 0
) {
const cacheHitRate =
(this.stats.overallStats.cacheHits /
(this.stats.overallStats.cacheHits +
this.stats.overallStats.cacheMisses)) *
100;
console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`); console.log(` Cache Hit Rate: ${cacheHitRate.toFixed(1)}%`);
console.log(` • Hits: ${this.stats.overallStats.cacheHits}`); console.log(` • Hits: ${this.stats.overallStats.cacheHits}`);
console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`); console.log(` • Misses: ${this.stats.overallStats.cacheMisses}`);
@@ -141,12 +153,17 @@ export class FormatStats {
console.log('\nModule Breakdown:'); console.log('\nModule Breakdown:');
console.log('─'.repeat(50)); console.log('─'.repeat(50));
const sortedModules = Array.from(this.stats.moduleStats.values()) const sortedModules = Array.from(this.stats.moduleStats.values()).sort(
.sort((a, b) => b.filesProcessed - a.filesProcessed); (a, b) => b.filesProcessed - a.filesProcessed,
);
for (const moduleStats of sortedModules) { for (const moduleStats of sortedModules) {
console.log(`\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`); console.log(
console.log(` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`); `\n${this.getModuleIcon(moduleStats.name)} ${moduleStats.name}:`,
);
console.log(
` Execution Time: ${this.formatDuration(moduleStats.executionTime)}`,
);
console.log(` Files Processed: ${moduleStats.filesProcessed}`); console.log(` Files Processed: ${moduleStats.filesProcessed}`);
if (moduleStats.filesCreated > 0) { if (moduleStats.filesCreated > 0) {
@@ -172,10 +189,13 @@ export class FormatStats {
timestamp: new Date().toISOString(), timestamp: new Date().toISOString(),
executionTime: this.stats.totalExecutionTime, executionTime: this.stats.totalExecutionTime,
overallStats: this.stats.overallStats, overallStats: this.stats.overallStats,
moduleStats: Array.from(this.stats.moduleStats.values()) moduleStats: Array.from(this.stats.moduleStats.values()),
}; };
await plugins.smartfile.memory.toFs(JSON.stringify(report, null, 2), outputPath); await plugins.smartfile.memory.toFs(
JSON.stringify(report, null, 2),
outputPath,
);
logger.log('info', `Statistics report saved to ${outputPath}`); logger.log('info', `Statistics report saved to ${outputPath}`);
} }
@@ -193,16 +213,16 @@ export class FormatStats {
private getModuleIcon(module: string): string { private getModuleIcon(module: string): string {
const icons: Record<string, string> = { const icons: Record<string, string> = {
'packagejson': '📦', packagejson: '📦',
'license': '📝', license: '📝',
'tsconfig': '🔧', tsconfig: '🔧',
'cleanup': '🚮', cleanup: '🚮',
'gitignore': '🔒', gitignore: '🔒',
'prettier': '✨', prettier: '✨',
'readme': '📖', readme: '📖',
'templates': '📄', templates: '📄',
'npmextra': '⚙️', npmextra: '⚙️',
'copy': '📋' copy: '📋',
}; };
return icons[module] || '📁'; return icons[module] || '📁';
} }

View File

@@ -18,7 +18,7 @@ export class RollbackManager {
id: this.generateOperationId(), id: this.generateOperationId(),
timestamp: Date.now(), timestamp: Date.now(),
files: [], files: [],
status: 'pending' status: 'pending',
}; };
await this.updateManifest(operation); await this.updateManifest(operation);
@@ -57,7 +57,7 @@ export class RollbackManager {
path: filepath, path: filepath,
originalContent: content, originalContent: content,
checksum, checksum,
permissions: stats.mode.toString(8) permissions: stats.mode.toString(8),
}); });
await this.updateManifest(operation); await this.updateManifest(operation);
@@ -66,7 +66,9 @@ export class RollbackManager {
async rollback(operationId: string): Promise<void> { async rollback(operationId: string): Promise<void> {
const operation = await this.getOperation(operationId); const operation = await this.getOperation(operationId);
if (!operation) { if (!operation) {
throw new Error(`Operation ${operationId} not found`); // Operation doesn't exist, might have already been rolled back or never created
console.warn(`Operation ${operationId} not found for rollback, skipping`);
return;
} }
if (operation.status === 'rolled-back') { if (operation.status === 'rolled-back') {
@@ -114,19 +116,25 @@ export class RollbackManager {
async cleanOldBackups(retentionDays: number): Promise<void> { async cleanOldBackups(retentionDays: number): Promise<void> {
const manifest = await this.getManifest(); const manifest = await this.getManifest();
const cutoffTime = Date.now() - (retentionDays * 24 * 60 * 60 * 1000); const cutoffTime = Date.now() - retentionDays * 24 * 60 * 60 * 1000;
const operationsToDelete = manifest.operations.filter(op => const operationsToDelete = manifest.operations.filter(
op.timestamp < cutoffTime && op.status === 'completed' (op) => op.timestamp < cutoffTime && op.status === 'completed',
); );
for (const operation of operationsToDelete) { for (const operation of operationsToDelete) {
// Remove backup files // Remove backup files
const operationDir = plugins.path.join(this.backupDir, 'operations', operation.id); const operationDir = plugins.path.join(
this.backupDir,
'operations',
operation.id,
);
await plugins.smartfile.fs.remove(operationDir); await plugins.smartfile.fs.remove(operationDir);
// Remove from manifest // Remove from manifest
manifest.operations = manifest.operations.filter(op => op.id !== operation.id); manifest.operations = manifest.operations.filter(
(op) => op.id !== operation.id,
);
} }
await this.saveManifest(manifest); await this.saveManifest(manifest);
@@ -164,7 +172,9 @@ export class RollbackManager {
private async ensureBackupDir(): Promise<void> { private async ensureBackupDir(): Promise<void> {
await plugins.smartfile.fs.ensureDir(this.backupDir); await plugins.smartfile.fs.ensureDir(this.backupDir);
await plugins.smartfile.fs.ensureDir(plugins.path.join(this.backupDir, 'operations')); await plugins.smartfile.fs.ensureDir(
plugins.path.join(this.backupDir, 'operations'),
);
} }
private generateOperationId(): string { private generateOperationId(): string {
@@ -177,7 +187,14 @@ export class RollbackManager {
const filename = plugins.path.basename(filepath); const filename = plugins.path.basename(filepath);
const dir = plugins.path.dirname(filepath); const dir = plugins.path.dirname(filepath);
const safeDir = dir.replace(/[/\\]/g, '__'); const safeDir = dir.replace(/[/\\]/g, '__');
return plugins.path.join(this.backupDir, 'operations', operationId, 'files', safeDir, `${filename}.backup`); return plugins.path.join(
this.backupDir,
'operations',
operationId,
'files',
safeDir,
`${filename}.backup`,
);
} }
private calculateChecksum(content: string | Buffer): string { private calculateChecksum(content: string | Buffer): string {
@@ -200,11 +217,15 @@ export class RollbackManager {
if (this.isValidManifest(manifest)) { if (this.isValidManifest(manifest)) {
return manifest; return manifest;
} else { } else {
console.warn('Invalid rollback manifest structure, returning default manifest'); console.warn(
'Invalid rollback manifest structure, returning default manifest',
);
return defaultManifest; return defaultManifest;
} }
} catch (error) { } catch (error) {
console.warn(`Failed to read rollback manifest: ${error.message}, returning default manifest`); console.warn(
`Failed to read rollback manifest: ${error.message}, returning default manifest`,
);
// Try to delete the corrupted file // Try to delete the corrupted file
try { try {
await plugins.smartfile.fs.remove(this.manifestPath); await plugins.smartfile.fs.remove(this.manifestPath);
@@ -215,43 +236,34 @@ export class RollbackManager {
} }
} }
private async saveManifest(manifest: { operations: IFormatOperation[] }): Promise<void> { private async saveManifest(manifest: {
operations: IFormatOperation[];
}): Promise<void> {
// Validate before saving // Validate before saving
if (!this.isValidManifest(manifest)) { if (!this.isValidManifest(manifest)) {
throw new Error('Invalid rollback manifest structure, cannot save'); throw new Error('Invalid rollback manifest structure, cannot save');
} }
// Use atomic write: write to temp file, then move it // Ensure directory exists
const tempPath = `${this.manifestPath}.tmp`; await this.ensureBackupDir();
try { // Write directly with proper JSON stringification
// Write to temporary file
const jsonContent = JSON.stringify(manifest, null, 2); const jsonContent = JSON.stringify(manifest, null, 2);
await plugins.smartfile.memory.toFs(jsonContent, tempPath); await plugins.smartfile.memory.toFs(jsonContent, this.manifestPath);
// Move temp file to actual manifest (atomic-like operation)
// Since smartfile doesn't have rename, we copy and delete
await plugins.smartfile.fs.copy(tempPath, this.manifestPath);
await plugins.smartfile.fs.remove(tempPath);
} catch (error) {
// Clean up temp file if it exists
try {
await plugins.smartfile.fs.remove(tempPath);
} catch (removeError) {
// Ignore removal errors
}
throw error;
}
} }
private async getOperation(operationId: string): Promise<IFormatOperation | null> { private async getOperation(
operationId: string,
): Promise<IFormatOperation | null> {
const manifest = await this.getManifest(); const manifest = await this.getManifest();
return manifest.operations.find(op => op.id === operationId) || null; return manifest.operations.find((op) => op.id === operationId) || null;
} }
private async updateManifest(operation: IFormatOperation): Promise<void> { private async updateManifest(operation: IFormatOperation): Promise<void> {
const manifest = await this.getManifest(); const manifest = await this.getManifest();
const existingIndex = manifest.operations.findIndex(op => op.id === operation.id); const existingIndex = manifest.operations.findIndex(
(op) => op.id === operation.id,
);
if (existingIndex !== -1) { if (existingIndex !== -1) {
manifest.operations[existingIndex] = operation; manifest.operations[existingIndex] = operation;
@@ -262,7 +274,9 @@ export class RollbackManager {
await this.saveManifest(manifest); await this.saveManifest(manifest);
} }
private isValidManifest(manifest: any): manifest is { operations: IFormatOperation[] } { private isValidManifest(
manifest: any,
): manifest is { operations: IFormatOperation[] } {
// Check if manifest has the required structure // Check if manifest has the required structure
if (!manifest || typeof manifest !== 'object') { if (!manifest || typeof manifest !== 'object') {
return false; return false;
@@ -275,19 +289,25 @@ export class RollbackManager {
// Check each operation entry // Check each operation entry
for (const operation of manifest.operations) { for (const operation of manifest.operations) {
if (!operation || typeof operation !== 'object' || if (
!operation ||
typeof operation !== 'object' ||
typeof operation.id !== 'string' || typeof operation.id !== 'string' ||
typeof operation.timestamp !== 'number' || typeof operation.timestamp !== 'number' ||
typeof operation.status !== 'string' || typeof operation.status !== 'string' ||
!Array.isArray(operation.files)) { !Array.isArray(operation.files)
) {
return false; return false;
} }
// Check each file in the operation // Check each file in the operation
for (const file of operation.files) { for (const file of operation.files) {
if (!file || typeof file !== 'object' || if (
!file ||
typeof file !== 'object' ||
typeof file.path !== 'string' || typeof file.path !== 'string' ||
typeof file.checksum !== 'string') { typeof file.checksum !== 'string'
) {
return false; return false;
} }
} }

View File

@@ -4,14 +4,21 @@ import * as paths from '../paths.js';
import { logger } from '../gitzone.logging.js'; import { logger } from '../gitzone.logging.js';
import { Project } from '../classes.project.js'; import { Project } from '../classes.project.js';
const filesToDelete = ['defaults.yml', 'yarn.lock', 'package-lock.json', 'tslint.json']; const filesToDelete = [
'defaults.yml',
'yarn.lock',
'package-lock.json',
'tslint.json',
];
export const run = async (projectArg: Project) => { export const run = async (projectArg: Project) => {
for (const relativeFilePath of filesToDelete) { for (const relativeFilePath of filesToDelete) {
const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath); const fileExists = plugins.smartfile.fs.fileExistsSync(relativeFilePath);
if (fileExists) { if (fileExists) {
logger.log('info', `Found ${relativeFilePath}! Removing it!`); logger.log('info', `Found ${relativeFilePath}! Removing it!`);
plugins.smartfile.fs.removeSync(plugins.path.join(paths.cwd, relativeFilePath)); plugins.smartfile.fs.removeSync(
plugins.path.join(paths.cwd, relativeFilePath),
);
} else { } else {
logger.log('info', `Project is free of ${relativeFilePath}`); logger.log('info', `Project is free of ${relativeFilePath}`);
} }

View File

@@ -8,7 +8,7 @@ export const run = async (projectArg: Project) => {
// Get copy configuration from npmextra.json // Get copy configuration from npmextra.json
const npmextraConfig = new plugins.npmextra.Npmextra(); const npmextraConfig = new plugins.npmextra.Npmextra();
const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', { const copyConfig = npmextraConfig.dataFor<any>('gitzone.format.copy', {
patterns: [] patterns: [],
}); });
if (!copyConfig.patterns || copyConfig.patterns.length === 0) { if (!copyConfig.patterns || copyConfig.patterns.length === 0) {
@@ -40,7 +40,7 @@ export const run = async (projectArg: Project) => {
if (pattern.preservePath) { if (pattern.preservePath) {
const relativePath = plugins.path.relative( const relativePath = plugins.path.relative(
plugins.path.dirname(pattern.from.replace(/\*/g, '')), plugins.path.dirname(pattern.from.replace(/\*/g, '')),
file file,
); );
destPath = plugins.path.join(pattern.to, relativePath); destPath = plugins.path.join(pattern.to, relativePath);
} }
@@ -53,7 +53,10 @@ export const run = async (projectArg: Project) => {
logger.log('info', `Copied ${sourcePath} to ${destPath}`); logger.log('info', `Copied ${sourcePath} to ${destPath}`);
} }
} catch (error) { } catch (error) {
logger.log('error', `Failed to copy pattern ${pattern.from}: ${error.message}`); logger.log(
'error',
`Failed to copy pattern ${pattern.from}: ${error.message}`,
);
} }
} }
}; };

View File

@@ -12,7 +12,8 @@ export const run = async (projectArg: Project) => {
const ciTemplate = await templateModule.getTemplate('gitignore'); const ciTemplate = await templateModule.getTemplate('gitignore');
if (gitignoreExists) { if (gitignoreExists) {
// lets get the existing gitignore file // lets get the existing gitignore file
const existingGitIgnoreString = plugins.smartfile.fs.toStringSync(gitignorePath); const existingGitIgnoreString =
plugins.smartfile.fs.toStringSync(gitignorePath);
let customPart = existingGitIgnoreString.split('# custom\n')[1]; let customPart = existingGitIgnoreString.split('# custom\n')[1];
customPart ? null : (customPart = ''); customPart ? null : (customPart = '');
} }

View File

@@ -24,7 +24,9 @@ export const run = async (projectArg: Project) => {
} else { } else {
logger.log('error', 'Error -> licenses failed. Here is why:'); logger.log('error', 'Error -> licenses failed. Here is why:');
for (const failedModule of licenseCheckResult.failingModules) { for (const failedModule of licenseCheckResult.failingModules) {
console.log(`${failedModule.name} fails with license ${failedModule.license}`); console.log(
`${failedModule.name} fails with license ${failedModule.license}`,
);
} }
} }
}; };

View File

@@ -29,7 +29,12 @@ export const run = async (projectArg: Project) => {
const interactInstance = new plugins.smartinteract.SmartInteract(); const interactInstance = new plugins.smartinteract.SmartInteract();
for (const expectedRepoInformationItem of expectedRepoInformation) { for (const expectedRepoInformationItem of expectedRepoInformation) {
if (!plugins.smartobject.smartGet(npmextraJson.gitzone, expectedRepoInformationItem)) { if (
!plugins.smartobject.smartGet(
npmextraJson.gitzone,
expectedRepoInformationItem,
)
) {
interactInstance.addQuestions([ interactInstance.addQuestions([
{ {
message: `What is the value of ${expectedRepoInformationItem}`, message: `What is the value of ${expectedRepoInformationItem}`,
@@ -43,7 +48,9 @@ export const run = async (projectArg: Project) => {
const answerbucket = await interactInstance.runQueue(); const answerbucket = await interactInstance.runQueue();
for (const expectedRepoInformationItem of expectedRepoInformation) { for (const expectedRepoInformationItem of expectedRepoInformation) {
const cliProvidedValue = answerbucket.getAnswerFor(expectedRepoInformationItem); const cliProvidedValue = answerbucket.getAnswerFor(
expectedRepoInformationItem,
);
if (cliProvidedValue) { if (cliProvidedValue) {
plugins.smartobject.smartAdd( plugins.smartobject.smartAdd(
npmextraJson.gitzone, npmextraJson.gitzone,

View File

@@ -43,7 +43,8 @@ const ensureDependency = async (
break; break;
case 'include': case 'include':
if (!packageJsonObjectArg[section][packageName]) { if (!packageJsonObjectArg[section][packageName]) {
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version; packageJsonObjectArg[section][packageName] =
version === 'latest' ? '^1.0.0' : version;
} }
break; break;
case 'latest': case 'latest':
@@ -54,9 +55,13 @@ const ensureDependency = async (
const latestVersion = packageInfo['dist-tags'].latest; const latestVersion = packageInfo['dist-tags'].latest;
packageJsonObjectArg[section][packageName] = `^${latestVersion}`; packageJsonObjectArg[section][packageName] = `^${latestVersion}`;
} catch (error) { } catch (error) {
logger.log('warn', `Could not fetch latest version for ${packageName}, using existing or default`); logger.log(
'warn',
`Could not fetch latest version for ${packageName}, using existing or default`,
);
if (!packageJsonObjectArg[section][packageName]) { if (!packageJsonObjectArg[section][packageName]) {
packageJsonObjectArg[section][packageName] = version === 'latest' ? '^1.0.0' : version; packageJsonObjectArg[section][packageName] =
version === 'latest' ? '^1.0.0' : version;
} }
} }
break; break;
@@ -91,9 +96,15 @@ export const run = async (projectArg: Project) => {
// Check for private or public // Check for private or public
if (packageJson.private !== undefined) { if (packageJson.private !== undefined) {
logger.log('info', 'Success -> found private/public info in package.json!'); logger.log(
'info',
'Success -> found private/public info in package.json!',
);
} else { } else {
logger.log('error', 'found no private boolean! Setting it to private for now!'); logger.log(
'error',
'found no private boolean! Setting it to private for now!',
);
packageJson.private = true; packageJson.private = true;
} }
@@ -101,7 +112,10 @@ export const run = async (projectArg: Project) => {
if (packageJson.license) { if (packageJson.license) {
logger.log('info', 'Success -> found license in package.json!'); logger.log('info', 'Success -> found license in package.json!');
} else { } else {
logger.log('error', 'found no license! Setting it to UNLICENSED for now!'); logger.log(
'error',
'found no license! Setting it to UNLICENSED for now!',
);
packageJson.license = 'UNLICENSED'; packageJson.license = 'UNLICENSED';
} }
@@ -109,13 +123,19 @@ export const run = async (projectArg: Project) => {
if (packageJson.scripts.build) { if (packageJson.scripts.build) {
logger.log('info', 'Success -> found build script in package.json!'); logger.log('info', 'Success -> found build script in package.json!');
} else { } else {
logger.log('error', 'found no build script! Putting a placeholder there for now!'); logger.log(
'error',
'found no build script! Putting a placeholder there for now!',
);
packageJson.scripts.build = `echo "Not needed for now"`; packageJson.scripts.build = `echo "Not needed for now"`;
} }
// Check for buildDocs script // Check for buildDocs script
if (!packageJson.scripts.buildDocs) { if (!packageJson.scripts.buildDocs) {
logger.log('info', 'found no buildDocs script! Putting tsdoc script there now.'); logger.log(
'info',
'found no buildDocs script! Putting tsdoc script there now.',
);
packageJson.scripts.buildDocs = `tsdoc`; packageJson.scripts.buildDocs = `tsdoc`;
} }
@@ -134,9 +154,24 @@ export const run = async (projectArg: Project) => {
]; ];
// check for dependencies // check for dependencies
await ensureDependency(packageJson, 'devDep', 'latest', '@push.rocks/tapbundle'); await ensureDependency(
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tstest'); packageJson,
await ensureDependency(packageJson, 'devDep', 'latest', '@git.zone/tsbuild'); 'devDep',
'latest',
'@push.rocks/tapbundle',
);
await ensureDependency(
packageJson,
'devDep',
'latest',
'@git.zone/tstest',
);
await ensureDependency(
packageJson,
'devDep',
'latest',
'@git.zone/tsbuild',
);
// set overrides // set overrides
const overrides = plugins.smartfile.fs.toObjectSync( const overrides = plugins.smartfile.fs.toObjectSync(

View File

@@ -16,7 +16,12 @@ const prettierDefaultMarkdownConfig: prettier.Options = {
parser: 'markdown', parser: 'markdown',
}; };
const filesToFormat = [`ts/**/*.ts`, `test/**/*.ts`, `readme.md`, `docs/**/*.md`]; const filesToFormat = [
`ts/**/*.ts`,
`test/**/*.ts`,
`readme.md`,
`docs/**/*.md`,
];
const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => { const choosePrettierConfig = (fileArg: plugins.smartfile.SmartFile) => {
switch (fileArg.parsedPath.ext) { switch (fileArg.parsedPath.ext) {
@@ -39,7 +44,10 @@ const prettierTypeScriptPipestop = plugins.through2.obj(
cb(null); cb(null);
} else { } else {
logger.log('info', `${fileArg.path} is being reformated!`); logger.log('info', `${fileArg.path} is being reformated!`);
const formatedFileString = await prettier.format(fileString, chosenConfig); const formatedFileString = await prettier.format(
fileString,
chosenConfig,
);
fileArg.setContentsFromString(formatedFileString); fileArg.setContentsFromString(formatedFileString);
cb(null, fileArg); cb(null, fileArg);
} }

View File

@@ -18,7 +18,8 @@ export const run = async () => {
} }
// Check and initialize readme.hints.md if it doesn't exist // Check and initialize readme.hints.md if it doesn't exist
const readmeHintsExists = await plugins.smartfile.fs.fileExists(readmeHintsPath); const readmeHintsExists =
await plugins.smartfile.fs.fileExists(readmeHintsPath);
if (!readmeHintsExists) { if (!readmeHintsExists) {
await plugins.smartfile.fs.toFs( await plugins.smartfile.fs.toFs(
'# Project Readme Hints\n\nThis is the initial readme hints file.', '# Project Readme Hints\n\nThis is the initial readme hints file.',

View File

@@ -26,10 +26,12 @@ export const run = async (project: Project) => {
case 'npm': case 'npm':
case 'wcc': case 'wcc':
if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') { if (project.gitzoneConfig.data.npmciOptions.npmAccessLevel === 'public') {
const ciTemplateDefault = await templateModule.getTemplate('ci_default'); const ciTemplateDefault =
await templateModule.getTemplate('ci_default');
ciTemplateDefault.writeToDisk(paths.cwd); ciTemplateDefault.writeToDisk(paths.cwd);
} else { } else {
const ciTemplateDefault = await templateModule.getTemplate('ci_default_private'); const ciTemplateDefault =
await templateModule.getTemplate('ci_default_private');
ciTemplateDefault.writeToDisk(paths.cwd); ciTemplateDefault.writeToDisk(paths.cwd);
} }
logger.log('info', 'Updated .gitlabci.yml!'); logger.log('info', 'Updated .gitlabci.yml!');
@@ -41,7 +43,8 @@ export const run = async (project: Project) => {
logger.log('info', 'Updated CI/CD config files!'); logger.log('info', 'Updated CI/CD config files!');
// lets care about docker // lets care about docker
const dockerTemplate = await templateModule.getTemplate('dockerfile_service'); const dockerTemplate =
await templateModule.getTemplate('dockerfile_service');
dockerTemplate.writeToDisk(paths.cwd); dockerTemplate.writeToDisk(paths.cwd);
logger.log('info', 'Updated Dockerfile!'); logger.log('info', 'Updated Dockerfile!');
@@ -56,17 +59,22 @@ export const run = async (project: Project) => {
// update html // update html
if (project.gitzoneConfig.data.projectType === 'website') { if (project.gitzoneConfig.data.projectType === 'website') {
const websiteUpdateTemplate = await templateModule.getTemplate('website_update'); const websiteUpdateTemplate =
const variables ={ await templateModule.getTemplate('website_update');
const variables = {
assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl, assetbrokerUrl: project.gitzoneConfig.data.module.assetbrokerUrl,
legalUrl: project.gitzoneConfig.data.module.legalUrl, legalUrl: project.gitzoneConfig.data.module.legalUrl,
}; };
console.log('updating website template with variables\n', JSON.stringify(variables, null, 2)); console.log(
'updating website template with variables\n',
JSON.stringify(variables, null, 2),
);
websiteUpdateTemplate.supplyVariables(variables); websiteUpdateTemplate.supplyVariables(variables);
await websiteUpdateTemplate.writeToDisk(paths.cwd); await websiteUpdateTemplate.writeToDisk(paths.cwd);
logger.log('info', `Updated html for website!`); logger.log('info', `Updated html for website!`);
} else if (project.gitzoneConfig.data.projectType === 'service') { } else if (project.gitzoneConfig.data.projectType === 'service') {
const websiteUpdateTemplate = await templateModule.getTemplate('service_update'); const websiteUpdateTemplate =
await templateModule.getTemplate('service_update');
await websiteUpdateTemplate.writeToDisk(paths.cwd); await websiteUpdateTemplate.writeToDisk(paths.cwd);
logger.log('info', `Updated html for element template!`); logger.log('info', `Updated html for element template!`);
} else if (project.gitzoneConfig.data.projectType === 'wcc') { } else if (project.gitzoneConfig.data.projectType === 'wcc') {

View File

@@ -19,8 +19,12 @@ export const run = async (projectArg: Project) => {
const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd); const publishModules = await tsPublishInstance.getModuleSubDirs(paths.cwd);
for (const publishModule of Object.keys(publishModules)) { for (const publishModule of Object.keys(publishModules)) {
const publishConfig = publishModules[publishModule]; const publishConfig = publishModules[publishModule];
tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [`./${publishModule}/index.js`]; tsconfigObject.compilerOptions.paths[`${publishConfig.name}`] = [
`./${publishModule}/index.js`,
];
} }
tsconfigSmartfile.setContentsFromString(JSON.stringify(tsconfigObject, null, 2)); tsconfigSmartfile.setContentsFromString(
JSON.stringify(tsconfigObject, null, 2),
);
await tsconfigSmartfile.write(); await tsconfigSmartfile.write();
}; };

View File

@@ -12,7 +12,12 @@ export class CleanupFormatter extends BaseFormatter {
const changes: IPlannedChange[] = []; const changes: IPlannedChange[] = [];
// List of files to remove // 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) { for (const file of filesToRemove) {
const exists = await plugins.smartfile.fs.fileExists(file); const exists = await plugins.smartfile.fs.fileExists(file);
@@ -21,7 +26,7 @@ export class CleanupFormatter extends BaseFormatter {
type: 'delete', type: 'delete',
path: file, path: file,
module: this.name, module: this.name,
description: `Remove obsolete file` description: `Remove obsolete file`,
}); });
} }
} }

View File

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

View File

@@ -12,12 +12,7 @@ export class PrettierFormatter extends BaseFormatter {
const changes: IPlannedChange[] = []; const changes: IPlannedChange[] = [];
// Define directories to format (TypeScript directories by default) // Define directories to format (TypeScript directories by default)
const includeDirs = [ const includeDirs = ['ts', 'ts_*', 'test', 'tests'];
'ts',
'ts_*',
'test',
'tests'
];
// File extensions to format // File extensions to format
const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}'; const extensions = '{ts,tsx,js,jsx,json,md,css,scss,html,xml,yaml,yml}';
@@ -36,7 +31,7 @@ export class PrettierFormatter extends BaseFormatter {
'CHANGELOG.md', 'CHANGELOG.md',
'license', 'license',
'LICENSE', 'LICENSE',
'*.md' '*.md',
]; ];
// Collect all files to format // Collect all files to format
@@ -45,7 +40,10 @@ export class PrettierFormatter extends BaseFormatter {
// Add files from TypeScript directories // Add files from TypeScript directories
for (const dir of includeDirs) { for (const dir of includeDirs) {
const globPattern = `${dir}/**/*.${extensions}`; const globPattern = `${dir}/**/*.${extensions}`;
const dirFiles = await plugins.smartfile.fs.listFileTree('.', globPattern); const dirFiles = await plugins.smartfile.fs.listFileTree(
'.',
globPattern,
);
allFiles.push(...dirFiles); allFiles.push(...dirFiles);
} }
@@ -53,7 +51,7 @@ export class PrettierFormatter extends BaseFormatter {
for (const pattern of rootConfigFiles) { for (const pattern of rootConfigFiles) {
const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern); const rootFiles = await plugins.smartfile.fs.listFileTree('.', pattern);
// Only include files at root level (no slashes in path) // 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); allFiles.push(...rootLevelFiles);
} }
@@ -80,7 +78,7 @@ export class PrettierFormatter extends BaseFormatter {
// Check which files need formatting // Check which files need formatting
for (const file of validFiles) { for (const file of validFiles) {
// Skip files that haven't changed // Skip files that haven't changed
if (!await this.shouldProcessFile(file)) { if (!(await this.shouldProcessFile(file))) {
logVerbose(`Skipping ${file} - no changes detected`); logVerbose(`Skipping ${file} - no changes detected`);
continue; continue;
} }
@@ -89,7 +87,7 @@ export class PrettierFormatter extends BaseFormatter {
type: 'modify', type: 'modify',
path: file, path: file,
module: this.name, module: this.name,
description: 'Format with Prettier' description: 'Format with Prettier',
}); });
} }
@@ -112,11 +110,15 @@ export class PrettierFormatter extends BaseFormatter {
batches.push(changes.slice(i, 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++) { for (let i = 0; i < batches.length; i++) {
const batch = batches[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 // Process batch in parallel
const promises = batch.map(async (change) => { const promises = batch.map(async (change) => {
@@ -125,7 +127,10 @@ export class PrettierFormatter extends BaseFormatter {
this.stats.recordFileOperation(this.name, change.type, true); this.stats.recordFileOperation(this.name, change.type, true);
} catch (error) { } catch (error) {
this.stats.recordFileOperation(this.name, change.type, false); 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 // Don't throw - continue with other files
} }
}); });
@@ -135,7 +140,7 @@ export class PrettierFormatter extends BaseFormatter {
await this.postExecute(); await this.postExecute();
} catch (error) { } catch (error) {
await this.context.rollbackOperation(); // Rollback removed - no longer tracking operations
throw error; throw error;
} finally { } finally {
this.stats.endModule(this.name, startTime); this.stats.endModule(this.name, startTime);
@@ -153,7 +158,7 @@ export class PrettierFormatter extends BaseFormatter {
const prettier = await import('prettier'); const prettier = await import('prettier');
const formatted = await prettier.format(content, { const formatted = await prettier.format(content, {
filepath: change.path, filepath: change.path,
...(await this.getPrettierConfig()) ...(await this.getPrettierConfig()),
}); });
// Only write if content actually changed // Only write if content actually changed
@@ -161,8 +166,6 @@ export class PrettierFormatter extends BaseFormatter {
await this.modifyFile(change.path, formatted); await this.modifyFile(change.path, formatted);
logVerbose(`Formatted ${change.path}`); logVerbose(`Formatted ${change.path}`);
} else { } else {
// Still update cache even if content didn't change
await this.cache.updateFileCache(change.path);
logVerbose(`No formatting changes for ${change.path}`); logVerbose(`No formatting changes for ${change.path}`);
} }
} catch (error) { } catch (error) {
@@ -181,7 +184,7 @@ export class PrettierFormatter extends BaseFormatter {
printWidth: 80, printWidth: 80,
tabWidth: 2, tabWidth: 2,
semi: true, semi: true,
arrowParens: 'always' arrowParens: 'always',
}); });
} }
} }

View File

@@ -8,12 +8,14 @@ export class ReadmeFormatter extends BaseFormatter {
} }
async analyze(): Promise<IPlannedChange[]> { async analyze(): Promise<IPlannedChange[]> {
return [{ return [
{
type: 'modify', type: 'modify',
path: 'readme.md', path: 'readme.md',
module: this.name, module: this.name,
description: 'Ensure readme files exist' description: 'Ensure readme files exist',
}]; },
];
} }
async applyChange(change: IPlannedChange): Promise<void> { async applyChange(change: IPlannedChange): Promise<void> {

View File

@@ -16,7 +16,8 @@ import { PrettierFormatter } from './formatters/prettier.formatter.js';
import { ReadmeFormatter } from './formatters/readme.formatter.js'; import { ReadmeFormatter } from './formatters/readme.formatter.js';
import { CopyFormatter } from './formatters/copy.formatter.js'; import { CopyFormatter } from './formatters/copy.formatter.js';
export let run = async (options: { export let run = async (
options: {
dryRun?: boolean; dryRun?: boolean;
yes?: boolean; yes?: boolean;
planOnly?: boolean; planOnly?: boolean;
@@ -26,7 +27,8 @@ export let run = async (options: {
interactive?: boolean; interactive?: boolean;
parallel?: boolean; parallel?: boolean;
verbose?: boolean; verbose?: boolean;
} = {}): Promise<any> => { } = {},
): Promise<any> => {
// Set verbose mode if requested // Set verbose mode if requested
if (options.verbose) { if (options.verbose) {
setVerboseMode(true); setVerboseMode(true);
@@ -34,7 +36,7 @@ export let run = async (options: {
const project = await Project.fromCwd(); const project = await Project.fromCwd();
const context = new FormatContext(); const context = new FormatContext();
await context.initializeCache(); // Initialize the cache system // Cache system removed - no longer needed
const planner = new FormatPlanner(); const planner = new FormatPlanner();
// Get configuration from npmextra // Get configuration from npmextra
@@ -49,24 +51,21 @@ export let run = async (options: {
autoRollbackOnError: true, autoRollbackOnError: true,
backupRetentionDays: 7, backupRetentionDays: 7,
maxBackupSize: '100MB', maxBackupSize: '100MB',
excludePatterns: ['node_modules/**', '.git/**'] excludePatterns: ['node_modules/**', '.git/**'],
}, },
modules: { modules: {
skip: [], skip: [],
only: [], only: [],
order: [] order: [],
}, },
parallel: true, parallel: true,
cache: { cache: {
enabled: true, enabled: true,
clean: true // Clean invalid entries from cache clean: true, // Clean invalid entries from cache
} },
}); });
// Clean cache if configured // Cache cleaning removed - no longer using cache system
if (formatConfig.cache.clean) {
await context.getChangeCache().clean();
}
// Override config with command options // Override config with command options
const interactive = options.interactive ?? formatConfig.interactive; const interactive = options.interactive ?? formatConfig.interactive;
@@ -89,7 +88,7 @@ export let run = async (options: {
]; ];
// Filter formatters based on configuration // Filter formatters based on configuration
const activeFormatters = formatters.filter(formatter => { const activeFormatters = formatters.filter((formatter) => {
if (formatConfig.modules.only.length > 0) { if (formatConfig.modules.only.length > 0) {
return formatConfig.modules.only.includes(formatter.name); return formatConfig.modules.only.includes(formatter.name);
} }
@@ -110,7 +109,10 @@ export let run = async (options: {
// Save plan if requested // Save plan if requested
if (options.savePlan) { if (options.savePlan) {
await plugins.smartfile.memory.toFs(JSON.stringify(plan, null, 2), options.savePlan); await plugins.smartfile.memory.toFs(
JSON.stringify(plan, null, 2),
options.savePlan,
);
logger.log('info', `Plan saved to ${options.savePlan}`); logger.log('info', `Plan saved to ${options.savePlan}`);
} }
@@ -132,7 +134,7 @@ export let run = async (options: {
type: 'confirm', type: 'confirm',
name: 'proceed', name: 'proceed',
message: 'Proceed with formatting?', message: 'Proceed with formatting?',
default: true default: true,
}); });
if (!(response as any).value) { if (!(response as any).value) {
@@ -142,7 +144,10 @@ export let run = async (options: {
} }
// Execute phase // Execute phase
logger.log('info', `Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`); logger.log(
'info',
`Executing format operations${parallel ? ' in parallel' : ' sequentially'}...`,
);
await planner.executePlan(plan, activeFormatters, context, parallel); await planner.executePlan(plan, activeFormatters, context, parallel);
// Finish statistics tracking // Finish statistics tracking
@@ -161,20 +166,10 @@ export let run = async (options: {
} }
logger.log('success', 'Format operations completed successfully!'); logger.log('success', 'Format operations completed successfully!');
} catch (error) { } catch (error) {
logger.log('error', `Format operation failed: ${error.message}`); logger.log('error', `Format operation failed: ${error.message}`);
// Automatic rollback if enabled // Rollback system has been removed for stability
if (formatConfig.rollback.enabled && formatConfig.rollback.autoRollbackOnError) {
logger.log('info', 'Attempting automatic rollback...');
try {
await context.rollbackOperation();
logger.log('success', 'Rollback completed successfully');
} catch (rollbackError) {
logger.log('error', `Rollback failed: ${rollbackError.message}`);
}
}
throw error; throw error;
} }
@@ -182,67 +177,13 @@ export let run = async (options: {
// Export CLI command handlers // Export CLI command handlers
export const handleRollback = async (operationId?: string): Promise<void> => { export const handleRollback = async (operationId?: string): Promise<void> => {
const context = new FormatContext(); logger.log('info', 'Rollback system has been disabled for stability');
const rollbackManager = context.getRollbackManager();
if (!operationId) {
// Rollback to last operation
const backups = await rollbackManager.listBackups();
const lastOperation = backups
.filter(op => op.status !== 'rolled-back')
.sort((a, b) => b.timestamp - a.timestamp)[0];
if (!lastOperation) {
logger.log('warn', 'No operations available for rollback');
return;
}
operationId = lastOperation.id;
}
try {
await rollbackManager.rollback(operationId);
logger.log('success', `Successfully rolled back operation ${operationId}`);
} catch (error) {
logger.log('error', `Rollback failed: ${error.message}`);
throw error;
}
}; };
export const handleListBackups = async (): Promise<void> => { export const handleListBackups = async (): Promise<void> => {
const context = new FormatContext(); logger.log('info', 'Backup system has been disabled for stability');
const rollbackManager = context.getRollbackManager();
const backups = await rollbackManager.listBackups();
if (backups.length === 0) {
logger.log('info', 'No backup operations found');
return;
}
console.log('\nAvailable backups:');
console.log('━'.repeat(50));
for (const backup of backups) {
const date = new Date(backup.timestamp).toLocaleString();
const status = backup.status;
const filesCount = backup.files.length;
console.log(`ID: ${backup.id}`);
console.log(`Date: ${date}`);
console.log(`Status: ${status}`);
console.log(`Files: ${filesCount}`);
console.log('─'.repeat(50));
}
}; };
export const handleCleanBackups = async (): Promise<void> => { export const handleCleanBackups = async (): Promise<void> => {
const context = new FormatContext(); logger.log('info', 'Backup cleaning has been disabled - backup system removed');
const rollbackManager = context.getRollbackManager();
// Get retention days from config
const npmextraConfig = new plugins.npmextra.Npmextra();
const retentionDays = npmextraConfig.dataFor<any>('gitzone.format.rollback.backupRetentionDays', 7);
await rollbackManager.cleanOldBackups(retentionDays);
logger.log('success', `Cleaned backups older than ${retentionDays} days`);
}; };

View File

@@ -9,7 +9,7 @@ export type IFormatOperation = {
}>; }>;
status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back'; status: 'pending' | 'in-progress' | 'completed' | 'failed' | 'rolled-back';
error?: Error; error?: Error;
} };
export type IFormatPlan = { export type IFormatPlan = {
summary: { summary: {
@@ -32,7 +32,7 @@ export type IFormatPlan = {
message: string; message: string;
module: string; module: string;
}>; }>;
} };
export type IPlannedChange = { export type IPlannedChange = {
type: 'create' | 'modify' | 'delete'; type: 'create' | 'modify' | 'delete';
@@ -42,4 +42,4 @@ export type IPlannedChange = {
content?: string; // For create/modify operations content?: string; // For create/modify operations
diff?: string; diff?: string;
size?: number; size?: number;
} };

View File

@@ -35,7 +35,10 @@ export class Meta {
* sorts the metaRepoData * sorts the metaRepoData
*/ */
public async sortMetaRepoData() { public async sortMetaRepoData() {
const stringifiedMetadata = plugins.smartjson.stringify(this.metaRepoData, []); const stringifiedMetadata = plugins.smartjson.stringify(
this.metaRepoData,
[],
);
this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata); this.metaRepoData = plugins.smartjson.parse(stringifiedMetadata);
} }
@@ -45,11 +48,15 @@ export class Meta {
public async readDirectory() { public async readDirectory() {
await this.syncToRemote(true); await this.syncToRemote(true);
logger.log('info', `reading directory`); logger.log('info', `reading directory`);
const metaFileExists = plugins.smartfile.fs.fileExistsSync(this.filePaths.metaJson); const metaFileExists = plugins.smartfile.fs.fileExistsSync(
this.filePaths.metaJson,
);
if (!metaFileExists) { if (!metaFileExists) {
throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`); throw new Error(`meta file does not exist at ${this.filePaths.metaJson}`);
} }
this.metaRepoData = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson); this.metaRepoData = plugins.smartfile.fs.toObjectSync(
this.filePaths.metaJson,
);
} }
/** /**
@@ -76,7 +83,10 @@ export class Meta {
this.filePaths.metaJson, this.filePaths.metaJson,
); );
// write .gitignore to disk // write .gitignore to disk
plugins.smartfile.memory.toFsSync(await this.generateGitignore(), this.filePaths.gitIgnore); plugins.smartfile.memory.toFsSync(
await this.generateGitignore(),
this.filePaths.gitIgnore,
);
} }
/** /**
@@ -84,13 +94,17 @@ export class Meta {
*/ */
public async syncToRemote(gitCleanArg = false) { public async syncToRemote(gitCleanArg = false) {
logger.log('info', `syncing from origin master`); logger.log('info', `syncing from origin master`);
await this.smartshellInstance.exec(`cd ${this.cwd} && git pull origin master`); await this.smartshellInstance.exec(
`cd ${this.cwd} && git pull origin master`,
);
if (gitCleanArg) { if (gitCleanArg) {
logger.log('info', `cleaning the repository from old directories`); logger.log('info', `cleaning the repository from old directories`);
await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`); await this.smartshellInstance.exec(`cd ${this.cwd} && git clean -fd`);
} }
logger.log('info', `syncing to remote origin master`); logger.log('info', `syncing to remote origin master`);
await this.smartshellInstance.exec(`cd ${this.cwd} && git push origin master`); await this.smartshellInstance.exec(
`cd ${this.cwd} && git push origin master`,
);
} }
/** /**
@@ -98,7 +112,9 @@ export class Meta {
*/ */
public async updateLocalRepos() { public async updateLocalRepos() {
await this.syncToRemote(); await this.syncToRemote();
const projects = plugins.smartfile.fs.toObjectSync(this.filePaths.metaJson).projects; const projects = plugins.smartfile.fs.toObjectSync(
this.filePaths.metaJson,
).projects;
const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd); const preExistingFolders = plugins.smartfile.fs.listFoldersSync(this.cwd);
for (const preExistingFolderArg of preExistingFolders) { for (const preExistingFolderArg of preExistingFolders) {
if ( if (
@@ -107,14 +123,18 @@ export class Meta {
projectFolder.startsWith(preExistingFolderArg), projectFolder.startsWith(preExistingFolderArg),
) )
) { ) {
const response = await plugins.smartinteraction.SmartInteract.getCliConfirmation( const response =
await plugins.smartinteraction.SmartInteract.getCliConfirmation(
`Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`, `Do you want to delete superfluous directory >>${preExistingFolderArg}<< ?`,
true, true,
); );
if (response) { if (response) {
logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`); logger.log('warn', `Deleting >>${preExistingFolderArg}<<!`);
} else { } else {
logger.log('warn', `Not deleting ${preExistingFolderArg} by request!`); logger.log(
'warn',
`Not deleting ${preExistingFolderArg} by request!`,
);
} }
} }
} }
@@ -160,7 +180,9 @@ export class Meta {
*/ */
public async initProject() { public async initProject() {
await this.syncToRemote(true); await this.syncToRemote(true);
const fileExists = await plugins.smartfile.fs.fileExists(this.filePaths.metaJson); const fileExists = await plugins.smartfile.fs.fileExists(
this.filePaths.metaJson,
);
if (!fileExists) { if (!fileExists) {
await plugins.smartfile.memory.toFs( await plugins.smartfile.memory.toFs(
JSON.stringify({ JSON.stringify({
@@ -168,7 +190,10 @@ export class Meta {
}), }),
this.filePaths.metaJson, this.filePaths.metaJson,
); );
logger.log(`success`, `created a new .meta.json in directory ${this.cwd}`); logger.log(
`success`,
`created a new .meta.json in directory ${this.cwd}`,
);
await plugins.smartfile.memory.toFs( await plugins.smartfile.memory.toFs(
JSON.stringify({ JSON.stringify({
name: this.dirName, name: this.dirName,
@@ -176,9 +201,15 @@ export class Meta {
}), }),
this.filePaths.packageJson, this.filePaths.packageJson,
); );
logger.log(`success`, `created a new package.json in directory ${this.cwd}`); logger.log(
`success`,
`created a new package.json in directory ${this.cwd}`,
);
} else { } else {
logger.log(`error`, `directory ${this.cwd} already has a .metaJson file. Doing nothing.`); logger.log(
`error`,
`directory ${this.cwd} already has a .metaJson file. Doing nothing.`,
);
} }
await this.smartshellInstance.exec( await this.smartshellInstance.exec(
`cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`, `cd ${this.cwd} && git add -A && git commit -m "feat(project): init meta project for ${this.dirName}"`,
@@ -195,7 +226,9 @@ export class Meta {
const existingProject = this.metaRepoData.projects[projectNameArg]; const existingProject = this.metaRepoData.projects[projectNameArg];
if (existingProject) { if (existingProject) {
throw new Error('Project already exists! Please remove it first before adding it again.'); throw new Error(
'Project already exists! Please remove it first before adding it again.',
);
} }
this.metaRepoData.projects[projectNameArg] = gitUrlArg; this.metaRepoData.projects[projectNameArg] = gitUrlArg;
@@ -217,7 +250,10 @@ export class Meta {
const existingProject = this.metaRepoData.projects[projectNameArg]; const existingProject = this.metaRepoData.projects[projectNameArg];
if (!existingProject) { if (!existingProject) {
logger.log('error', `Project ${projectNameArg} does not exist! So it cannot be removed`); logger.log(
'error',
`Project ${projectNameArg} does not exist! So it cannot be removed`,
);
return; return;
} }
@@ -228,7 +264,9 @@ export class Meta {
await this.writeToDisk(); await this.writeToDisk();
logger.log('info', 'removing directory from cwd'); logger.log('info', 'removing directory from cwd');
await plugins.smartfile.fs.remove(plugins.path.join(paths.cwd, projectNameArg)); await plugins.smartfile.fs.remove(
plugins.path.join(paths.cwd, projectNameArg),
);
await this.updateLocalRepos(); await this.updateLocalRepos();
} }
} }

View File

@@ -16,7 +16,9 @@ export let run = () => {
* create a new project with 'gitzone template [template]' * create a new project with 'gitzone template [template]'
the following templates exist: ${(() => { the following templates exist: ${(() => {
let projects = `\n`; let projects = `\n`;
for (const template of plugins.smartfile.fs.listFoldersSync(paths.templatesDir)) { for (const template of plugins.smartfile.fs.listFoldersSync(
paths.templatesDir,
)) {
projects += ` - ${template}\n`; projects += ` - ${template}\n`;
} }
return projects; return projects;

View File

@@ -15,7 +15,9 @@ export const run = async (argvArg: any) => {
}); });
await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`); await smartshellInstance.execStrict(`cd ${paths.cwd} && git checkout master`);
await smartshellInstance.execStrict(`cd ${paths.cwd} && git pull origin master`); await smartshellInstance.execStrict(
`cd ${paths.cwd} && git pull origin master`,
);
await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`); await smartshellInstance.execStrict(`cd ${paths.cwd} && npm ci`);
await provideNoGitFiles(); await provideNoGitFiles();

View File

@@ -16,7 +16,9 @@ export const isTemplate = async (templateNameArg: string) => {
export const getTemplate = async (templateNameArg: string) => { export const getTemplate = async (templateNameArg: string) => {
if (isTemplate(templateNameArg)) { if (isTemplate(templateNameArg)) {
const localScafTemplate = new plugins.smartscaf.ScafTemplate(getTemplatePath(templateNameArg)); const localScafTemplate = new plugins.smartscaf.ScafTemplate(
getTemplatePath(templateNameArg),
);
await localScafTemplate.readTemplateFromDir(); await localScafTemplate.readTemplateFromDir();
return localScafTemplate; return localScafTemplate;
} else { } else {
@@ -32,7 +34,8 @@ export const run = async (argvArg: any) => {
const answerBucket = await smartinteract.askQuestion({ const answerBucket = await smartinteract.askQuestion({
type: 'list', type: 'list',
default: 'npm', default: 'npm',
message: 'What template do you want to scaffold? (Only showing mpost common options)', message:
'What template do you want to scaffold? (Only showing mpost common options)',
name: 'templateName', name: 'templateName',
choices: ['npm', 'service', 'wcc', 'website'], choices: ['npm', 'service', 'wcc', 'website'],
}); });