From 4c932edabc8e8bc38b8140db0d86411066335f3f Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Tue, 13 Jan 2026 23:12:27 +0000 Subject: [PATCH] fix: make postinstall patcher idempotent --- CHANGELOG.md | 5 ++++ scripts/postinstall.js | 20 ++++++++++++++ src/postinstall-patcher.test.ts | 46 +++++++++++++++++++++++++++++++++ 3 files changed, 71 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 58ff1f56a..6dd789686 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,10 @@ # Changelog +## 2026.1.13 (Unreleased) + +### Fixes +- Postinstall: treat already-applied pnpm patches as no-ops to avoid npm/bun install failures. + ## 2026.1.12-2 ### Fixes diff --git a/scripts/postinstall.js b/scripts/postinstall.js index 4627b6dc1..c14686f55 100644 --- a/scripts/postinstall.js +++ b/scripts/postinstall.js @@ -149,6 +149,26 @@ function writeFileLines(targetPath, lines, hadTrailingNewline) { function applyHunk(lines, hunk, offset) { let cursor = hunk.oldStart - 1 + offset; + const expected = []; + for (const raw of hunk.lines) { + const marker = raw[0]; + if (marker === " " || marker === "+") { + expected.push(raw.slice(1)); + } + } + if (cursor >= 0 && cursor + expected.length <= lines.length) { + let alreadyApplied = true; + for (let i = 0; i < expected.length; i += 1) { + if (lines[cursor + i] !== expected[i]) { + alreadyApplied = false; + break; + } + } + if (alreadyApplied) { + const delta = hunk.newLines - hunk.oldLines; + return offset + delta; + } + } for (const raw of hunk.lines) { const marker = raw[0]; diff --git a/src/postinstall-patcher.test.ts b/src/postinstall-patcher.test.ts index 79afd2c51..2a468e157 100644 --- a/src/postinstall-patcher.test.ts +++ b/src/postinstall-patcher.test.ts @@ -74,6 +74,52 @@ index 0000000..1111111 100644 fs.rmSync(dir, { recursive: true, force: true }); }); + it("treats already-applied hunks as no-ops", () => { + const dir = makeTempDir(); + const target = path.join(dir, "lib"); + fs.mkdirSync(target); + + const filePath = path.join(target, "main.js"); + const original = `${[ + "var QRCode = require('./../vendor/QRCode'),", + " QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'),", + ' black = "\\033[40m \\033[0m",', + ' white = "\\033[47m \\033[0m",', + " toCell = function (isBlack) {", + ].join("\n")}\n`; + fs.writeFileSync(filePath, original, "utf-8"); + + const patchText = `diff --git a/lib/main.js b/lib/main.js +index 0000000..1111111 100644 +--- a/lib/main.js ++++ b/lib/main.js +@@ -1,5 +1,5 @@ +-var QRCode = require('./../vendor/QRCode'), +- QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel'), ++var QRCode = require('./../vendor/QRCode/index.js'), ++ QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'), + black = "\\033[40m \\033[0m", + white = "\\033[47m \\033[0m", + toCell = function (isBlack) { +`; + + applyPatchSet({ patchText, targetDir: dir }); + applyPatchSet({ patchText, targetDir: dir }); + + const updated = fs.readFileSync(filePath, "utf-8"); + expect(updated).toBe( + `${[ + "var QRCode = require('./../vendor/QRCode/index.js'),", + " QRErrorCorrectLevel = require('./../vendor/QRCode/QRErrorCorrectLevel.js'),", + ' black = "\\033[40m \\033[0m",', + ' white = "\\033[47m \\033[0m",', + " toCell = function (isBlack) {", + ].join("\n")}\n`, + ); + + fs.rmSync(dir, { recursive: true, force: true }); + }); + it("handles multiple hunks with offsets", () => { const dir = makeTempDir(); const filePath = path.join(dir, "file.txt");