项目文件夹

文件
wehub-resource-sync 070959e133
landing-page-staging / Deploy landing page to staging (push) Has been skipped
landing-page-ci / Validate landing page (push) Failing after 4s
visual-baseline / Capture visual baselines (push) Has been cancelled
bake-plugin-previews / Bake plugin previews (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:00:47 +08:00

128 行
4.2 KiB
JavaScript
可执行文件

#!/usr/bin/env node
import fs from "node:fs";
import path from "node:path";
import crypto from "node:crypto";
import { loadPlaywright, launchChromium } from "./lib/playwright-loader.mjs";
function usage() {
console.log(`Usage:
node scripts/network-capture.mjs --url <url> --out RECON/network [--label original] [--wait 5000] [--max-bytes 1000000]
Captures browser requests and saves JSON/text XHR/fetch responses as fixtures.
`);
}
function parseArgs(argv) {
const out = { url: "", outDir: "RECON/network", label: "site", waitMs: 5000, maxBytes: 1_000_000 };
for (let i = 0; i < argv.length; i += 1) {
const arg = argv[i];
if (arg === "--help" || arg === "-h") out.help = true;
else if (arg === "--url") out.url = argv[++i] || "";
else if (arg === "--out") out.outDir = argv[++i] || "RECON/network";
else if (arg === "--label") out.label = argv[++i] || "site";
else if (arg === "--wait") out.waitMs = Number(argv[++i] || "5000");
else if (arg === "--max-bytes") out.maxBytes = Number(argv[++i] || "1000000");
else throw new Error(`Unexpected argument: ${arg}`);
}
return out;
}
function safeFixtureName(url, contentType) {
const parsed = new URL(url);
const cleanPath = parsed.pathname.replace(/[^a-z0-9._-]+/gi, "-").replace(/^-+|-+$/g, "").slice(0, 80) || "response";
const hash = crypto.createHash("sha1").update(url).digest("hex").slice(0, 10);
const ext = contentType.includes("json") ? ".json" : ".txt";
return `${cleanPath}-${hash}${ext}`;
}
function shouldSave(resourceType, contentType) {
if (!["xhr", "fetch"].includes(resourceType)) return false;
return /json|text|graphql|javascript/i.test(contentType || "");
}
try {
const args = parseArgs(process.argv.slice(2));
if (args.help || !args.url) {
usage();
process.exit(args.help ? 0 : 1);
}
const outDir = path.resolve(args.outDir);
const fixturesDir = path.join(outDir, "fixtures");
fs.mkdirSync(fixturesDir, { recursive: true });
const { chromium } = loadPlaywright();
const browser = await launchChromium(chromium);
const page = await browser.newPage({ viewport: { width: 1440, height: 900 } });
const requests = [];
const responses = [];
page.on("request", (request) => {
requests.push({
url: request.url(),
method: request.method(),
resourceType: request.resourceType(),
headers: request.headers(),
postData: request.postData() || "",
});
});
page.on("response", async (response) => {
const request = response.request();
const headers = response.headers();
const contentType = headers["content-type"] || "";
const entry = {
url: response.url(),
status: response.status(),
ok: response.ok(),
method: request.method(),
resourceType: request.resourceType(),
contentType,
fixture: "",
error: "",
};
if (shouldSave(request.resourceType(), contentType)) {
try {
const body = await response.body();
if (body.length <= args.maxBytes) {
const file = path.join(fixturesDir, safeFixtureName(response.url(), contentType));
fs.writeFileSync(file, body);
entry.fixture = path.relative(outDir, file);
entry.bytes = body.length;
} else {
entry.error = `body too large: ${body.length}`;
}
} catch (error) {
entry.error = error.message;
}
}
responses.push(entry);
});
await page.goto(args.url, { waitUntil: "domcontentloaded", timeout: 45000 });
await page.waitForLoadState("networkidle", { timeout: args.waitMs }).catch(() => {});
if (args.waitMs > 0) await page.waitForTimeout(args.waitMs);
await browser.close();
const manifest = {
label: args.label,
url: args.url,
capturedAt: new Date().toISOString(),
requestCount: requests.length,
responseCount: responses.length,
fixtureCount: responses.filter((response) => response.fixture).length,
requests,
responses,
};
const manifestFile = path.join(outDir, `${args.label}-network.json`);
fs.writeFileSync(manifestFile, `${JSON.stringify(manifest, null, 2)}\n`);
console.log(manifestFile);
} catch (error) {
console.error(`network-capture failed: ${error.message}`);
process.exit(1);
}