leonitousconforti/tinyburg

View on GitHub
packages/nucleus/src/data/update-common-data.cjs

Summary

Maintainability
Test Coverage
const path = require("node:path");
const Effect = require("effect/Effect");
const HashMap = require("effect/HashMap");
const Option = require("effect/Option");
const MobyApi = require("the-moby-effect/Moby");
const NodeContext = require("@effect/platform-node/NodeContext");

/**
 * This script is automatically ran by heft during before typescript
 * transpilation and will regenerate the data files in this directory.
 */

const fs = require("node:fs/promises");
const prettier = require("prettier");

// Banner to put at the top of every data file
const sourceCodeBanner = `/**
 * This file was auto-generated by a frida agent
 *
 * Generated by:
 * __filename
 *
 * With TinyTower version: __version
 *
 * On: __date
 */
`;

module.exports.runAsync = async () => {
    const apks = await import("@tinyburg/fount");
    const { architect, cleanup } = await import("@tinyburg/architect");
    const { getSemanticVersionsByRelativeVersions } = await import("@tinyburg/fount/versions");
    const { bootstrapAgentOnRemote, cleanupAgent, GetterAgents } = await import("@tinyburg/insight");

    // Find editorconfig and prettier formatting files
    const prettierOptions = {
        parser: "typescript",
        ...(await prettier.resolveConfig(process.cwd(), { editorconfig: true })),
    };

    // To know where to put the generated source code
    const AgentOutputFileMap = {
        "./bitbook-posts.ts": GetterAgents.BitbookAgent,
        // "./bitizen.ts": GetterAgents.BitizenAgent,
        "./costumes.ts": GetterAgents.CostumeAgent,
        "./elevators.ts": GetterAgents.ElevatorAgent,
        "./floors.ts": GetterAgents.FloorAgent,
        "./missions.ts": GetterAgents.MissionAgent,
        "./pets.ts": GetterAgents.PetAgent,
        "./roofs.ts": GetterAgents.RoofAgent,
    };

    const latestVersion = await getSemanticVersionsByRelativeVersions(apks.Games.TinyTower)
        .pipe(Effect.map(HashMap.get("latest version")))
        .pipe(Effect.map(Option.getOrThrow))
        .pipe(Effect.map(({ semanticVersion }) => semanticVersion))
        .pipe(Effect.runPromise);

    console.log(latestVersion);
    const alreadyGenerateForLatestVersion = await Promise.all(
        Object.keys(AgentOutputFileMap).map(async (file) => {
            const outputPath = path.join(__dirname, file);
            const contents = await fs.readFile(outputPath);
            return contents.toString().includes(`With TinyTower version: ${latestVersion}`);
        })
    );

    if (alreadyGenerateForLatestVersion.every((_) => _ === true)) {
        return;
    }

    // Create a container to extract information from
    const apk = await apks
        .loadApk(apks.Games.TinyTower)
        .pipe(Effect.provide(NodeContext.layer))
        .pipe(Effect.runPromise);

    const { containerEndpoints, emulatorContainer, installApk } = await architect()
        .pipe(Effect.provide(NodeContext.layer))
        .pipe(Effect.provide(MobyApi.fromDockerHostEnvironmentVariable))
        .pipe(Effect.runPromise);

    await installApk(apk)
        .pipe(Effect.provide(NodeContext.layer))
        .pipe(Effect.provide(MobyApi.fromDockerHostEnvironmentVariable))
        .pipe(Effect.runPromise);

    for (const [outputDestination, agent] of Object.entries(AgentOutputFileMap)) {
        const { runAgentMain, ...agentDetails } = await bootstrapAgentOnRemote(
            agent,
            `host.docker.internal${containerEndpoints[0].fridaAddress}`
        );

        const result = await runAgentMain();
        await cleanupAgent(agentDetails);
        const formattedSource = await prettier.format(result, prettierOptions);
        const version = result.match(/TinyTower version: ([\d.]+)/gm)?.[0];
        const cleanedSource = formattedSource.replaceAll(/\/\/ TinyTower version: ([\d.]+)/gm, "");

        const formattedBanner = sourceCodeBanner
            .replace("__filename", agent.agentFile)
            .replace("__date", new Date().toUTCString())
            .replace("__version", version?.split(":")[1]?.trim() || "unknown");

        const outputPath = path.join(__dirname, outputDestination);
        await fs.writeFile(outputPath, formattedBanner + cleanedSource);
    }

    await cleanup({ emulatorContainer })
        .pipe(Effect.provide(NodeContext.layer))
        .pipe(Effect.provide(MobyApi.fromDockerHostEnvironmentVariable))
        .pipe(Effect.runPromise);
};

if (require.main === module) {
    module.exports.runAsync().catch(console.error);
}