Files
paseo/scripts/postinstall-patches.mjs
T
Mohamed Boudra 9477fe0c00 fix(app): refresh Android layout after background fold changes (#8285)
Backport the Unistyles 3.5.3 Activity configuration fix while keeping 3.2.4 and its existing fixes. Application resources could leave the responsive shell on the previous fold size after resume. Cover the real device-state sequence with a native UI regression.
2026-10-30 17:22:53 +07:00

142 lines
5.8 KiB
JavaScript

import { copyFileSync, existsSync, mkdirSync, readdirSync, rmSync } from "node:fs";
import { spawnSync } from "node:child_process";
import { join, relative } from "node:path";
// In CI we often install a single workspace (e.g. server/relay/website). Only apply patches
// when the patched dependency is actually present.
// `cwd` is where patch-package must run from. Packages that npm does not hoist to the
// workspace root live in their workspace's own node_modules, and patch-package resolves
// the patch's node_modules/... paths relative to its working directory.
const patchedPackages = [
// Backport for expo-modules-core 57.0.23: https://github.com/expo/expo/pull/49634.
// Remove when the installed Expo SDK includes the Activity recreation fix.
{
nodeModulesPath: "node_modules/expo-modules-core",
patchPrefix: "expo-modules-core+",
},
// Backport https://github.com/software-mansion/enriched-markdown/pull/924: preserve settled
// Android measurements across parent clones. Remove those hunks when upgrading past
// 1.2.0-nightly-20261019-926ef06f0; reconcile the other local patch hunks separately.
{
nodeModulesPath: "packages/app/node_modules/react-native-enriched-markdown",
patchPrefix: "react-native-enriched-markdown+",
cwd: "packages/app",
},
// Registry cleanup: remove after react-native-unistyles ships
// https://github.com/jpudysz/react-native-unistyles/pull/1203.
// Adaptive themes: keep until colorScheme reads no longer consume listener-owned media changes.
// withUnistyles: also keep the uniProps filter until upstream consumes its control prop.
// Android configuration: backport 3.5.3; remove the two NativePlatform hunks at >= 3.5.3.
// https://github.com/jpudysz/react-native-unistyles/compare/v3.5.2...v3.5.3
{
nodeModulesPath: "node_modules/react-native-unistyles",
patchPrefix: "react-native-unistyles+",
},
// Backport of https://github.com/software-mansion/react-native-reanimated/pull/10435
// (Reanimated 4.7.1): skip synchronous Android prop updates for unmounted view tags
// instead of throwing and printing a full stack trace on the UI thread per tag per event.
// Remove when the installed Reanimated is >= 4.7.1.
{
nodeModulesPath: "node_modules/react-native-reanimated",
patchPrefix: "react-native-reanimated+",
},
{
nodeModulesPath: "node_modules/react-native-draggable-flatlist",
patchPrefix: "react-native-draggable-flatlist+",
},
{
nodeModulesPath: "node_modules/react-native-gesture-handler",
patchPrefix: "react-native-gesture-handler+",
},
// Exports BottomSheetModalInternalProvider so a subtree can host its own sheets on the shared
// modal stack (see packages/app/src/components/ui/isolated-bottom-sheet-modal/host-scope.tsx), and
// keeps a dismissing modal's status through its final onChange(-1), so a modal with
// enableDismissOnClose=false that unmounts while presented still leaves the portal.
// Dimensions subscription cleanup: https://github.com/gorhom/react-native-bottom-sheet/issues/2734
// and https://github.com/gorhom/react-native-bottom-sheet/pull/2751. Remove the useAnimatedLayout
// hunks once an installed release includes the cleanup; retain the other local hunks.
{
nodeModulesPath: "node_modules/@gorhom/bottom-sheet",
patchPrefix: "@gorhom+bottom-sheet+",
},
{
nodeModulesPath: "packages/app/node_modules/react-native-svg",
patchPrefix: "react-native-svg+",
cwd: "packages/app",
},
{
nodeModulesPath: "node_modules/@mattermost/react-native-paste-input",
patchPrefix: "@mattermost+react-native-paste-input+",
},
{
nodeModulesPath: "packages/server/node_modules/@opencode-ai/sdk",
patchPrefix: "@opencode-ai+sdk+",
cwd: "packages/server",
},
];
const installedPackages = patchedPackages.filter(({ nodeModulesPath }) =>
existsSync(nodeModulesPath),
);
if (!existsSync("patches") || installedPackages.length === 0) {
process.exit(0);
}
const patchFiles = readdirSync("patches").filter((file) => file.endsWith(".patch"));
const isWindows = process.platform === "win32";
const cmd = isWindows ? "patch-package.cmd" : "patch-package";
let failed = false;
for (const [index, { nodeModulesPath, patchPrefix, cwd = "." }] of installedPackages.entries()) {
const files = patchFiles.filter((file) => file.startsWith(patchPrefix));
if (files.length === 0) {
continue;
}
const outcome = applyPatches(cwd, files, index);
if (outcome === "applied") {
continue;
}
failed = true;
if (outcome === "unavailable") {
continue;
}
// patch-package cannot apply a patch that changed after an earlier version of it was
// applied. Without the package, the next install fetches it clean and applies the patch.
rmSync(nodeModulesPath, { recursive: true, force: true });
console.error(
`postinstall-patches: removed ${nodeModulesPath} because its patch did not apply. ` +
"Run npm install again to reinstall it with the current patch. " +
"If it fails again on the clean package, the patch is out of date and must be regenerated.",
);
}
process.exit(failed ? 1 : 0);
function applyPatches(cwd, files, index) {
const tempPatchDir = join(".tmp", `postinstall-patches-${process.pid}-${index}`);
mkdirSync(tempPatchDir, { recursive: true });
for (const patchFile of files) {
copyFileSync(join("patches", patchFile), join(tempPatchDir, patchFile));
}
let result;
try {
result = spawnSync(cmd, ["--patch-dir", relative(cwd, tempPatchDir), "--error-on-fail"], {
cwd,
shell: isWindows,
stdio: "inherit",
windowsHide: true,
});
} finally {
rmSync(tempPatchDir, { recursive: true, force: true });
}
if (result.error) {
console.error("postinstall-patches: patch-package failed to spawn:", result.error.message);
return "unavailable";
}
return result.status === 0 ? "applied" : "rejected";
}