import * as path from "path"; import { readFile, writeFile, copyFile, mkdir } from "fs/promises"; import { fileURLToPath, pathToFileURL } from "url"; import open from "open"; import { get } from "httpie/dist/httpie.mjs"; import { generateV8Log } from "v8-deopt-generate-log"; import { parseV8Log, groupByFile } from "v8-deopt-parser"; import { determineCommonRoot } from "./determineCommonRoot.js"; // TODO: Replace with import.meta.resolve when stable import { createRequire } from "module"; // @ts-ignore const __dirname = path.dirname(fileURLToPath(import.meta.url)); const templatePath = path.join(__dirname, "template.html"); /** * @param {import('v8-deopt-parser').PerFileV8DeoptInfo["files"]} deoptInfo * @returns {Promise<Record<string, import('v8-deopt-webapp/src/index').V8DeoptInfoWithSources>>} */ async function addSources(deoptInfo) { const files = Object.keys(deoptInfo); const root = determineCommonRoot(files); /** @type {Record<string, import('v8-deopt-webapp/src/index').V8DeoptInfoWithSources>} */ const result = Object.create(null); for (let file of files) { let srcPath; let src, srcError; if (file.startsWith("https://") || file.startsWith("http://")) { try { srcPath = file; const { data } = await get(file); src = data; } catch (e) { srcError = e; } } else { let filePath = file; if (file.startsWith("file://")) { // Convert Linux-like file URLs for Windows and assume C: root. Useful for testing if ( process.platform == "win32" && !file.match(/^file:\/\/\/[a-zA-z]:/) ) { filePath = fileURLToPath(file.replace(/^file:\/\/\//, "file:///C:/")); } else { filePath = fileURLToPath(file); } } if (path.isAbsolute(filePath)) { try { srcPath = filePath; src = await readFile(filePath, "utf8"); } catch (e) { srcError = e; } } else { srcError = new Error("File path is not absolute"); } } const relativePath = root ? file.slice(root.length) : file; if (srcError) { result[file] = { ...deoptInfo[file], relativePath, srcPath, srcError: srcError.toString(), }; } else { result[file] = { ...deoptInfo[file], relativePath, srcPath, src, }; } } return result; } /** * @param {string} srcFile * @param {import('.').Options} options */ export default async function run(srcFile, options) { let logFilePath; if (srcFile) { console.log("Running and generating log..."); logFilePath = await generateV8Log(srcFile, { logFilePath: path.join(options.out, "v8.log"), browserTimeoutMs: options.timeout, traceMaps: !options["skip-maps"], }); } else if (options.input) { logFilePath = path.isAbsolute(options.input) ? options.input : path.join(process.cwd(), options.input); } else { throw new Error( 'Either a file/url to generate a log or the "--input" flag pointing to a v8.log must be provided' ); } // Ensure output directory exists await mkdir(options.out, { recursive: true }); console.log("Parsing log..."); const logContents = await readFile(logFilePath, "utf8"); // New IC format has 10 values instead of 9 const hasNewIcFormat = /\w+IC(,.*){10}/.test(logContents); const rawDeoptInfo = await parseV8Log(logContents, { keepInternals: options["keep-internals"], hasNewIcFormat, }); console.log("Adding sources..."); // Group DeoptInfo by files and extend the files data with sources const groupDeoptInfo = groupByFile(rawDeoptInfo); const deoptInfo = { ...groupDeoptInfo, files: await addSources(groupDeoptInfo.files), }; const deoptInfoString = JSON.stringify(deoptInfo, null, 2); const jsContents = `window.V8Data = ${deoptInfoString};`; await writeFile(path.join(options.out, "v8-data.js"), jsContents, "utf8"); console.log("Generating webapp..."); const template = await readFile(templatePath, "utf8"); const indexPath = path.join(options.out, "index.html"); await writeFile(indexPath, template, "utf8"); // @ts-ignore const require = createRequire(import.meta.url); const webAppIndexPath = require.resolve("v8-deopt-webapp"); const webAppStylesPath = webAppIndexPath.replace(/.js$/g, ".css"); await copyFile(webAppIndexPath, path.join(options.out, "v8-deopt-webapp.js")); await copyFile( webAppStylesPath, path.join(options.out, "v8-deopt-webapp.css") ); if (options.open) { await open(pathToFileURL(indexPath).toString(), { url: true }); console.log( `Done! Opening ${path.join(options.out, "index.html")} in your browser...` ); } else { console.log( `Done! Open ${path.join(options.out, "index.html")} in your browser.` ); } }