taylo
Version:
Make changes to a branch a plugin. A command-line tool to manage and apply plugins '.taylored'. Supports applying, removing, verifying plugins, and generating them from branch (GIT).
248 lines (247 loc) • 9.5 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.resolveTayloredFileName = resolveTayloredFileName;
exports.printUsageAndExit = printUsageAndExit;
exports.analyzeDiffContent = analyzeDiffContent;
exports.getAndAnalyzeDiff = getAndAnalyzeDiff;
exports.extractMessageFromPatch = extractMessageFromPatch;
exports.findPatchesInDirectory = findPatchesInDirectory;
exports.sortPatchesNumerically = sortPatchesNumerically;
const child_process_1 = require("child_process");
const parseDiffModule = __importStar(require("parse-diff"));
const fs = __importStar(require("fs/promises"));
const path = __importStar(require("path"));
const constants_1 = require("./constants");
function resolveTayloredFileName(userInputFileName) {
if (userInputFileName.endsWith(constants_1.TAYLORED_FILE_EXTENSION)) {
return userInputFileName;
}
return userInputFileName + constants_1.TAYLORED_FILE_EXTENSION;
}
function printUsageAndExit(message, printFullUsage = false) {
if (message) {
console.error(message);
}
if (printFullUsage || message) {
console.log(`
Usage: taylored <option> [arguments]`);
console.log(`
Core Patching Commands (require to be run in a Git repository root):`);
console.log(` --add <taylored_file_name> Applies the patch.`);
console.log(` --remove <taylored_file_name> Reverses the patch.`);
console.log(` --verify-add <taylored_file_name> Verifies if the patch can be applied.`);
console.log(` --verify-remove <taylored_file_name> Verifies if the patch can be reversed.`);
console.log(` --save <branch_name> Creates a patch from changes in <branch_name>.`);
console.log(` --list Lists all applied patches.`);
console.log(` --offset <taylored_file_name> [BRANCH_NAME] Adjusts patch offsets based on current branch or specified BRANCH_NAME.`);
console.log(` --automatic <EXTENSIONS> <branch_name> [--exclude <DIR_LIST>]`);
console.log(` Automatically computes and applies line offsets for patches based on Git history.`);
console.log(` --upgrade <patch_file> [target_file_path]`);
console.log(` Analyzes patch frames against the target file (or inferred file).`);
console.log(` If frames are intact, updates the patch content from the target file.`);
}
if (!message) {
process.exit(0);
}
process.exit(1);
}
function analyzeDiffContent(diffOutput) {
let additions = 0;
let deletions = 0;
let isPure = false;
let success = true;
let errorMessage;
if (typeof diffOutput === 'string') {
if (diffOutput.trim() === '') {
isPure = true;
}
else {
try {
const parsedDiffFiles = parseDiffModule.default(diffOutput);
for (const file of parsedDiffFiles) {
additions += file.additions;
deletions += file.deletions;
}
isPure =
(additions > 0 && deletions === 0) ||
(deletions > 0 && additions === 0) ||
(additions === 0 && deletions === 0);
}
catch (parseError) {
errorMessage = `Failed to parse diff output. Error: ${parseError.message}`;
success = false;
}
}
}
else {
errorMessage = `Diff output was unexpectedly undefined.`;
success = false;
}
return { additions, deletions, isPure, success, errorMessage };
}
function getAndAnalyzeDiff(branchName, CWD) {
const command = `git diff HEAD "${branchName.replace(/"/g, '\\"')}"`;
let diffOutput;
let errorMessage;
let commandSuccess = false;
let additions = 0;
let deletions = 0;
let isPure = false;
try {
diffOutput = (0, child_process_1.execSync)(command, { encoding: 'utf8', cwd: CWD });
commandSuccess = true;
}
catch (error) {
if (error.status === 1 && typeof error.stdout === 'string') {
diffOutput = error.stdout;
commandSuccess = true;
}
else {
errorMessage = `CRITICAL ERROR: 'git diff' command failed for branch '${branchName}'.`;
if (error.status) {
errorMessage += ` Exit status: ${error.status}.`;
}
if (error.stderr &&
typeof error.stderr === 'string' &&
error.stderr.trim() !== '') {
errorMessage += ` Git stderr: ${error.stderr.trim()}.`;
}
else if (error.message) {
errorMessage += ` Error message: ${error.message}.`;
}
errorMessage += ` Attempted command: ${command}.`;
commandSuccess = false;
}
}
if (commandSuccess) {
const analysis = analyzeDiffContent(diffOutput);
if (analysis.success) {
additions = analysis.additions;
deletions = analysis.deletions;
isPure = analysis.isPure;
}
else {
errorMessage =
(errorMessage ? errorMessage + '\n' : '') +
`CRITICAL ERROR: Post-diff analysis failed. ${analysis.errorMessage}`;
commandSuccess = false;
}
}
return {
diffOutput,
additions,
deletions,
isPure,
errorMessage,
success: commandSuccess,
};
}
function extractMessageFromPatch(patchContent) {
if (!patchContent || typeof patchContent !== 'string') {
return null;
}
const lines = patchContent.split('\n');
for (const line of lines) {
if (line.startsWith('Subject:')) {
let message = line.substring('Subject:'.length).trim();
message = message.replace(/^\[PATCH(?:\s+\d+\/\d+)?\]\s*/, '');
if (message) {
return message;
}
}
}
let inHeader = true;
const potentialMessageLines = [];
for (const line of lines) {
if (line.startsWith('---') || line.startsWith('diff --git')) {
inHeader = false;
break;
}
if (inHeader &&
(line.startsWith('From:') ||
line.startsWith('Date:') ||
line.startsWith('Signed-off-by:'))) {
continue;
}
if (inHeader &&
line.trim() !== '' &&
!line.startsWith(' ') &&
!line.startsWith('git') &&
!line.includes('/')) {
potentialMessageLines.push(line.trim());
}
}
if (potentialMessageLines.length > 0) {
return potentialMessageLines.slice(0, 3).join('\n');
}
return null;
}
async function findPatchesInDirectory(directoryPath) {
const allFiles = [];
const entries = await fs.readdir(directoryPath, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(directoryPath, entry.name);
if (entry.isDirectory()) {
allFiles.push(...(await findPatchesInDirectory(fullPath)));
}
else if (entry.isFile() && entry.name.endsWith(constants_1.TAYLORED_FILE_EXTENSION)) {
allFiles.push(fullPath);
}
}
return allFiles;
}
function sortPatchesNumerically(filePaths) {
const extractNumber = (fileName) => {
const match = path.basename(fileName).match(/^(\d+)/);
return match ? parseInt(match[1], 10) : Infinity;
};
return [...filePaths].sort((a, b) => {
const numA = extractNumber(a);
const numB = extractNumber(b);
if (numA !== Infinity && numB !== Infinity) {
return numA - numB;
}
else if (numA !== Infinity) {
return -1;
}
else if (numB !== Infinity) {
return 1;
}
else {
return path.basename(a).localeCompare(path.basename(b));
}
});
}