Ctrl K
ComunidadGuías…

scripts/generatePodUsage.ts

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

Archivo original ↗Preguntar a KiBot
#!/usr/bin/env bun
/**
 * Regenerates ios/pod-usage.json from the installed Pods directory.
 *
 * checkUnusedPods.ts cannot resolve a pod's module name on its own: the name a
 * pod is imported under is decided by its podspec, not by its Podfile entry
 * (GzipSwift exports a module called Gzip), and Objective-C pods that ship no
 * module are reachable only through their public headers. Both live under
 * ios/Pods, which is gitignored, so they are resolved here once and committed to
 * the manifest.
 *
 * Run this after `pod install` whenever the Podfile changes, then review the
 * diff — a pod this script cannot find any reference for is written out as
 * `exempt` with an empty reason, which the check rejects until you fill it in.
 *
 * Usage: npx bun scripts/generatePodUsage.ts
 */

import fs from 'fs';
import path from 'path';

import type {PodUsageManifest} from './podUsageShared';

import {IOS_DIR, MANIFEST_PATH, countReferences, readDeclaredPods, readManifest, readSources, writeManifest} from './podUsageShared';

const TARGET_SUPPORT_FILES = path.join(IOS_DIR, 'Pods', 'Target Support Files');
const PUBLIC_HEADERS = path.join(IOS_DIR, 'Pods', 'Headers', 'Public');

function readModuleName(pod: string): string | undefined {
    const dir = path.join(TARGET_SUPPORT_FILES, pod);
    if (!fs.existsSync(dir)) {
        return undefined;
    }
    for (const entry of fs.readdirSync(dir)) {
        if (!entry.endsWith('.modulemap')) {
            continue;
        }
        const match = /^\s*(?:framework\s+)?module\s+([A-Za-z0-9_]+)/m.exec(fs.readFileSync(path.join(dir, entry), 'utf8'));
        if (match) {
            return match[1];
        }
    }
    return undefined;
}

function readPublicHeaders(pod: string): string[] {
    const dir = path.join(PUBLIC_HEADERS, pod);
    if (!fs.existsSync(dir)) {
        return [];
    }
    const out: string[] = [];
    const walk = (current: string) => {
        for (const entry of fs.readdirSync(current, {withFileTypes: true})) {
            const full = path.join(current, entry.name);
            if (entry.isDirectory()) {
                walk(full);
            } else if (entry.name.endsWith('.h')) {
                out.push(entry.name);
            }
        }
    };
    walk(dir);
    return out.sort();
}

function main() {
    if (!fs.existsSync(TARGET_SUPPORT_FILES)) {
        console.error(`${TARGET_SUPPORT_FILES} is missing. Run \`bundle exec pod install\` in ios/ first.`);
        process.exit(1);
    }

    const sources = readSources();
    const previous: PodUsageManifest = fs.existsSync(MANIFEST_PATH) ? readManifest() : {};
    const manifest: PodUsageManifest = {};

    for (const pod of readDeclaredPods()) {
        const moduleName = readModuleName(pod) ?? pod.replaceAll(/[^A-Za-z0-9_]/g, '_');
        if (countReferences(sources, {usage: 'module', module: moduleName}) > 0) {
            manifest[pod] = {usage: 'module', module: moduleName};
            continue;
        }

        const headers = readPublicHeaders(pod);
        if (headers.length && countReferences(sources, {usage: 'headers', headers}, pod) > 0) {
            manifest[pod] = {usage: 'headers', headers};
            continue;
        }

        // Nothing in the sources reaches this pod. It is either dead or alive by a
        // route the sources do not show — a build phase, a linker flag, another pod
        // depending on it. Keep whatever reason was already recorded so a
        // regeneration does not wipe it.
        const existing = previous[pod];
        manifest[pod] = existing?.usage === 'exempt' ? existing : {usage: 'exempt', reason: '', reviewed: ''};
    }

    writeManifest(manifest);

    const blank = Object.entries(manifest).filter(([, entry]) => entry.usage === 'exempt' && !entry.reason);
    console.log(`Wrote ${Object.keys(manifest).length} pods to ${path.relative(process.cwd(), MANIFEST_PATH)}.`);
    if (blank.length) {
        console.log(`\n${blank.length} pod(s) need a reason before the check will pass:`);
        for (const [pod] of blank) {
            console.log(`  ${pod}`);
        }
    }
}

main();