This commit is contained in:
KingKDot
2026-06-24 23:40:38 -05:00
parent 94ef889d27
commit a51c465227
2 changed files with 71 additions and 3 deletions
@@ -6,6 +6,7 @@ import {
arePluginNeedsApproved,
compilePluginTypeScript,
computePluginNeedsHash,
ensurePluginExtracted,
loadPluginBundle,
type PluginState,
} from "./plugin-state-bundle";
@@ -70,6 +71,31 @@ describe("loadPluginBundle", () => {
expect(bundle.size).toBe(4);
});
test("keeps helper DLLs out of the native platform binary map", async () => {
const root = await createTempRoot();
const AdmZip = require("adm-zip");
const zip = new AdmZip();
zip.addFile("config.json", Buffer.from(JSON.stringify({
name: "Plink",
apiVersion: 2,
runtime: "native",
uiEntry: "src/ui.ts",
})));
zip.addFile("plink.html", Buffer.from("<div id=\"plink-root\"></div>"));
zip.addFile("plink.css", Buffer.from("#plink-root { display: block; }"));
zip.addFile("src/ui.ts", Buffer.from("document.body.dataset.plugin = 'plink';"));
zip.addFile("plink-windows-amd64.dll", Buffer.from("entry-dll"));
zip.addFile("plink-payload.dll", Buffer.from("helper-dll"));
zip.writeZip(join(root, "plink.zip"));
await ensurePluginExtracted(root, "plink", (name) => name);
const manifest = JSON.parse(await readFile(join(root, "plink", "manifest.json"), "utf-8"));
expect(manifest.binaries["windows-amd64"]).toBe("plink-windows-amd64.dll");
const bundle = await loadPluginBundle(root, "plink", async () => {}, "Windows 11", "amd64");
expect(bundle.binaryPath).toBe(join(root, "plink", "plink-windows-amd64.dll"));
});
test("computes stable need hashes and invalidates changed approvals", () => {
const state: PluginState = {
enabled: {},
@@ -149,11 +149,17 @@ export async function ensurePluginExtracted(
throw new Error(`Invalid plugin bundle: ${safeId} (missing .html, .css, and either .js or src/ui.ts)`);
}
const binariesMap: Record<string, string> = {};
for (const [filename, data] of nativeBinaries) {
await fs.writeFile(path.join(pluginDir, filename), data);
}
const binariesMap: Record<string, string> = {};
applyConfiguredNativeBinaries(binariesMap, extraConfig, nativeBinaries);
const nativeBinaryNames = Array.from(nativeBinaries.keys())
.sort((a, b) => nativeBinaryPriority(a, safeId) - nativeBinaryPriority(b, safeId) || a.localeCompare(b));
for (const filename of nativeBinaryNames) {
const platformKey = derivePlatformKey(filename);
if (platformKey) {
if (platformKey && !binariesMap[platformKey]) {
binariesMap[platformKey] = filename;
}
}
@@ -203,6 +209,7 @@ export async function ensurePluginExtracted(
runtime,
version: extraConfig.version || "1.0.0",
description: extraConfig.description,
...(typeof extraConfig.binary === "string" && extraConfig.binary && { binary: extraConfig.binary }),
...(runtime === "wasm" && { wasm: String(extraConfig.wasm || wasmEntry?.filename || binariesMap["wasm32-wasi"] || "") }),
...(needs && { needs }),
binaries: binariesMap,
@@ -345,7 +352,7 @@ export async function loadPluginBundle(
const files = await fs.readdir(dir);
const archRegex = /-(linux|darwin|windows|freebsd)-(amd64|arm64|arm|386)\.(so|dll|dylib)$/i;
const found = files.find((f) => {
const found = files.find((f: string) => {
const m = f.match(archRegex);
if (m) {
return (
@@ -733,3 +740,38 @@ function derivePlatformKey(filename: string): string {
if (lower.endsWith(".so")) return "linux-amd64";
return "";
}
function applyConfiguredNativeBinaries(
binariesMap: Record<string, string>,
config: any,
nativeBinaries: Map<string, Buffer>,
): void {
if (config?.binaries && typeof config.binaries === "object") {
for (const [platformKey, filename] of Object.entries(config.binaries)) {
if (typeof platformKey !== "string" || typeof filename !== "string") continue;
const base = path.basename(filename);
if (!nativeBinaries.has(base)) continue;
binariesMap[platformKey.toLowerCase()] = base;
}
}
if (typeof config?.binary === "string" && config.binary) {
const base = path.basename(config.binary);
if (!nativeBinaries.has(base)) return;
const platformKey = derivePlatformKey(base);
if (platformKey) {
binariesMap[platformKey] = base;
}
}
}
function nativeBinaryPriority(filename: string, pluginId: string): number {
const escapedId = pluginId.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
if (new RegExp(`^${escapedId}-(linux|darwin|windows|freebsd)-(amd64|arm64|arm|386)\\.(so|dll|dylib)$`, "i").test(filename)) {
return 0;
}
if (/-(linux|darwin|windows|freebsd)-(amd64|arm64|arm|386)\.(so|dll|dylib)$/i.test(filename)) {
return 1;
}
return 2;
}