UNPKG

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
'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;