writepool
Version:
Collects files before writing to disk, allowing change tracking, output management, and dry-runs for controlled file handling.
198 lines (190 loc) • 5.36 kB
JavaScript
'use strict';
var node_path = require('node:path');
var node_url = require('node:url');
var colorette = require('colorette');
var fastMyersDiff = require('fast-myers-diff');
var node_fs = require('node:fs');
var EventEmitter = require('node:events');
var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
const flattenDiff = (diff) => {
return Array.from(diff).flatMap(([type, contents]) => {
return contents.map((line) => [type, line]);
});
};
class FileChanges {
constructor(path) {
this.path = path;
}
log = [];
list() {
return this.log.map((change, index) => ({
index,
timestamp: change.timestamp,
origin: change.origin
}));
}
stackChange(contents, origin) {
const record = {
timestamp: Date.now(),
origin,
contents
};
this.log.push(record);
}
get(index) {
return this.log.at(index);
}
diff(prevIndex, nextIndex) {
const prev = this.log.at(prevIndex);
const next = this.log.at(nextIndex);
const diff = fastMyersDiff.calcSlices(
prev.contents.split("\n"),
next.contents.split("\n")
);
const flatDiff = flattenDiff(diff);
const flat = flatDiff.map(([type, line]) => {
let icon = " ";
if (type === -1) {
icon = " - ";
}
if (type === 1) {
icon = " + ";
}
return `${icon}${line}`;
});
const formatted = flatDiff.map(([type, line]) => {
let color = colorette.reset;
let icon = colorette.reset(" ");
if (type === -1) {
color = colorette.red;
icon = colorette.red(" - ");
}
if (type === 1) {
color = colorette.green;
icon = colorette.green(" + ");
}
return `${icon}${color(line)}`;
});
return {
diff: flatDiff,
flat,
formatted,
log() {
console.log("\n", formatted.join("\n"));
}
};
}
}
class ChangeLog {
changes = /* @__PURE__ */ new Map();
add(path, contents, origin) {
if (!this.changes.has(path)) {
this.changes.set(path, new FileChanges(path));
}
this.changes.get(path).stackChange(contents, origin);
}
get(path) {
return this.changes.get(path);
}
}
const outputFileSync = (file, data, options) => {
const dir = node_path.dirname(file);
if (!node_fs.existsSync(dir)) {
node_fs.mkdirSync(dir, { recursive: true });
}
node_fs.writeFileSync(file, data, options);
};
class TypedEventEmitter {
emitter = new EventEmitter();
on(name, listener) {
this.emitter.on(name, listener);
}
off(name, listener) {
this.emitter.off(name, listener);
}
emit(name, payload) {
return this.emitter.emit(name, payload);
}
once(name, listener) {
this.emitter.once(name, listener);
}
}
class Writepool {
static instance;
emitter = new TypedEventEmitter();
changeLog = new ChangeLog();
files = /* @__PURE__ */ new Map();
origin;
rootCollection = this;
options = {
outDir: node_path.dirname(node_url.fileURLToPath((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)))),
dry: false,
logChanges: false
};
on = this.rootCollection.emitter.on;
once = this.rootCollection.emitter.once;
constructor(options) {
this.options = { ...this.options, ...options };
this.rootCollection.on = this.rootCollection.emitter.on.bind(this);
this.rootCollection.once = this.rootCollection.emitter.once.bind(this);
}
get size() {
return this.rootCollection.files.size;
}
static getInstance(options) {
if (!Writepool.instance) {
Writepool.instance = new Writepool(options);
}
return Writepool.instance;
}
withOrigin(name) {
const collection = new Writepool(this.options);
collection.origin = name;
collection.rootCollection = this.rootCollection;
return collection;
}
get(pathOrPredicate) {
if (typeof pathOrPredicate === "function") {
for (const file of this.rootCollection.files) {
if (pathOrPredicate(...file)) {
return file;
}
}
return;
}
if (this.rootCollection.files.has(pathOrPredicate)) {
return [pathOrPredicate, this.rootCollection.files.get(pathOrPredicate)];
}
}
write(path, contents, origin = this.origin) {
this.rootCollection.files.set(path, contents);
if (this.options.logChanges) {
this.rootCollection.changeLog.add(path, contents, origin);
}
this.rootCollection.emitter.emit("file:stacked", {
path,
origin,
contents
});
}
getChanges(path) {
if (!this.options.logChanges) return;
return this.rootCollection.changeLog.get(path);
}
*writeFilesToDisk(options) {
const finalOptions = {
...this.rootCollection.options,
...options
};
let index = 1;
for (const [file, contents] of this.rootCollection.files) {
const path = node_path.resolve(finalOptions.outDir, file);
if (!this.rootCollection.options.dry) {
outputFileSync(path, contents);
}
yield { path, index, total: this.rootCollection.files.size };
index++;
}
}
}
exports.Writepool = Writepool;