Files
2026-07-13 13:08:41 +08:00

467 lines
12 KiB
JavaScript

const fs = require("fs");
const os = require("os");
const path = require("path");
const { spawnSync } = require("child_process");
const {
Browser,
computeExecutablePath,
detectBrowserPlatform,
install,
} = require("@puppeteer/browsers");
const buildId = (process.env.EXPORT_CHROME_BUILD_ID || "146.0.7680.76").trim();
const cacheDir = path.join(__dirname, "..", "resources", "chromium");
const manifestPath = path.join(cacheDir, "presenton-runtime.json");
const windowsRequiredRuntimeFiles = [
"chrome.dll",
"chrome_100_percent.pak",
"chrome_200_percent.pak",
"icudtl.dat",
"resources.pak",
"v8_context_snapshot.bin",
path.join("locales", "en-US.pak"),
];
function getRevisionDir(platform) {
return path.join(cacheDir, Browser.CHROME, `${platform}-${buildId}`);
}
function fileLooksPresent(filePath) {
try {
const stat = fs.statSync(filePath);
return stat.isFile() && stat.size > 0;
} catch {
return false;
}
}
function validateWindowsRuntimeLayout(executablePath) {
if (!fileLooksPresent(executablePath)) {
return { ok: false, reason: `Chromium executable is missing: ${executablePath}` };
}
const chromeDir = path.dirname(executablePath);
const missingFiles = windowsRequiredRuntimeFiles.filter(
(fileName) => !fileLooksPresent(path.join(chromeDir, fileName))
);
if (missingFiles.length > 0) {
return {
ok: false,
reason: `Chromium runtime layout is incomplete. Missing: ${missingFiles.join(", ")}`,
};
}
return { ok: true };
}
function runtimeLooksComplete(executablePath) {
if (!fs.existsSync(executablePath)) {
return false;
}
if (process.platform === "darwin") {
return macChromiumBundleLooksCodeSignReady(executablePath);
}
if (process.platform !== "win32") {
return true;
}
return validateWindowsRuntimeLayout(executablePath).ok;
}
function removeProbeProfile(profileDir) {
try {
fs.rmSync(profileDir, {
recursive: true,
force: true,
maxRetries: 10,
retryDelay: 100,
});
} catch (error) {
console.warn(
`[Chromium] Could not remove temporary probe profile ${profileDir}: ${error.message}`,
);
}
}
function validateExecutable(executablePath) {
if (process.platform === "win32") {
// Windows Chrome startup can be slow or session-dependent during packaging.
// Runtime export still probes launchability, so the build validates the bundle.
return validateWindowsRuntimeLayout(executablePath);
}
if (!runtimeLooksComplete(executablePath)) {
return { ok: false, reason: "Chromium runtime layout is incomplete." };
}
if (process.platform === "darwin") {
const appBundlePath = findAppBundle(executablePath);
const result = spawnSync(
"codesign",
["--verify", "--deep", "--strict", appBundlePath],
{
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
},
);
if (result.status !== 0) {
const detail = result.error?.message || result.stderr || `status=${result.status}`;
return { ok: false, reason: detail.trim() };
}
return { ok: true };
}
if (process.platform === "linux") {
const result = spawnSync(executablePath, ["--version"], {
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
});
if (result.error || result.status !== 0) {
const detail = result.error?.message || result.stderr || `status=${result.status}`;
return { ok: false, reason: detail.trim() };
}
if (!/chrome|chromium/i.test(result.stdout || "")) {
return { ok: false, reason: "Chromium version probe produced unexpected output." };
}
return { ok: true };
}
const profileDir = fs.mkdtempSync(path.join(os.tmpdir(), "presenton-chromium-probe-"));
try {
const result = spawnSync(
executablePath,
[
"--headless=new",
"--disable-gpu",
"--disable-dev-shm-usage",
"--disable-extensions",
"--disable-crash-reporter",
"--no-first-run",
"--no-sandbox",
"--password-store=basic",
"--use-mock-keychain",
`--user-data-dir=${profileDir}`,
"--dump-dom",
"about:blank",
],
{
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
timeout: 15000,
windowsHide: process.platform === "win32",
},
);
if (result.status !== 0) {
const detail = result.error?.message || result.stderr || `status=${result.status}`;
return { ok: false, reason: detail.trim() };
}
if (!(result.stdout || "").toLowerCase().includes("<html")) {
return { ok: false, reason: "Chromium probe did not produce HTML output." };
}
return { ok: true };
} finally {
removeProbeProfile(profileDir);
}
}
function writeManifest(platform, executablePath) {
fs.writeFileSync(
manifestPath,
JSON.stringify(
{
browser: Browser.CHROME,
buildId,
platform,
nodePlatform: process.platform,
arch: process.arch,
executable: path.relative(cacheDir, executablePath),
createdAt: new Date().toISOString(),
},
null,
2,
),
);
}
function findAppBundle(executablePath) {
let current = path.dirname(executablePath);
while (true) {
if (current.endsWith(".app")) {
return current;
}
const parent = path.dirname(current);
if (parent === current) {
return null;
}
current = parent;
}
}
function isSymlink(filePath) {
try {
return fs.lstatSync(filePath).isSymbolicLink();
} catch {
return false;
}
}
function macChromiumFrameworkPath(appBundlePath) {
return path.join(
appBundlePath,
"Contents",
"Frameworks",
"Google Chrome for Testing Framework.framework",
);
}
function macFrameworkLayoutLooksValid(frameworkPath) {
if (!fs.existsSync(frameworkPath)) {
return false;
}
const entries = fs.readdirSync(frameworkPath, { withFileTypes: true });
for (const entry of entries) {
if (entry.name === "Versions") {
continue;
}
if (!isSymlink(path.join(frameworkPath, entry.name))) {
return false;
}
}
return isSymlink(path.join(frameworkPath, "Versions", "Current"));
}
function macChromiumBundleLooksCodeSignReady(executablePath) {
const appBundlePath = findAppBundle(executablePath);
if (!appBundlePath) {
return false;
}
return macFrameworkLayoutLooksValid(macChromiumFrameworkPath(appBundlePath));
}
function canonicalizeFrameworkSymlinkTargets(frameworkPath) {
if (!fs.existsSync(frameworkPath)) {
return 0;
}
const versionsPath = path.join(frameworkPath, "Versions");
const currentPath = path.join(versionsPath, "Current");
if (!fs.existsSync(versionsPath)) {
return 0;
}
let rewritten = 0;
const versionEntries = fs
.readdirSync(versionsPath, { withFileTypes: true })
.filter((entry) => entry.name !== "Current" && entry.isDirectory())
.map((entry) => entry.name);
const currentTarget =
isSymlink(currentPath) &&
fs.existsSync(path.resolve(versionsPath, fs.readlinkSync(currentPath)))
? fs.readlinkSync(currentPath)
: versionEntries[0];
if (!currentTarget) {
return 0;
}
if (!isSymlink(currentPath) || fs.readlinkSync(currentPath) !== currentTarget) {
fs.rmSync(currentPath, { recursive: true, force: true });
fs.symlinkSync(currentTarget, currentPath);
rewritten += 1;
}
const topLevelSymlinks = [
"Google Chrome for Testing Framework",
"Helpers",
"Libraries",
"Resources",
];
for (const name of topLevelSymlinks) {
const linkPath = path.join(frameworkPath, name);
const linkTarget = ["Versions", "Current", name].join("/");
if (!fs.existsSync(path.resolve(frameworkPath, linkTarget))) {
continue;
}
if (isSymlink(linkPath) && fs.readlinkSync(linkPath) === linkTarget) {
continue;
}
fs.rmSync(linkPath, { recursive: true, force: true });
fs.symlinkSync(linkTarget, linkPath);
rewritten += 1;
}
return rewritten;
}
function canonicalizeMacFrameworkSymlinks(rootDir) {
if (!fs.existsSync(rootDir)) {
return 0;
}
const stack = [rootDir];
let rewritten = 0;
while (stack.length) {
const current = stack.pop();
const entries = fs.readdirSync(current, { withFileTypes: true });
for (const entry of entries) {
const fullPath = path.join(current, entry.name);
if (!entry.isDirectory()) {
continue;
}
if (entry.name === "Google Chrome for Testing Framework.framework") {
rewritten += canonicalizeFrameworkSymlinkTargets(fullPath);
continue;
}
if (entry.name.endsWith(".framework")) {
continue;
}
stack.push(fullPath);
}
}
return rewritten;
}
function normalizeMacBundleForPackaging(executablePath) {
const appBundlePath = findAppBundle(executablePath);
if (!appBundlePath || !fs.existsSync(appBundlePath)) {
return 0;
}
const frameworkPath = macChromiumFrameworkPath(appBundlePath);
const rewritten = canonicalizeFrameworkSymlinkTargets(frameworkPath);
if (rewritten > 0) {
console.log(
`[Chromium] Canonicalized ${rewritten} framework symlinks for App Store packaging.`,
);
}
adHocSignMacBundle(appBundlePath);
return rewritten;
}
function adHocSignMacBundle(appBundlePath) {
if (process.platform !== "darwin") {
return;
}
const result = spawnSync(
"codesign",
["--force", "--deep", "--sign", "-", "--timestamp=none", appBundlePath],
{
stdio: ["ignore", "pipe", "pipe"],
encoding: "utf8",
},
);
if (result.status !== 0) {
throw new Error(
`Failed to re-sign normalized Chromium bundle: ${(result.stderr || result.stdout || "").trim()}`,
);
}
console.log(`[Chromium] Re-signed normalized macOS bundle: ${appBundlePath}`);
}
function normalizeBundledMacChromiumForPackaging(rootDir = cacheDir) {
const rewritten = canonicalizeMacFrameworkSymlinks(rootDir);
if (rewritten > 0) {
console.log(
`[Chromium] Canonicalized ${rewritten} bundled macOS framework symlinks before packaging.`,
);
}
return rewritten;
}
function removeIncompleteRuntime(platform, executablePath) {
if (validateExecutable(executablePath).ok) {
return;
}
const revisionDir = getRevisionDir(platform);
if (!fs.existsSync(revisionDir)) {
return;
}
console.log(
`[Chromium] Removing incomplete runtime before download: ${revisionDir}`
);
fs.rmSync(revisionDir, { recursive: true, force: true });
}
async function main() {
if (process.env.SKIP_BUNDLED_CHROMIUM === "1") {
console.log("[Chromium] SKIP_BUNDLED_CHROMIUM=1; leaving runtime unbundled.");
return;
}
const platform = detectBrowserPlatform();
if (!platform) {
throw new Error(`Unsupported platform for bundled Chromium: ${process.platform}-${process.arch}`);
}
const options = {
browser: Browser.CHROME,
buildId,
cacheDir,
platform,
};
const executablePath = computeExecutablePath(options);
if (runtimeLooksComplete(executablePath)) {
if (!validateExecutable(executablePath).ok) {
removeIncompleteRuntime(platform, executablePath);
} else {
normalizeMacBundleForPackaging(executablePath);
if (!validateExecutable(executablePath).ok) {
removeIncompleteRuntime(platform, executablePath);
} else {
writeManifest(platform, executablePath);
console.log(`[Chromium] Bundled runtime already exists: ${executablePath}`);
return;
}
}
}
if (validateExecutable(executablePath).ok) {
writeManifest(platform, executablePath);
return;
}
removeIncompleteRuntime(platform, executablePath);
fs.mkdirSync(cacheDir, { recursive: true });
console.log(`[Chromium] Downloading Chrome for Testing ${buildId} into ${cacheDir}`);
await install({
...options,
downloadProgressCallback(downloadedBytes, totalBytes) {
if (totalBytes <= 0) return;
const percent = Math.floor((downloadedBytes / totalBytes) * 100);
process.stdout.write(`\r[Chromium] ${percent}%`);
},
});
process.stdout.write("\n");
normalizeMacBundleForPackaging(executablePath);
const validation = validateExecutable(executablePath);
if (!validation.ok) {
throw new Error(
`Chromium install finished, but the launch probe failed: ${validation.reason}\n${executablePath}`,
);
}
writeManifest(platform, executablePath);
console.log(`[Chromium] Bundled runtime ready: ${executablePath}`);
}
if (require.main === module) {
main().catch((error) => {
console.error(error);
process.exit(1);
});
}
module.exports = {
adHocSignMacBundle,
canonicalizeMacFrameworkSymlinks,
normalizeBundledMacChromiumForPackaging,
normalizeMacBundleForPackaging,
};