UNPKG

frida-compile

Version:

Compile a Frida script comprised of one or more Node.js modules

174 lines (173 loc) 5.96 kB
#!/usr/bin/env node import chalk from "chalk"; import { program, Option } from "commander"; import frida from "frida"; import fs from "node:fs"; import path from "node:path"; import process from "node:process"; import readline from "node:readline"; import { performance } from "node:perf_hooks"; const styleReset = chalk.reset; const styleFile = chalk.cyan.bold; const styleLocation = chalk.yellow.bold; const styleCode = chalk.gray; const CATEGORY_STYLE = { error: chalk.red.bold, warning: chalk.yellow.bold, info: chalk.blueBright, suggestion: chalk.greenBright, }; async function main() { program .usage("[options]") .argument("<module>", "TypeScript/JavaScript module to compile") .option("-o, --output <file>", "write output to <file>", "-") .option("-w, --watch", "watch for changes and recompile", false) .option("-S, --no-source-maps", "omit source-maps", false) .option("-c, --compress", "minify code", false) .option("-v, --verbose", "be verbose", false) .addOption(new Option("-F, --output-format <format>", "desired output format") .choices(["unescaped", "hex-bytes", "c-string"]) .default("unescaped")) .addOption(new Option("-B, --bundle-format <format>", "desired bundle format") .choices(["esm", "iife"]) .default("esm")) .addOption(new Option("-T, --type-check <mode>", "desired type-checking mode") .choices(["full", "none"]) .default("full")); program.parse(); const opts = program.opts(); const projectRoot = process.cwd(); const entrypoint = program.args[0]; const outputPath = opts.output; const verbose = opts.watch || opts.verbose; const compilerOpts = { projectRoot, outputFormat: opts.outputFormat, bundleFormat: opts.bundleFormat, typeCheck: opts.typeCheck, sourceMaps: opts.sourceMaps ? frida.SourceMaps.Included : frida.SourceMaps.Omitted, compression: opts.compress ? frida.JsCompression.Terser : frida.JsCompression.None, }; let compilationStarted = null; const compiler = new frida.Compiler(); if (verbose) { compiler.starting.connect(onStarting); compiler.finished.connect(onFinished); } compiler.diagnostics.connect(onDiagnostics); if (opts.watch) { compiler.output.connect(onOutput); try { await compiler.watch(entrypoint, compilerOpts); } catch (e) { stop(); throw e; } process.on("SIGINT", stop); process.on("SIGTERM", stop); function onOutput(bundle) { try { writeBundle(bundle); } catch (e) { console.error(chalk.redBright(e.message)); process.exitCode = 1; stop(); } } function stop() { compiler.output.disconnect(onOutput); } } else { const bundle = await compiler.build(entrypoint, compilerOpts); writeBundle(bundle); } function writeBundle(bundle) { if (outputPath === "-") { process.stdout.write(bundle); } else { try { fs.writeFileSync(outputPath, bundle, { encoding: "utf-8", }); } catch (e) { throw new Error(`Unable to write bundle: ${e.message}`); } } } function onStarting() { compilationStarted = performance.now(); if (opts.watch) { readline.cursorTo(process.stdout, 0, 0); readline.clearScreenDown(process.stdout); } console.log(formatCompiling(entrypoint, projectRoot)); } function onFinished() { const timeFinished = performance.now(); console.log(formatCompiled(entrypoint, projectRoot, compilationStarted, timeFinished)); } function onDiagnostics(diagnostics) { for (const diag of diagnostics) { console.log(formatDiagnostic(diag, projectRoot)); } } } function formatCompiling(scriptPath, cwd) { const name = formatFilename(scriptPath, cwd); return (styleReset("") + "Compiling " + styleFile(name) + styleReset("") + "..."); } function formatCompiled(scriptPath, cwd, timeStarted, timeFinished) { const name = formatFilename(scriptPath, cwd); const elapsed = Math.floor(timeFinished - timeStarted); return (styleReset("") + "Compiled " + styleFile(name) + styleReset("") + styleCode(` (${elapsed} ms)`) + styleReset("")); } function formatDiagnostic(diag, cwd) { const { category, code, text, file } = diag; let prefix = ""; if (file !== undefined) { const filename = formatFilename(file.path, cwd); const line = file.line + 1n; const character = file.character + 1n; const pathSegment = styleFile(filename); const lineSegment = styleLocation(String(line)); const charSegment = styleLocation(String(character)); prefix = `${pathSegment}:${lineSegment}:${charSegment} - `; } const categoryStyler = CATEGORY_STYLE[category] ?? styleReset; const styledCategory = categoryStyler(category); const styledCode = styleCode(`TS${code}`); return `${prefix}${styledCategory}${styleReset("")} ${styledCode}${styleReset("")}: ${text}`; } function formatFilename(filePath, cwd) { const absoluteCwd = path.resolve(cwd); const absolutePath = path.resolve(filePath); if (absolutePath.startsWith(absoluteCwd + path.sep)) { return absolutePath.slice(absoluteCwd.length + 1); } return filePath; } main().catch((e) => { setImmediate(() => { console.error(chalk.red.bold(e.message)); process.exitCode = 1; }); });