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).
585 lines (584 loc) • 26.8 kB
JavaScript
;
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.PatchAnalyzer = void 0;
const fs = __importStar(require("fs"));
class PatchAnalyzer {
constructor() {
}
readPatch(patchPath) {
try {
const patchContent = fs.readFileSync(patchPath, 'utf8');
return this.parsePatch(patchContent);
}
catch (error) {
throw new Error(`Error reading patch file ${patchPath}: ${error.message}`);
}
}
parsePatch(patchContent) {
const lines = patchContent.split('\n');
const patches = [];
let currentPatch = null;
let currentHunk = null;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (line.startsWith('---')) {
if (currentPatch) {
patches.push(currentPatch);
}
currentPatch = {
oldFile: line.substring(4).trim(),
newFile: null,
hunks: [],
};
}
else if (line.startsWith('+++')) {
if (currentPatch) {
currentPatch.newFile = line.substring(4).trim();
}
}
else if (line.startsWith('@@')) {
const hunkInfo = this.parseHunkHeader(line);
currentHunk = {
oldStart: hunkInfo.oldStart,
oldCount: hunkInfo.oldCount,
newStart: hunkInfo.newStart,
newCount: hunkInfo.newCount,
changes: [],
context: [],
};
if (currentPatch) {
currentPatch.hunks.push(currentHunk);
}
}
else if (currentHunk &&
(line.startsWith(' ') || line.startsWith('+') || line.startsWith('-'))) {
const changeType = line.charAt(0);
const content = line.substring(1);
currentHunk.changes.push({
type: changeType,
content: content,
lineNumber: 0,
});
}
}
if (currentPatch) {
patches.push(currentPatch);
}
return patches;
}
parseHunkHeader(header) {
const match = header.match(/@@\s*-(\d+)(?:,(\d+))?\s*\+(\d+)(?:,(\d+))?\s*@@/);
if (!match) {
throw new Error(`Invalid hunk header: ${header}`);
}
const oldStart = parseInt(match[1], 10);
const oldCountRaw = parseInt(match[2], 10);
const newStart = parseInt(match[3], 10);
const newCountRaw = parseInt(match[4], 10);
return {
oldStart: oldStart,
oldCount: isNaN(oldCountRaw) ? 1 : oldCountRaw,
newStart: newStart,
newCount: isNaN(newCountRaw) ? 1 : newCountRaw,
};
}
identifyModificationBlocks(patch) {
const blocks = [];
patch.hunks.forEach((hunk, hunkIndex) => {
let currentBlock = null;
let lastContextLine = null;
for (let i = 0; i < hunk.changes.length; i++) {
const change = hunk.changes[i];
if (change.type === ' ') {
const currentOldLine = this.calculateActualLineNumber(hunk, i, 'old');
const currentNewLine = this.calculateActualLineNumber(hunk, i, 'new');
if (currentBlock && !currentBlock.bottomFrame) {
currentBlock.bottomFrame = {
content: change.content,
oldLineNumber: currentOldLine,
newLineNumber: currentNewLine,
};
blocks.push(currentBlock);
currentBlock = null;
}
lastContextLine = {
content: change.content,
oldLineNumber: currentOldLine,
newLineNumber: currentNewLine,
};
}
else if (change.type === '+' || change.type === '-') {
if (!currentBlock) {
currentBlock = {
type: change.type === '+' ? 'addition' : 'deletion',
changes: [],
topFrame: lastContextLine,
bottomFrame: null,
hunkIndex: hunkIndex,
startLineNumber: this.calculateActualLineNumber(hunk, i, change.type === '+' ? 'new' : 'old'),
};
}
const expectedType = currentBlock.type === 'addition' ? '+' : '-';
if (change.type !== expectedType) {
console.warn(`Warning: Mixed modification block (e.g., additions interspersed with deletions without intermediate context) detected in hunk ${hunkIndex}. Current block processing stopped.`);
currentBlock = null;
lastContextLine = null;
continue;
}
currentBlock.changes.push(change);
}
}
if (currentBlock) {
blocks.push(currentBlock);
}
});
return blocks;
}
calculateActualLineNumber(hunk, changeIndexInHunk, lineType) {
let oldLineCounter = hunk.oldStart;
let newLineCounter = hunk.newStart;
for (let i = 0; i < changeIndexInHunk; i++) {
const change = hunk.changes[i];
if (change.type === ' ') {
oldLineCounter++;
newLineCounter++;
}
else if (change.type === '-') {
oldLineCounter++;
}
else if (change.type === '+') {
newLineCounter++;
}
}
if (lineType === 'old') {
return oldLineCounter;
}
else {
return newLineCounter;
}
}
updatePatchBlocks(patch, blocks, fileLines) {
for (const block of blocks) {
if (!block.topFrame) {
console.warn('Skipping block update due to missing top frame. Block cannot be reliably anchored.');
continue;
}
const topFrameLineInFile = block.type === 'addition'
? block.topFrame.newLineNumber
: block.topFrame.oldLineNumber;
let actualBlockStartInFile = -1;
for (let i = Math.max(0, topFrameLineInFile - 5); i < fileLines.length; i++) {
if (fileLines[i]?.trim() === block.topFrame.content.trim()) {
const expectedTopFrameIndex = (block.type === 'addition'
? block.topFrame.newLineNumber
: block.topFrame.oldLineNumber) - 1;
if (i === expectedTopFrameIndex) {
if (block.bottomFrame) {
const expectedBottomFrameIndex = i + 1 + block.changes.length;
if (expectedBottomFrameIndex < fileLines.length &&
fileLines[expectedBottomFrameIndex]?.trim() ===
block.bottomFrame.content.trim()) {
actualBlockStartInFile = i + 1;
break;
}
else {
console.warn(`Warning: Top frame matched at line ${i + 1}, but bottom frame did not match as expected. Block update skipped for safety.`);
actualBlockStartInFile = -1;
break;
}
}
else {
actualBlockStartInFile = i + 1;
break;
}
}
}
}
if (actualBlockStartInFile !== -1) {
const hunk = patch.hunks[block.hunkIndex];
let firstChangeInHunkIndex = -1;
let tempOldLine = hunk.oldStart;
let tempNewLine = hunk.newStart;
let changesMatched = 0;
for (let hunkChangeIndex = 0; hunkChangeIndex < hunk.changes.length; hunkChangeIndex++) {
const currentHunkChange = hunk.changes[hunkChangeIndex];
const currentBlockChange = block.changes[changesMatched];
if (currentBlockChange &&
currentHunkChange.type === currentBlockChange.type &&
((block.type === 'addition' &&
tempNewLine === block.startLineNumber) ||
(block.type === 'deletion' &&
tempOldLine === block.startLineNumber))) {
let sequenceMatches = true;
for (let k = 0; k < block.changes.length; k++) {
if (hunkChangeIndex + k >= hunk.changes.length ||
hunk.changes[hunkChangeIndex + k].type !==
block.changes[k].type ||
hunk.changes[hunkChangeIndex + k].content !==
block.changes[k].content) {
sequenceMatches = false;
break;
}
}
if (sequenceMatches) {
firstChangeInHunkIndex = hunkChangeIndex;
break;
}
}
if (currentHunkChange.type === ' ') {
tempOldLine++;
tempNewLine++;
}
else if (currentHunkChange.type === '-') {
tempOldLine++;
}
else if (currentHunkChange.type === '+') {
tempNewLine++;
}
}
if (firstChangeInHunkIndex !== -1) {
for (let i = 0; i < block.changes.length; i++) {
const changeToUpdateInHunk = hunk.changes[firstChangeInHunkIndex + i];
const correspondingFileLineIndex = actualBlockStartInFile + i;
if (correspondingFileLineIndex < fileLines.length) {
changeToUpdateInHunk.content =
fileLines[correspondingFileLineIndex];
}
else {
console.warn(`Warning: Target file content ended before block of type '${block.type}' could be fully updated. Hunk ${block.hunkIndex}.`);
break;
}
}
}
else {
console.warn(`Warning: Could not find matching start of block in hunk for update. Hunk ${block.hunkIndex}. This may indicate an issue with startLineNumber calculation or block identification.`);
}
}
else {
console.warn(`Warning: Could not find block's starting position in the target file based on its top frame. Top frame content from patch: "${block.topFrame.content.trim()}". Block update skipped. Hunk ${block.hunkIndex}.`);
}
}
}
reconstructPatch(patches) {
let patchContent = '';
for (const patch of patches) {
patchContent += `--- ${patch.oldFile}\n`;
patchContent += `+++ ${patch.newFile}\n`;
for (const hunk of patch.hunks) {
patchContent += `@@ -${hunk.oldStart},${hunk.oldCount} +${hunk.newStart},${hunk.newCount} @@\n`;
for (const change of hunk.changes) {
patchContent += `${change.type}${change.content}\n`;
}
}
}
return patchContent;
}
savePatch(patchPath, patchContent) {
const backupPath = patchPath + '.backup';
try {
if (fs.existsSync(patchPath)) {
fs.copyFileSync(patchPath, backupPath);
console.log(`Backup of original patch saved to: ${backupPath}`);
}
fs.writeFileSync(patchPath, patchContent);
console.log(`Updated patch saved to: ${patchPath}`);
}
catch (error) {
throw new Error(`Error saving patch ${patchPath}: ${error.message}`);
}
}
async verifyIntegrityAndUpgrade(patchPath, targetFilePathOverride) {
const parsedPatches = this.readPatch(patchPath);
const results = [];
for (const singlePatch of parsedPatches) {
let filePath = targetFilePathOverride;
if (!filePath) {
filePath = singlePatch.oldFile.startsWith('a/')
? singlePatch.oldFile.substring(2)
: singlePatch.oldFile;
if ((!filePath || filePath === '/dev/null') && singlePatch.newFile) {
filePath = singlePatch.newFile.startsWith('b/')
? singlePatch.newFile.substring(2)
: singlePatch.newFile;
}
}
if (!filePath || filePath === '/dev/null') {
results.push({
file: singlePatch.oldFile ||
singlePatch.newFile ||
'Unknown file (from patch)',
status: 'error',
message: 'Could not determine target file path from patch for verification.',
});
continue;
}
let isEntireFilePatch = false;
if (singlePatch.hunks.length === 1) {
const hunk = singlePatch.hunks[0];
const isFullFileAddition = hunk.oldStart === 0 && hunk.oldCount === 0;
const isFullFileDeletion = hunk.newStart === 0 && hunk.newCount === 0;
const isFullFileReplacement = hunk.oldStart > 0 &&
hunk.newStart > 0 &&
!hunk.changes.some((c) => c.type === ' ');
if (isFullFileAddition || isFullFileDeletion || isFullFileReplacement) {
isEntireFilePatch = true;
}
}
if (isEntireFilePatch) {
const hunk = singlePatch.hunks[0];
const isFullAddition = hunk.oldStart === 0 && hunk.oldCount === 0;
const isFullDeletion = hunk.newStart === 0 && hunk.newCount === 0;
if (isFullAddition) {
console.log(`Patch for '${filePath}' is a full file addition. Updating content from target file...`);
const newFileLines = fs.readFileSync(filePath, 'utf8').split('\n');
hunk.changes = newFileLines.map((line) => ({
type: '+',
content: line,
lineNumber: 0,
}));
hunk.newCount = newFileLines.length;
results.push({
file: filePath,
status: 'intact',
message: 'Full file patch content was updated from the target file.',
updated: true,
});
continue;
}
if (isFullDeletion) {
console.log(`Patch for '${filePath}' is a full file deletion. Checking if file exists...`);
if (!fs.existsSync(filePath)) {
results.push({
file: filePath,
status: 'intact',
message: 'Full file deletion patch is valid (file does not exist). No update needed.',
updated: false,
});
}
else {
results.push({
file: filePath,
status: 'corrupted',
message: 'Full file deletion patch is corrupted (file still exists). No update performed.',
updated: false,
});
}
continue;
}
if (fs.existsSync(filePath)) {
console.log(`Patch for '${filePath}' is a full file replacement. Updating content from target file...`);
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileLines = fileContent.split('\n');
const originalLinesCount = hunk.changes.filter((c) => c.type === '-').length;
hunk.changes = fileLines.map((line) => ({
type: '+',
content: line,
lineNumber: 0,
}));
hunk.oldStart = 1;
hunk.oldCount = originalLinesCount;
hunk.newStart = 1;
hunk.newCount = fileLines.length;
results.push({
file: filePath,
status: 'intact',
message: 'Full file replacement patch content was updated from the target file.',
updated: true,
});
}
else {
results.push({
file: filePath,
status: 'error',
message: `Target file not found for full file replacement: ${filePath}`,
updated: false,
});
}
continue;
}
if (!fs.existsSync(filePath)) {
results.push({
file: filePath,
status: 'error',
message: `Target file not found: ${filePath}`,
});
continue;
}
const fileContent = fs.readFileSync(filePath, 'utf8');
const fileLines = fileContent.split('\n');
const modificationBlocks = this.identifyModificationBlocks(singlePatch);
if (modificationBlocks.length === 0) {
results.push({
file: filePath,
status: 'intact',
message: 'No homogeneous modification blocks found to verify or upgrade.',
blocks: [],
});
continue;
}
const blockChecks = [];
let allFramesIntact = true;
for (const block of modificationBlocks) {
const topFrameCheck = this.checkFrame(fileLines, block.topFrame, 'top', block, isEntireFilePatch);
const bottomFrameCheck = this.checkFrame(fileLines, block.bottomFrame, 'bottom', block, isEntireFilePatch);
blockChecks.push({
blockType: block.type,
topFrame: topFrameCheck,
bottomFrame: bottomFrameCheck,
});
if (!topFrameCheck.intact || !bottomFrameCheck.intact) {
allFramesIntact = false;
}
}
results.push({
file: filePath,
status: allFramesIntact ? 'intact' : 'corrupted',
message: allFramesIntact
? 'All frames are intact.'
: 'Some frames are modified or not found. Patch not updated.',
blocks: blockChecks,
updated: false,
});
if (allFramesIntact && modificationBlocks.length > 0) {
console.log(`Frames are intact for ${filePath}. Attempting to upgrade patch content...`);
this.updatePatchBlocks(singlePatch, modificationBlocks, fileLines);
const lastResult = results[results.length - 1];
if (lastResult) {
lastResult.updated = true;
lastResult.message =
'All frames are intact. Patch content has been updated from the target file.';
}
}
}
const anyUpdates = results.some((result) => result.updated);
if (anyUpdates) {
const updatedPatchContent = this.reconstructPatch(parsedPatches);
this.savePatch(patchPath, updatedPatchContent);
console.log('Patch file has been successfully updated with new content.');
}
return results;
}
checkFrame(fileLines, frame, position, block, isEntireFilePatch) {
if (!frame) {
return {
intact: true,
message: `Frame ${position} not present, which is valid for patches at file boundaries.`,
expected: null,
actual: null,
};
}
let frameLineIndexInFile;
if (position === 'top') {
frameLineIndexInFile =
(block.type === 'addition'
? frame.newLineNumber
: frame.oldLineNumber) - 1;
}
else {
frameLineIndexInFile =
(block.type === 'addition'
? frame.newLineNumber
: frame.oldLineNumber) - 1;
}
if (frameLineIndexInFile < 0 || frameLineIndexInFile >= fileLines.length) {
return {
intact: false,
message: `Frame ${position} expected line number ${frameLineIndexInFile + 1} (0-indexed: ${frameLineIndexInFile}) is outside file boundaries (0-${fileLines.length - 1}).`,
expected: frame.content,
actual: null,
lineNumber: frameLineIndexInFile + 1,
};
}
const actualContent = fileLines[frameLineIndexInFile];
const intact = actualContent?.trim() === frame.content.trim();
return {
intact: intact,
message: intact
? `Frame ${position} is intact at line ${frameLineIndexInFile + 1}.`
: `Frame ${position} content mismatch at line ${frameLineIndexInFile + 1}.`,
expected: frame.content,
actual: actualContent,
lineNumber: frameLineIndexInFile + 1,
};
}
generateReport(results) {
let report = '=== FRAME INTEGRITY VERIFICATION REPORT ===\n\n';
for (const result of results) {
report += `File: ${result.file}\n`;
report += `Status: ${result.status.toUpperCase()}`;
if (result.updated) {
report += ' (PATCH UPDATED)';
}
report += '\n';
report += `Message: ${result.message}\n`;
if (result.blocks && result.blocks.length > 0) {
report += `\n Modification Blocks Checked: ${result.blocks.length}\n`;
result.blocks.forEach((blockCheck, index) => {
report += `\n Block ${index + 1} (${blockCheck.blockType}):\n`;
report += ` Top Frame: ${blockCheck.topFrame.intact ? 'INTACT' : 'MODIFIED/MISSING'}`;
if (blockCheck.topFrame.lineNumber) {
report += ` (Expected at line ${blockCheck.topFrame.lineNumber})`;
}
report += '\n';
if (!blockCheck.topFrame.intact &&
blockCheck.topFrame.expected !== null) {
report += ` Expected: "${blockCheck.topFrame.expected}"\n`;
report += ` Actual: "${blockCheck.topFrame.actual}"\n`;
}
else if (!blockCheck.topFrame.intact) {
report += ` Message: ${blockCheck.topFrame.message}\n`;
}
report += ` Bottom Frame: ${blockCheck.bottomFrame.intact ? 'INTACT' : 'MODIFIED/MISSING'}`;
if (blockCheck.bottomFrame.lineNumber) {
report += ` (Expected at line ${blockCheck.bottomFrame.lineNumber})`;
}
report += '\n';
if (!blockCheck.bottomFrame.intact &&
blockCheck.bottomFrame.expected !== null) {
report += ` Expected: "${blockCheck.bottomFrame.expected}"\n`;
report += ` Actual: "${blockCheck.bottomFrame.actual}"\n`;
}
else if (!blockCheck.bottomFrame.intact) {
report += ` Message: ${blockCheck.bottomFrame.message}\n`;
}
});
}
report += '\n' + '='.repeat(50) + '\n\n';
}
return report;
}
}
exports.PatchAnalyzer = PatchAnalyzer;