monorepo-cache-manager
Version:
Lightweight cache manager for monorepos with cross-workspace cache sharing and optimization
219 lines • 8.81 kB
JavaScript
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.MonorepoCacheManager = void 0;
const fs = __importStar(require("fs-extra"));
const path = __importStar(require("path"));
const utils_1 = require("./utils");
class MonorepoCacheManager {
constructor(config = {}) {
this.cacheIndex = new Map();
this.stats = {
hits: 0,
misses: 0,
totalRequests: 0
};
this.config = {
rootDir: process.cwd(),
cacheDir: '.cache',
maxSize: 100 * 1024 * 1024,
ttl: 24 * 60 * 60 * 1000,
compression: false,
workspacePatterns: ['packages/*', 'apps/*', 'libs/*'],
...config
};
}
async initialize() {
await utils_1.CacheUtils.ensureDir(this.config.cacheDir);
await this.loadCacheIndex();
await this.cleanup();
}
async get(workspace, options = {}) {
const cacheKey = utils_1.CacheUtils.createCacheKey(workspace, options.dependencies);
const entry = this.cacheIndex.get(cacheKey);
if (!entry) {
this.stats.misses++;
this.stats.totalRequests++;
return null;
}
if (Date.now() - entry.timestamp > this.config.ttl) {
await this.remove(cacheKey);
this.stats.misses++;
this.stats.totalRequests++;
return null;
}
const cachePath = path.join(this.config.cacheDir, cacheKey);
if (!(await fs.pathExists(cachePath))) {
await this.remove(cacheKey);
this.stats.misses++;
this.stats.totalRequests++;
return null;
}
this.stats.hits++;
this.stats.totalRequests++;
return entry;
}
async set(workspace, buildOutput, options = {}) {
const cacheKey = utils_1.CacheUtils.createCacheKey(workspace, options.dependencies);
const workspacePath = path.join(this.config.rootDir, workspace);
const outputHash = await utils_1.CacheUtils.generateHash(workspacePath);
const size = await utils_1.CacheUtils.getDirectorySize(workspacePath);
await this.enforceSizeLimit(size);
const entry = {
key: cacheKey,
workspace,
timestamp: Date.now(),
size,
hash: outputHash,
dependencies: options.dependencies || [],
metadata: options.metadata || {}
};
const cachePath = path.join(this.config.cacheDir, cacheKey);
await utils_1.CacheUtils.ensureDir(cachePath);
for (const output of buildOutput) {
const sourcePath = path.join(workspacePath, output);
const targetPath = path.join(cachePath, output);
if (await fs.pathExists(sourcePath)) {
await fs.copy(sourcePath, targetPath);
}
}
const metadataPath = path.join(cachePath, 'metadata.json');
await fs.writeJson(metadataPath, entry);
this.cacheIndex.set(cacheKey, entry);
await this.saveCacheIndex();
}
async remove(cacheKey) {
const cachePath = path.join(this.config.cacheDir, cacheKey);
await fs.remove(cachePath);
this.cacheIndex.delete(cacheKey);
await this.saveCacheIndex();
}
async clear() {
await fs.remove(this.config.cacheDir);
await utils_1.CacheUtils.ensureDir(this.config.cacheDir);
this.cacheIndex.clear();
await this.saveCacheIndex();
}
getStats() {
const totalEntries = this.cacheIndex.size;
const totalSize = Array.from(this.cacheIndex.values()).reduce((sum, entry) => sum + entry.size, 0);
const hitRate = this.stats.totalRequests > 0 ? this.stats.hits / this.stats.totalRequests : 0;
const missRate = 1 - hitRate;
const timestamps = Array.from(this.cacheIndex.values()).map(entry => entry.timestamp);
const oldestEntry = timestamps.length > 0 ? Math.min(...timestamps) : 0;
const newestEntry = timestamps.length > 0 ? Math.max(...timestamps) : 0;
return {
totalEntries,
totalSize,
hitRate,
missRate,
oldestEntry,
newestEntry
};
}
async findWorkspaces() {
const workspacePaths = await utils_1.CacheUtils.findWorkspaces(this.config.rootDir, this.config.workspacePatterns);
const workspaces = [];
for (const workspacePath of workspacePaths) {
const relativePath = path.relative(this.config.rootDir, workspacePath);
const packageJsonPath = path.join(workspacePath, 'package.json');
if (await fs.pathExists(packageJsonPath)) {
const packageJson = await fs.readJson(packageJsonPath);
const dependencies = [
...Object.keys(packageJson.dependencies || {}),
...Object.keys(packageJson.devDependencies || {})
];
workspaces.push({
name: packageJson.name || relativePath,
path: relativePath,
dependencies,
buildOutput: ['dist', 'build', 'lib', 'out'],
cacheKey: utils_1.CacheUtils.createCacheKey(relativePath, dependencies)
});
}
}
return workspaces;
}
async restore(workspace, options = {}) {
const entry = await this.get(workspace, options);
if (!entry) {
return false;
}
const cacheKey = utils_1.CacheUtils.createCacheKey(workspace, options.dependencies);
const cachePath = path.join(this.config.cacheDir, cacheKey);
const workspacePath = path.join(this.config.rootDir, workspace);
if (await fs.pathExists(cachePath)) {
await fs.copy(cachePath, workspacePath, { overwrite: true });
return true;
}
return false;
}
async loadCacheIndex() {
const indexPath = path.join(this.config.cacheDir, 'index.json');
if (await fs.pathExists(indexPath)) {
try {
const indexData = await fs.readJson(indexPath);
this.cacheIndex = new Map(Object.entries(indexData));
}
catch (error) {
this.cacheIndex = new Map();
}
}
}
async saveCacheIndex() {
const indexPath = path.join(this.config.cacheDir, 'index.json');
const indexData = Object.fromEntries(this.cacheIndex);
await fs.writeJson(indexPath, indexData, { spaces: 2 });
}
async enforceSizeLimit(newEntrySize) {
const stats = this.getStats();
if (stats.totalSize + newEntrySize > this.config.maxSize) {
const entries = Array.from(this.cacheIndex.entries())
.sort(([, a], [, b]) => a.timestamp - b.timestamp);
for (const [key, entry] of entries) {
await this.remove(key);
const newStats = this.getStats();
if (newStats.totalSize + newEntrySize <= this.config.maxSize) {
break;
}
}
}
}
async cleanup() {
await utils_1.CacheUtils.cleanOldFiles(this.config.cacheDir, this.config.ttl);
}
}
exports.MonorepoCacheManager = MonorepoCacheManager;
//# sourceMappingURL=cache-manager.js.map