@smushytaco/custompatch
Version:
Tool for patching buggy NPM packages instead of forking them
182 lines (179 loc) • 7.46 kB
JavaScript
import fs from 'node:fs';
import path from 'pathe';
import { parsePatch, reversePatch, applyPatch, createTwoFilesPatch } from 'diff';
import pc from 'picocolors';
import { r as readFileContent, p as pathNormalize, e as ensureDirectoryExists } from './file-utilities.mjs';
import { a as patchDirectory, c as currentDirectory, t as temporaryDirectory, p as programOptions } from './variables.mjs';
import 'commander';
import 'node:os';
function createPatch(packageName, pathname, patch) {
const newFile = path.join(currentDirectory, "node_modules", packageName, pathname);
const oldFile = path.join(temporaryDirectory, packageName, pathname);
const oldString = fs.existsSync(oldFile) ? readFileContent(oldFile) : "";
const newString = readFileContent(newFile);
if (pathname === "package.json" && !programOptions.all)
return;
if (oldString !== newString) {
patch.write(createTwoFilesPatch(oldFile.replace(temporaryDirectory, ""), newFile.replace(path.join(currentDirectory, "node_modules"), ""), oldString, newString));
}
}
function makePatchName(packageName, version) {
return `${packageName.replaceAll("/", "+")}#${version}.patch`;
}
async function comparePackages(packageName, version) {
const patchFile = makePatchName(packageName, version);
ensureDirectoryExists(patchDirectory);
const stream = fs.createWriteStream(path.join(patchDirectory, patchFile));
stream.on("error", (error) => {
console.error(`${pc.redBright("ERROR:")} Failed to write to patch file - ${error.message}`);
});
stream.cork();
scanFiles(packageName, "", stream);
stream.uncork();
if (stream.write("")) {
stream.end();
} else {
stream.once("drain", () => {
stream.end();
});
}
console.log(`Successfully created ${pc.greenBright(patchFile)}`);
}
function scanFiles(packageName, source, patch) {
const baseDirectory = path.join(currentDirectory, "node_modules", packageName);
const stack = [source];
const visitedPaths = /* @__PURE__ */ new Set();
while (stack.length > 0) {
const currentSource = stack.pop();
if (currentSource === void 0)
continue;
const directoryPath = path.join(baseDirectory, currentSource);
let files;
try {
files = fs.readdirSync(directoryPath);
} catch (error) {
console.error(`${pc.redBright("ERROR:")} Failed to read directory ${directoryPath} - ${error instanceof Error ? error.message : String(error)}`);
continue;
}
for (const item of files) {
if (item === "node_modules")
continue;
const pathname = path.join(currentSource, item);
const itemPath = path.join(baseDirectory, pathname);
let stat;
try {
stat = fs.lstatSync(itemPath);
} catch (error) {
console.error(`${pc.redBright("ERROR:")} Failed to get stats for ${itemPath} - ${error instanceof Error ? error.message : String(error)}`);
continue;
}
if (stat.isSymbolicLink()) {
continue;
}
if (stat.isDirectory()) {
const realPath = fs.realpathSync(itemPath);
if (visitedPaths.has(realPath)) {
continue;
}
visitedPaths.add(realPath);
stack.push(pathname);
} else {
createPatch(packageName, pathname, patch);
}
}
}
}
async function readPatch(packageName, version, reverse = false) {
const thePackageName = packageName.replaceAll("+", path.sep);
const config = getConfig(thePackageName);
if (config) {
const patchFile = makePatchName(packageName, version);
const patchFilePath = path.join(patchDirectory, patchFile);
if (!fs.existsSync(patchFilePath)) {
console.warn(`${pc.yellowBright("WARNING:")} Patch file "${patchFile}" does not exist.`);
return;
}
const patchContent = readFileContent(patchFilePath);
const patches = parsePatch(patchContent);
for (const patchItem of patches) {
const filePath = patchItem.newFileName ?? patchItem.oldFileName;
if (!filePath) {
console.error(`${pc.redBright("ERROR:")} Patch item has no file names for package ${packageName}`);
continue;
}
const normalizedPath = pathNormalize(filePath);
const fileName = path.join(currentDirectory, "node_modules", normalizedPath);
let fileContent = "";
if (fs.existsSync(fileName)) {
fileContent = readFileContent(fileName);
} else {
console.warn(`${pc.yellowBright("WARNING:")} File "${fileName}" does not exist - skipping.`);
continue;
}
if (reverse) {
const reversedPatchText = reversePatch(patchItem);
const reversePatchedContent = applyPatch(fileContent, reversedPatchText);
if (reversePatchedContent === false) {
const patchedContent = applyPatch(fileContent, patchItem);
if (patchedContent === false) {
console.warn(`${pc.yellowBright("WARNING:")} Failed to reverse patch for ${pc.redBright(fileName)}`);
} else {
console.log(`Patch already reversed for ${pc.greenBright(fileName)}`);
}
} else {
try {
fs.writeFileSync(fileName, reversePatchedContent, "utf8");
console.log(`Reversed patch for ${pc.greenBright(fileName)}`);
} catch (error) {
console.error(`${pc.redBright("ERROR:")} Could not write the new content for file ${fileName} - ${error instanceof Error ? error.message : String(error)}`);
}
}
} else {
const patchedContent = applyPatch(fileContent, patchItem);
if (patchedContent === false) {
const reversedPatchText = reversePatch(patchItem);
const reversePatchedContent = applyPatch(fileContent, reversedPatchText);
if (reversePatchedContent === false) {
console.warn(`${pc.yellowBright("WARNING:")} Failed to apply patch to ${pc.redBright(fileName)}`);
} else {
console.log(`Patch already applied to ${pc.greenBright(fileName)}`);
}
} else {
try {
fs.writeFileSync(fileName, patchedContent, "utf8");
console.log(`Patched ${pc.greenBright(fileName)}`);
} catch (error) {
console.error(`${pc.redBright("ERROR:")} Could not write the new content for file ${fileName} - ${error instanceof Error ? error.message : String(error)}`);
}
}
}
}
} else {
console.error(`${pc.redBright("ERROR:")} Could not get config for package ${packageName}`);
}
}
function getConfig(packageName) {
const folder = path.join(currentDirectory, "node_modules", packageName);
const configName = path.join(folder, "package.json");
if (!fs.existsSync(folder)) {
console.error(`${pc.redBright("ERROR:")} Missing folder "${pc.whiteBright(folder)}"`);
return false;
}
try {
fs.accessSync(configName, fs.constants.R_OK);
} catch {
console.error(`${pc.redBright("ERROR:")} Cannot read "${pc.whiteBright(configName)}"`);
return false;
}
const packageConfig = readFileContent(configName);
let config;
try {
config = JSON.parse(packageConfig);
} catch (error) {
console.error(`${pc.redBright("ERROR:")} Could not parse "${pc.whiteBright("package.json")}" - ${pc.redBright(error instanceof Error ? error.message : String(error))}`);
return false;
}
return config;
}
export { comparePackages as c, getConfig as g, readPatch as r };
//# sourceMappingURL=patch-utilities.mjs.map