epd
Version:
Enhanced peer dependency resolution for npm, yarn, and pnpm
127 lines • 4.83 kB
JavaScript
/**
* Enhanced Peer Dependencies (epd)
* Copyright (c) 2024 Enhanced Peer Dependencies (epd)
* Licensed under the MIT License
*/
import fs from 'fs/promises';
import { existsSync } from 'fs';
import path from 'path';
export class DependencyTimeline {
timelinePath;
backupDir;
constructor(projectRoot = process.cwd()) {
this.timelinePath = path.join(projectRoot, '.epd', 'timeline.json');
this.backupDir = path.join(projectRoot, '.epd', 'backups');
}
async init() {
const epdDir = path.dirname(this.timelinePath);
if (!existsSync(epdDir)) {
await fs.mkdir(epdDir, { recursive: true });
}
if (!existsSync(this.backupDir)) {
await fs.mkdir(this.backupDir, { recursive: true });
}
}
async snapshot(action, packages) {
await this.init();
const packageJson = JSON.parse(await fs.readFile('package.json', 'utf-8'));
const hash = this.generateHash(packageJson);
const entry = {
timestamp: new Date().toISOString(),
action: action,
packages,
packageJson,
hash
};
// Save backup
await fs.writeFile(path.join(this.backupDir, `${hash}.json`), JSON.stringify(packageJson, null, 2));
// Update timeline
const timeline = await this.loadTimeline();
timeline.entries.push(entry);
timeline.current = timeline.entries.length - 1;
await fs.writeFile(this.timelinePath, JSON.stringify(timeline, null, 2));
}
async getTimeline() {
return this.loadTimeline();
}
async rollback(target) {
const timeline = await this.loadTimeline();
let targetIndex;
if (typeof target === 'string') {
// Date-based rollback
const targetDate = new Date(target);
targetIndex = timeline.entries.findIndex(entry => new Date(entry.timestamp) <= targetDate);
if (targetIndex === -1) {
targetIndex = timeline.entries.findIndex(entry => new Date(entry.timestamp) >= targetDate) - 1;
}
}
else {
targetIndex = target;
}
if (targetIndex < 0 || targetIndex >= timeline.entries.length) {
throw new Error('Invalid rollback target');
}
const targetEntry = timeline.entries[targetIndex];
// Restore package.json
await fs.writeFile('package.json', JSON.stringify(targetEntry.packageJson, null, 2));
// Update timeline current pointer
timeline.current = targetIndex;
await fs.writeFile(this.timelinePath, JSON.stringify(timeline, null, 2));
return true;
}
async showHistory(limit = 10) {
const timeline = await this.loadTimeline();
const entries = timeline.entries.slice(-limit).reverse();
console.log('📅 Dependency Timeline:\n');
entries.forEach((entry, index) => {
const isCurrent = timeline.current === timeline.entries.length - 1 - index;
const marker = isCurrent ? '→' : ' ';
const date = new Date(entry.timestamp).toLocaleString();
console.log(`${marker} ${date} - ${entry.action}`);
Object.entries(entry.packages).forEach(([pkg, change]) => {
if (change.from) {
console.log(` ${pkg}: ${change.from} → ${change.to}`);
}
else {
console.log(` ${pkg}: ${change.to} (new)`);
}
});
console.log();
});
}
async loadTimeline() {
if (!existsSync(this.timelinePath)) {
return { entries: [], current: -1 };
}
try {
const content = await fs.readFile(this.timelinePath, 'utf-8');
return JSON.parse(content);
}
catch {
return { entries: [], current: -1 };
}
}
generateHash(packageJson) {
const deps = JSON.stringify({
dependencies: packageJson.dependencies || {},
devDependencies: packageJson.devDependencies || {}
});
return Buffer.from(deps).toString('base64').slice(0, 8);
}
}
export async function createSnapshot(action, packages) {
const timeline = new DependencyTimeline();
await timeline.snapshot(action, packages);
}
export async function showTimeline(limit) {
const timeline = new DependencyTimeline();
await timeline.showHistory(limit);
}
export async function rollbackTo(target) {
const timeline = new DependencyTimeline();
const success = await timeline.rollback(target);
if (success) {
console.log(`✅ Rolled back to ${typeof target === 'string' ? target : `entry #${target}`}`);
}
}
//# sourceMappingURL=timeline.js.map