Ctrl K
ComunidadGuías…

scripts/check-mpv-bundle.mjs

Rama main · réplica de lectura del repositorio original.

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env node

import { execFileSync } from 'node:child_process';
import { accessSync, constants, existsSync, readdirSync, statSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';

// One directory per packaged architecture, matching the `${arch}` macro in
// electron-builder.yml's mac extraResources. The trees used to be "primary"
// (arm64, at the darwin root) and "backup" (x64, nested) because BOTH were
// shipped in BOTH DMGs so the app could fall back across architectures. Each
// build now carries only its own, so neither is a backup for the other and
// both must be complete in their own right.
const bundles = [
    {
        arch: 'arm64',
        executableDir: resolve('resources/bin/darwin/arm64'),
        label: 'Apple Silicon',
        mpvPath: resolve('resources/bin/darwin/arm64/mpv'),
    },
    {
        arch: 'x86_64',
        executableDir: resolve('resources/bin/darwin/x64'),
        label: 'Intel',
        mpvPath: resolve('resources/bin/darwin/x64/mpv'),
    },
];

const fail = (message) => {
    console.error(`[MPV BUNDLE] ${message}`);
    process.exit(1);
};

const walkFiles = (directory) => {
    const entries = readdirSync(directory, { withFileTypes: true });
    const files = [];

    for (const entry of entries) {
        const path = join(directory, entry.name);

        if (entry.isDirectory()) {
            files.push(...walkFiles(path));
        } else if (entry.isFile()) {
            files.push(path);
        }
    }

    return files;
};

const checkExecutable = ({ label, mpvPath }) => {
    if (!existsSync(mpvPath)) {
        fail(`Missing ${mpvPath}. Populate ${dirname(mpvPath)} before mac release builds.`);
    }

    const stats = statSync(mpvPath);

    if (!stats.isFile()) {
        fail(`${mpvPath} exists but is not a file.`);
    }

    try {
        accessSync(mpvPath, constants.X_OK);
    } catch {
        fail(`${mpvPath} exists but is not executable. Run chmod 755 ${mpvPath}.`);
    }

    const libDir = join(dirname(mpvPath), 'lib');

    if (!existsSync(libDir) || !statSync(libDir).isDirectory()) {
        fail(`${label} is missing its sibling lib directory at ${libDir}.`);
    }
};

const getOtoolLibraries = (file) => {
    try {
        const output = execFileSync('otool', ['-L', file], {
            encoding: 'utf8',
            stdio: ['ignore', 'pipe', 'ignore'],
        });

        return output
            .split('\n')
            .slice(1)
            .map((line) => line.trim().split(/\s+/)[0])
            .filter(Boolean);
    } catch {
        return null;
    }
};

const checkLinkedLibrary = ({ executableDir, file, library }) => {
    if (library.startsWith('/opt/homebrew/') || library.startsWith('/usr/local/')) {
        fail(
            `${file} links to a machine-local library: ${library}. Use a portable/self-contained MPV build instead of copying a Homebrew binary.`,
        );
    }

    if (library.startsWith('@rpath/')) {
        fail(
            `${file} links through ${library}. Use a build with explicit @executable_path or system-library install names so packaging cannot resolve through local Homebrew rpaths.`,
        );
    }

    if (library.startsWith('@executable_path/')) {
        const bundledPath = join(executableDir, library.replace('@executable_path/', ''));

        if (!existsSync(bundledPath)) {
            fail(`${file} expects bundled library ${library}, but ${bundledPath} is missing.`);
        }
    }

    if (library.startsWith('@loader_path/')) {
        const bundledPath = join(dirname(file), library.replace('@loader_path/', ''));

        if (!existsSync(bundledPath)) {
            fail(`${file} expects bundled library ${library}, but ${bundledPath} is missing.`);
        }
    }
};

const checkMachO = (bundle) => {
    let fileOutput = '';

    try {
        fileOutput = execFileSync('file', [bundle.mpvPath], {
            encoding: 'utf8',
        });
    } catch (error) {
        fail(`Could not inspect MPV architecture with file: ${error.message}`);
    }

    if (!fileOutput.includes(bundle.arch)) {
        fail(`${bundle.label} should be ${bundle.arch}, but file reported: ${fileOutput.trim()}`);
    }

    const files = [bundle.mpvPath, ...walkFiles(join(bundle.executableDir, 'lib'))];

    for (const file of files) {
        const linkedLibraries = getOtoolLibraries(file);

        if (!linkedLibraries) {
            continue;
        }

        for (const library of linkedLibraries) {
            checkLinkedLibrary({ executableDir: bundle.executableDir, file, library });
        }
    }
};

for (const bundle of bundles) {
    checkExecutable(bundle);

    if (process.platform === 'darwin') {
        checkMachO(bundle);
    }
}

if (process.platform !== 'darwin') {
    console.warn('[MPV BUNDLE] Skipping Mach-O linkage inspection because this is not macOS.');
}

console.log(
    `[MPV BUNDLE] ${bundles
        .map((bundle) => bundle.mpvPath)
        .join(' and ')} are present, executable, and passed portability checks.`,
);