Files
paseo/scripts/postinstall-patches.test.mjs
T
2026-10-27 16:45:05 +01:00

132 lines
4.2 KiB
JavaScript

import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import { existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from "node:fs";
import { tmpdir } from "node:os";
import path from "node:path";
import { test } from "node:test";
const script = path.resolve("scripts", "postinstall-patches.mjs");
const bin = path.resolve("node_modules", ".bin");
const packageName = "react-native-draggable-flatlist";
const patchFile = `${packageName}+4.0.3.patch`;
test("patched Markdown ships iOS props generated from its native specs", () => {
const packageDir = path.resolve("packages/app/node_modules/react-native-enriched-markdown");
const output = mkdtempSync(path.join(tmpdir(), "markdown-ios-codegen-"));
try {
// includesGeneratedCode makes CocoaPods use the shipped headers instead of
// regenerating them after patch-package changes the TypeScript specs.
const result = spawnSync(
process.execPath,
[
path.resolve("node_modules/react-native/scripts/generate-codegen-artifacts.js"),
"-p",
packageDir,
"-t",
"ios",
"-o",
output,
"-s",
"library",
],
{ encoding: "utf8" },
);
assert.equal(result.status, 0, result.stdout + result.stderr);
const header = "ReactCodegen/EnrichedMarkdownTextSpec/Props.h";
assert.equal(
readFileSync(path.join(packageDir, "ios/generated", header), "utf8"),
readFileSync(path.join(output, header), "utf8"),
);
} finally {
rmSync(output, { recursive: true, force: true });
}
});
function createCheckout() {
const root = mkdtempSync(path.join(tmpdir(), "postinstall-patches-"));
writeFileSync(path.join(root, "package.json"), JSON.stringify({ name: "checkout" }));
const packageDir = path.join(root, "node_modules", packageName);
mkdirSync(packageDir, { recursive: true });
writeFileSync(
path.join(packageDir, "package.json"),
JSON.stringify({ name: packageName, version: "4.0.3" }),
);
writeFileSync(path.join(packageDir, "index.js"), "export const value = 1;\n");
mkdirSync(path.join(root, "patches"));
return { root, packageDir };
}
function writePatch(root, from, to) {
const file = `node_modules/${packageName}/index.js`;
writeFileSync(
path.join(root, "patches", patchFile),
[
`diff --git a/${file} b/${file}`,
`--- a/${file}`,
`+++ b/${file}`,
"@@ -1 +1 @@",
`-export const value = ${from};`,
`+export const value = ${to};`,
"",
].join("\n"),
);
}
function install(root, PATH = `${bin}${path.delimiter}${process.env.PATH}`) {
return spawnSync(process.execPath, [script], {
cwd: root,
encoding: "utf8",
env: { ...process.env, PATH },
});
}
test("a patch that changed since it was applied removes the package for a clean reinstall", () => {
const { root, packageDir } = createCheckout();
try {
writePatch(root, 1, 2);
assert.equal(install(root).status, 0);
assert.equal(
readFileSync(path.join(packageDir, "index.js"), "utf8"),
"export const value = 2;\n",
);
writePatch(root, 1, 3);
const result = install(root);
assert.equal(result.status, 1);
assert.match(result.stderr, /Run npm install again/);
assert.equal(existsSync(packageDir), false);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("an already applied patch leaves the package in place", () => {
const { root, packageDir } = createCheckout();
try {
writePatch(root, 1, 2);
assert.equal(install(root).status, 0);
assert.equal(install(root).status, 0);
assert.equal(
readFileSync(path.join(packageDir, "index.js"), "utf8"),
"export const value = 2;\n",
);
} finally {
rmSync(root, { recursive: true, force: true });
}
});
test("a missing patch-package fails the install and leaves the package in place", () => {
const { root, packageDir } = createCheckout();
try {
writePatch(root, 1, 2);
const result = install(root, root);
assert.equal(result.status, 1);
assert.match(result.stderr, /failed to spawn/);
assert.equal(existsSync(packageDir), true);
} finally {
rmSync(root, { recursive: true, force: true });
}
});