UNPKG

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
"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.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;