Git MCP Server
by cyanheads
Verified
- scripts
#!/usr/bin/env node
/**
* Project Directory Cleaning Operation
* ====================================
*
* This utility performs automated cleanup of build artifacts and temporary directories
* in the project filesystem. It provides configurable directory targeting with special
* handling for certain directory types.
*
* Functionality:
* - Removes specified directories completely (default: 'dist')
* - For 'logs' directory: preserves structure but removes all contained files
* - Creates directories that don't exist when using content-only cleaning
* - Supports custom directory specification via command line arguments
*
* @module utilities/clean.project.directories.operation
*
* Usage examples:
* - Add to package.json: "clean": "ts-node scripts/clean.ts"
* - Direct execution: npm run clean
* - With build chain: "rebuild": "npm run clean && npm run build"
* - Custom directories: ts-node scripts/clean.ts temp coverage
*
* Platform compatibility:
* - Cross-platform support (Windows, macOS, Linux) via Node.js path normalization
*/
import { access, mkdir, readdir, rm } from 'fs/promises';
import { join } from 'path';
// -----------------------------------
// Type Definitions
// -----------------------------------
/**
* Standardized error category classification
*/
const ErrorCategoryType = {
CATEGORY_VALIDATION: 'VALIDATION',
CATEGORY_FILESYSTEM: 'FILESYSTEM',
CATEGORY_SYSTEM: 'SYSTEM',
CATEGORY_UNKNOWN: 'UNKNOWN'
} as const;
type ErrorCategoryType = typeof ErrorCategoryType[keyof typeof ErrorCategoryType];
/**
* Error severity classification
*/
const ErrorSeverityLevel = {
SEVERITY_DEBUG: 0,
SEVERITY_INFO: 1,
SEVERITY_WARN: 2,
SEVERITY_ERROR: 3,
SEVERITY_FATAL: 4
} as const;
type ErrorSeverityLevel = typeof ErrorSeverityLevel[keyof typeof ErrorSeverityLevel];
/**
* Standardized error structure for consistent error handling
*/
interface StandardizedApplicationErrorObject {
errorMessage: string; // Human-readable description
errorCode: string; // Machine-readable identifier
errorCategory: ErrorCategoryType; // System area affected
errorSeverity: ErrorSeverityLevel; // How critical the error is
errorTimestamp: string; // When the error occurred
errorContext: Record<string, unknown>; // Additional relevant data
errorStack?: string; // Stack trace if available
}
/**
* Successful result from a directory cleaning operation
*/
interface DirectoryCleanOperationSuccessResult {
resultSuccessful: true;
resultData: {
directoryPath: string;
directoryName: string;
cleaningMethod: 'removed' | 'contentsOnly';
};
}
/**
* Failed result from a directory cleaning operation
*/
interface DirectoryCleanOperationFailureResult {
resultSuccessful: false;
resultError: StandardizedApplicationErrorObject;
}
/**
* Combined result type for directory cleaning operations
*/
type DirectoryCleanOperationResult =
| DirectoryCleanOperationSuccessResult
| DirectoryCleanOperationFailureResult;
/**
* Configuration options for the cleaning operation
*/
interface DirectoryCleaningConfiguration {
targetDirectories: string[];
preserveStructureDirectories: string[];
}
// -----------------------------------
// Utility Functions
// -----------------------------------
/**
* Creates a standardized success result
*/
function createSuccessResult<DataType>(data: DataType): { resultSuccessful: true; resultData: DataType } {
return { resultSuccessful: true, resultData: data };
}
/**
* Creates a standardized failure result
*/
function createFailureResult<ErrorType>(error: ErrorType): { resultSuccessful: false; resultError: ErrorType } {
return { resultSuccessful: false, resultError: error };
}
/**
* Creates a standardized error object
*/
function createStandardizedError(
message: string,
code: string,
category: ErrorCategoryType,
severity: ErrorSeverityLevel,
context: Record<string, unknown> = {}
): StandardizedApplicationErrorObject {
return {
errorMessage: message,
errorCode: code,
errorCategory: category,
errorSeverity: severity,
errorTimestamp: new Date().toISOString(),
errorContext: context
};
}
/**
* Converts an exception to a standardized error object
*/
function wrapExceptionAsStandardizedError(
exception: unknown,
defaultMessage: string
): StandardizedApplicationErrorObject {
const errorMessage = exception instanceof Error ? exception.message : defaultMessage;
const errorStack = exception instanceof Error ? exception.stack : undefined;
return {
errorMessage,
errorCode: 'UNEXPECTED_ERROR',
errorCategory: ErrorCategoryType.CATEGORY_UNKNOWN,
errorSeverity: ErrorSeverityLevel.SEVERITY_ERROR,
errorTimestamp: new Date().toISOString(),
errorContext: { originalException: exception },
errorStack
};
}
// -----------------------------------
// Implementation Functions
// -----------------------------------
/**
* Checks if a directory exists at the specified path
*
* @param directoryPath - Full path to check for existence
* @returns Promise resolving to boolean indicating existence
*/
async function checkDirectoryExists(directoryPath: string): Promise<boolean> {
try {
await access(directoryPath);
return true;
} catch {
return false;
}
}
/**
* Cleans the contents of a directory while preserving the directory structure
*
* This operation will:
* 1. Create the directory if it doesn't exist
* 2. Remove all files within the directory
* 3. Recursively clean the contents of subdirectories
*
* @param directoryPath - Full path to the directory to clean
* @returns Promise resolving when the operation completes
*/
async function cleanDirectoryContentsOnly(directoryPath: string): Promise<DirectoryCleanOperationResult> {
try {
// Check if directory exists
const directoryExistsFlag = await checkDirectoryExists(directoryPath);
if (!directoryExistsFlag) {
// Create the directory if it doesn't exist
await mkdir(directoryPath, { recursive: true });
return createSuccessResult({
directoryPath,
directoryName: directoryPath.split('/').pop() || directoryPath,
cleaningMethod: 'contentsOnly'
});
}
// Read directory contents
const directoryEntries = await readdir(directoryPath, { withFileTypes: true });
// Process each entry
for (const entryItem of directoryEntries) {
const entryItemPath = join(directoryPath, entryItem.name);
if (entryItem.isDirectory()) {
// For subdirectories, recursively clean their contents
const subDirectoryResult = await cleanDirectoryContentsOnly(entryItemPath);
if (!subDirectoryResult.resultSuccessful) {
return subDirectoryResult; // Propagate error
}
} else {
// For files, remove them
await rm(entryItemPath, { force: true });
}
}
return createSuccessResult({
directoryPath,
directoryName: directoryPath.split('/').pop() || directoryPath,
cleaningMethod: 'contentsOnly'
});
} catch (exceptionObject) {
return createFailureResult(
wrapExceptionAsStandardizedError(
exceptionObject,
`Failed to clean contents of directory: ${directoryPath}`
)
);
}
}
/**
* Removes a directory completely
*
* @param directoryPath - Full path to the directory to remove
* @returns Promise resolving to the operation result
*/
async function removeDirectoryCompletely(directoryPath: string): Promise<DirectoryCleanOperationResult> {
try {
// Check if directory exists before attempting to remove it
const directoryExistsFlag = await checkDirectoryExists(directoryPath);
if (!directoryExistsFlag) {
return createSuccessResult({
directoryPath,
directoryName: directoryPath.split('/').pop() || directoryPath,
cleaningMethod: 'removed'
});
}
// Remove the directory and all its contents
await rm(directoryPath, { recursive: true, force: true });
return createSuccessResult({
directoryPath,
directoryName: directoryPath.split('/').pop() || directoryPath,
cleaningMethod: 'removed'
});
} catch (exceptionObject) {
return createFailureResult(
wrapExceptionAsStandardizedError(
exceptionObject,
`Failed to remove directory: ${directoryPath}`
)
);
}
}
/**
* Processes a single directory based on configuration
*
* @param directoryName - Name of the directory to process
* @param configurationSettings - Configuration for the cleaning operation
* @returns Promise resolving to the operation result
*/
async function processDirectoryCleaning(
directoryName: string,
configurationSettings: DirectoryCleaningConfiguration
): Promise<DirectoryCleanOperationResult> {
try {
const directoryPath = join(process.cwd(), directoryName);
// Determine handling method based on configuration
const preserveStructure = configurationSettings.preserveStructureDirectories.includes(directoryName);
if (preserveStructure) {
return await cleanDirectoryContentsOnly(directoryPath);
} else {
return await removeDirectoryCompletely(directoryPath);
}
} catch (exceptionObject) {
return createFailureResult(
wrapExceptionAsStandardizedError(
exceptionObject,
`Unexpected error processing directory: ${directoryName}`
)
);
}
}
/**
* Main operation function that processes all directories
*
* @returns Promise that resolves when all directories have been processed
*/
async function cleanProjectDirectories(): Promise<void> {
try {
// Default configuration
const cleaningConfiguration: DirectoryCleaningConfiguration = {
targetDirectories: ['dist', 'logs'],
preserveStructureDirectories: ['logs']
};
// Override target directories if specified in command line arguments
const commandLineArguments = process.argv.slice(2);
if (commandLineArguments.length > 0) {
cleaningConfiguration.targetDirectories = commandLineArguments;
}
console.log(`Cleaning directories: ${cleaningConfiguration.targetDirectories.join(', ')}`);
// Process each directory and collect results
const operationResults = await Promise.allSettled(
cleaningConfiguration.targetDirectories.map(async (directoryName) => {
return await processDirectoryCleaning(directoryName, cleaningConfiguration);
})
);
// Report results
for (const operationResult of operationResults) {
if (operationResult.status === 'fulfilled') {
const result = operationResult.value;
if (result.resultSuccessful) {
const { directoryName, cleaningMethod } = result.resultData;
if (cleaningMethod === 'contentsOnly') {
console.log(`✓ Successfully cleaned contents of ${directoryName} directory while preserving structure`);
} else {
console.log(`✓ Successfully cleaned ${directoryName} directory`);
}
} else {
console.error(`× Error: ${result.resultError.errorMessage}`);
}
} else {
console.error(`× Unhandled error: ${operationResult.reason}`);
}
}
} catch (exceptionObject) {
const standardizedError = wrapExceptionAsStandardizedError(
exceptionObject,
'Unhandled error during directory cleaning operation'
);
console.error(`× Fatal error during cleanup: ${standardizedError.errorMessage}`);
process.exit(1);
}
}
// -----------------------------------
// Script Execution
// -----------------------------------
// Execute the main operation function
cleanProjectDirectories();