vibe-janitor
Version:
A CLI tool that cleans AI-generated JavaScript/TypeScript projects efficiently and intelligently
168 lines (167 loc) • 6.64 kB
JavaScript
import fs from 'fs-extra';
/**
* Creates a mock function that can be called like a Jest mock
*/
function createMockFn(implementation) {
const mockFn = function (...args) {
return implementation(...args);
};
// Add mockImplementation method to simulate Jest's mock functionality
mockFn.mockImplementation = (impl) => {
return createMockFn(impl);
};
return mockFn;
}
/**
* Mock implementation for file system operations
* This allows tests to run without touching the actual file system
*/
export class FsMock {
mockFiles = new Map();
originalFsMethods = {};
constructor() {
// Store original fs methods before mocking
this.originalFsMethods = {
readFile: fs.readFile,
writeFile: fs.writeFile,
readdir: fs.readdir,
stat: fs.stat,
existsSync: fs.existsSync,
readFileSync: fs.readFileSync,
};
}
/**
* Set up mock file system with initial file structure
*/
setup(fileStructure) {
// Reset mock files
this.mockFiles.clear();
// Add initial files
for (const [path, content] of Object.entries(fileStructure)) {
this.mockFiles.set(path, content);
}
// Create mock implementations without relying on Jest
const mockReadFile = createMockFn((path, options, callback) => {
if (!callback && typeof options === 'function') {
callback = options;
options = 'utf8';
}
const normalizedPath = this.normalizePath(path);
if (this.mockFiles.has(normalizedPath)) {
const content = this.mockFiles.get(normalizedPath);
return callback ? callback(null, content) : Promise.resolve(content);
}
else {
const error = new Error(`ENOENT: no such file or directory, open '${normalizedPath}'`);
return callback ? callback(error) : Promise.reject(error);
}
});
const mockWriteFile = createMockFn((path, data, options, callback) => {
if (!callback && typeof options === 'function') {
callback = options;
options = 'utf8';
}
const normalizedPath = this.normalizePath(path);
const content = typeof data === 'string' ? data : data.toString();
this.mockFiles.set(normalizedPath, content);
return callback ? callback(null) : Promise.resolve();
});
const mockExistsSync = createMockFn((path) => {
const normalizedPath = this.normalizePath(path);
return this.mockFiles.has(normalizedPath);
});
const mockReadFileSync = createMockFn((path, _options) => {
// Using _options with underscore to indicate it's intentionally unused
const normalizedPath = this.normalizePath(path);
if (this.mockFiles.has(normalizedPath)) {
return this.mockFiles.get(normalizedPath);
}
else {
throw new Error(`ENOENT: no such file or directory, open '${normalizedPath}'`);
}
});
const mockReaddir = createMockFn((path, options, callback) => {
if (!callback && typeof options === 'function') {
callback = options;
options = {};
}
const normalizedPath = this.normalizePath(path);
const dirs = Array.from(this.mockFiles.keys())
.filter((filePath) => filePath.startsWith(`${normalizedPath}/`))
.map((filePath) => {
const relativePath = filePath.substring(normalizedPath.length + 1);
return relativePath.split('/')[0];
})
.filter((value, index, self) => self.indexOf(value) === index); // Unique values
return callback ? callback(null, dirs) : Promise.resolve(dirs);
});
const mockStat = createMockFn((path, callback) => {
const normalizedPath = this.normalizePath(path);
if (this.mockFiles.has(normalizedPath)) {
const stats = {
isFile: () => true,
isDirectory: () => false,
size: this.mockFiles.get(normalizedPath)?.length ?? 0,
};
return callback ? callback(null, stats) : Promise.resolve(stats);
}
else {
// Check if it's a directory (has files under this path)
const isDirectory = Array.from(this.mockFiles.keys()).some((filePath) => filePath.startsWith(`${normalizedPath}/`));
if (isDirectory) {
const stats = {
isFile: () => false,
isDirectory: () => true,
size: 0,
};
return callback ? callback(null, stats) : Promise.resolve(stats);
}
else {
const error = new Error(`ENOENT: no such file or directory, stat '${normalizedPath}'`);
return callback ? callback(error) : Promise.reject(error);
}
}
});
// Use Object.defineProperty to mock fs methods without TypeScript errors
Object.defineProperty(fs, 'readFile', { value: mockReadFile });
Object.defineProperty(fs, 'writeFile', { value: mockWriteFile });
Object.defineProperty(fs, 'existsSync', { value: mockExistsSync });
Object.defineProperty(fs, 'readFileSync', { value: mockReadFileSync });
Object.defineProperty(fs, 'readdir', { value: mockReaddir });
Object.defineProperty(fs, 'stat', { value: mockStat });
}
/**
* Normalize path for consistent handling
*/
normalizePath(path) {
return typeof path === 'string' ? path : path.toString();
}
/**
* Restore original fs methods
*/
restore() {
for (const [method, original] of Object.entries(this.originalFsMethods)) {
if (method in fs) {
Object.defineProperty(fs, method, { value: original });
}
}
}
/**
* Get content of a mock file
*/
getMockFileContent(path) {
return this.mockFiles.get(path);
}
/**
* Set content of a mock file
*/
setMockFileContent(path, content) {
this.mockFiles.set(path, content);
}
/**
* Get all mock files
*/
getAllMockFiles() {
return new Map(this.mockFiles);
}
}