packages/nucleus/src/data/update-common-data.cjs
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);
}