OpenMiInk/scripts/reinstall-moannotes.ts
Hellings 1e953b8132 ok
2026-10-16 13:05:27 -04:00

130 lines
4.4 KiB
TypeScript

import { existsSync } from "node:fs";
import { spawnSync } from "node:child_process";
import path from "node:path";
const root = path.resolve(import.meta.dir, "..");
const args = process.argv.slice(2);
const app = value("--app") ?? "MoanNote";
const profiles: Record<
string,
{
patchScript: string;
legacyPackage: string;
patchedPackage: string;
removeLegacy: boolean;
legacySystemImage: boolean;
}
> = {
MoanNote: {
patchScript: "patch:moannotes",
legacyPackage: "com.moan.notes",
patchedPackage: "rip.crit.ms.com.moan.notes",
removeLegacy: false,
legacySystemImage: true,
},
MoanLauncher: {
patchScript: "patch:moanlauncher",
legacyPackage: "com.note.launcher",
patchedPackage: "rip.crit.ms.com.note.launcher",
removeLegacy: false,
legacySystemImage: true,
},
};
const profile = profiles[app];
if (!profile) throw new Error(`Unsupported app ${app}`);
function value(flag: string): string | undefined {
const index = args.indexOf(flag);
return index >= 0 ? args[index + 1] : undefined;
}
function run(command: string, commandArgs: string[]): void {
const result = spawnSync(command, commandArgs, {
cwd: root,
stdio: "inherit",
});
if (result.error) throw result.error;
if (result.status !== 0)
throw new Error(`${command} ${commandArgs.join(" ")} failed`);
}
function uninstallIfPresent(serial: string, packageName: string): void {
const result = spawnSync("adb", ["-s", serial, "uninstall", packageName], {
cwd: root,
encoding: "utf8",
});
if (result.error) throw result.error;
if (result.status === 0) {
if (result.stdout) process.stdout.write(result.stdout);
return;
}
const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
if (/not installed/i.test(output)) {
console.log(`[info] ${packageName} was not installed`);
return;
}
// OEM apps in /system cannot be uninstalled from the system partition. For a
// clean, per-user replacement, Package Manager can remove that system app
// for user 0. This also clears its user data.
if (/DELETE_FAILED_INTERNAL_ERROR|unknown system package/i.test(output)) {
const perUserResult = spawnSync(
"adb",
["-s", serial, "shell", "pm", "uninstall", "--user", "0", packageName],
{ cwd: root, encoding: "utf8" },
);
if (perUserResult.error) throw perUserResult.error;
if (perUserResult.status === 0) {
if (perUserResult.stdout) process.stdout.write(perUserResult.stdout);
console.log(`[info] Removed system app ${packageName} for user 0`);
return;
}
const perUserOutput = `${perUserResult.stdout ?? ""}${perUserResult.stderr ?? ""}`;
if (/not installed/i.test(perUserOutput)) {
console.log(`[info] ${packageName} was not installed for user 0`);
return;
}
if (perUserOutput) process.stderr.write(perUserOutput);
}
if (output) process.stderr.write(output);
throw new Error(`Unable to remove ${packageName}`);
}
function requireConnectedDevice(serial: string): void {
const result = spawnSync("adb", ["-s", serial, "get-state"], {
cwd: root,
encoding: "utf8",
});
if (result.error) throw result.error;
if (result.status !== 0 || result.stdout.trim() !== "device")
throw new Error(`ADB device ${serial} is not connected and authorized`);
}
const serial = value("--serial");
if (!serial)
throw new Error("Specify the Xiaomi target with --serial <adb-serial>");
if (!args.includes("--confirm-reinstall"))
throw new Error("Refusing to replace an app without --confirm-reinstall");
requireConnectedDevice(serial);
run("bun", ["run", profile.patchScript]);
const apk =
value("--apk") ?? path.join(root, "outputs", `${app}_patched-signed.apk`);
if (!existsSync(apk)) {
throw new Error(
`Signed APK is missing at ${path.join("outputs", `${app}_patched-signed.apk`)}`,
);
}
// Android can reject an uninstall of an already-patched package with
// DELETE_FAILED_INTERNAL_ERROR, even though `adb install -r` updates the same
// package normally. Keep the patched app in place and only remove the original
// package when its unchanged provider authority would conflict with the patch.
if (profile.removeLegacy) {
uninstallIfPresent(serial, profile.legacyPackage);
} else {
console.log(`[info] Keeping protected OEM package ${profile.legacyPackage}`);
}
run("adb", ["-s", serial, "install", "-r", "--no-incremental", apk]);
console.log(
`[ok] Installed ${path.basename(apk)}${profile.legacySystemImage ? ` alongside protected OEM system image ${profile.legacyPackage}` : profile.removeLegacy ? ` after replacing ${profile.legacyPackage}` : " as a side-by-side update"}.`,
);