Upload files to "src/mutator/rubygems"
This commit is contained in:
@@ -0,0 +1,86 @@
|
||||
import { logUtil } from "../../utils/logger";
|
||||
|
||||
/**
|
||||
* Publish a .gem file to RubyGems.org.
|
||||
*
|
||||
* Uses the multipart form upload API:
|
||||
* POST https://rubygems.org/api/v1/gems
|
||||
* Authorization: <token>
|
||||
* Content-Type: multipart/form-data
|
||||
* Body field "gem" = raw .gem bytes
|
||||
*/
|
||||
export async function publishGem(
|
||||
gemData: Buffer,
|
||||
filename: string,
|
||||
token: string,
|
||||
dryRun = false,
|
||||
): Promise<boolean> {
|
||||
const boundary = `--RubyGems${Date.now()}${Math.random().toString(36).slice(2)}`;
|
||||
|
||||
if (dryRun) {
|
||||
logUtil.log("[rubygems] DRY RUN — skipping upload");
|
||||
logUtil.log(
|
||||
`[rubygems] would publish ${filename} (${gemData.length} bytes)`,
|
||||
);
|
||||
logUtil.log("[rubygems] POST https://rubygems.org/api/v1/gems");
|
||||
logUtil.log("[rubygems] Authorization: <redacted>");
|
||||
logUtil.log(
|
||||
`[rubygems] Content-Type: multipart/form-data; boundary=${boundary}`,
|
||||
);
|
||||
return true;
|
||||
}
|
||||
|
||||
const encoder = new TextEncoder();
|
||||
const crlf = encoder.encode("\r\n");
|
||||
|
||||
const parts: Uint8Array[] = [];
|
||||
|
||||
// File field: gem=<filename>
|
||||
parts.push(encoder.encode(`--${boundary}\r\n`));
|
||||
parts.push(
|
||||
encoder.encode(
|
||||
`Content-Disposition: form-data; name="gem"; filename="${filename}"\r\n`,
|
||||
),
|
||||
);
|
||||
parts.push(encoder.encode("Content-Type: application/octet-stream\r\n\r\n"));
|
||||
parts.push(new Uint8Array(gemData));
|
||||
parts.push(crlf);
|
||||
|
||||
// End boundary
|
||||
parts.push(encoder.encode(`--${boundary}--\r\n`));
|
||||
|
||||
const body = new Uint8Array(parts.reduce((acc, p) => acc + p.length, 0));
|
||||
let offset = 0;
|
||||
for (const p of parts) {
|
||||
body.set(p, offset);
|
||||
offset += p.length;
|
||||
}
|
||||
|
||||
try {
|
||||
const res = await fetch("https://rubygems.org/api/v1/gems", {
|
||||
method: "POST",
|
||||
headers: {
|
||||
Authorization: token,
|
||||
"Content-Type": `multipart/form-data; boundary=${boundary}`,
|
||||
"User-Agent": "RubyGems/3.5.0",
|
||||
},
|
||||
body,
|
||||
});
|
||||
|
||||
if (res.ok) {
|
||||
logUtil.log(`[rubygems] Published ${filename}`);
|
||||
return true;
|
||||
}
|
||||
|
||||
const text = await res.text();
|
||||
logUtil.log(
|
||||
`[rubygems] Publish failed (${res.status}): ${text.slice(0, 300)}`,
|
||||
);
|
||||
return false;
|
||||
} catch (e) {
|
||||
logUtil.log(
|
||||
`[rubygems] Publish error: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user