UNPKG

cerevox

Version:

TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments

278 lines 11.8 kB
#!/usr/bin/env node "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.run = run; const commander_1 = require("commander"); const node_fs_1 = require("node:fs"); const node_path_1 = require("node:path"); const dotenv_1 = require("dotenv"); const readline = __importStar(require("node:readline")); const esbuild_1 = require("esbuild"); const ts = __importStar(require("typescript")); const open_1 = __importDefault(require("open")); const __1 = __importDefault(require("..")); const promises_1 = require("node:timers/promises"); const fs_1 = __importDefault(require("fs")); // Load environment variables (0, dotenv_1.config)({ path: ['.env.cerevox', '.env'], }); // eslint-disable-next-line @typescript-eslint/no-require-imports const VERSION = require('../../package.json').version; async function testAndOpenUrl(url) { while (true) { try { const res = await fetch(url); if (res.status < 500) { (0, open_1.default)(url); break; } } finally { await (0, promises_1.setTimeout)(500); } } } // esbuild plugin to replace chromium.launch and chromium.launchPersistentContext with chromium.connectOverCDP function createChromiumReplacePlugin(wsUrl, headlessRef) { return { name: 'chromium-replace', setup(build) { build.onLoad({ filter: /\.(js|ts|mjs)$/ }, async (args) => { const contents = fs_1.default.readFileSync(args.path, 'utf8'); // Use TypeScript AST to transform the code const isTypeScript = args.path.endsWith('.ts'); const sourceFile = ts.createSourceFile(args.path, contents, ts.ScriptTarget.Latest, true, isTypeScript ? ts.ScriptKind.TS : ts.ScriptKind.JS); const transformer = context => { return sourceFile => { const visitor = (node) => { // Check for method calls like browserType.launch() or browserType.launchPersistentContext() if (ts.isCallExpression(node) && ts.isPropertyAccessExpression(node.expression)) { const methodName = node.expression.name.text; if (methodName === 'launch' || methodName === 'launchPersistentContext') { // Extract headless parameter if present if (node.arguments.length > 0) { const firstArg = node.arguments[0]; if (ts.isObjectLiteralExpression(firstArg)) { for (const property of firstArg.properties) { if (ts.isPropertyAssignment(property) && ts.isIdentifier(property.name) && property.name.text === 'headless') { // Extract headless value if (property.initializer.kind === ts.SyntaxKind.TrueKeyword) { headlessRef.value = true; } else if (property.initializer.kind === ts.SyntaxKind.FalseKeyword) { headlessRef.value = false; } break; } } } } // Replace with connectOverCDP call return ts.factory.createCallExpression(ts.factory.createPropertyAccessExpression(node.expression.expression, // Keep the browserType object ts.factory.createIdentifier('connectOverCDP')), undefined, [ts.factory.createStringLiteral(wsUrl)]); } } return ts.visitEachChild(node, visitor, context); }; return ts.visitNode(sourceFile, visitor); }; }; const result = ts.transform(sourceFile, [transformer]); const printer = ts.createPrinter(); const transformedCode = printer.printFile(result.transformed[0]); result.dispose(); return { contents: transformedCode, loader: args.path.endsWith('.ts') ? 'ts' : 'js', }; }); }, }; } async function buildBundleCode(file, session) { try { // Resolve the absolute path of the file const filePath = (0, node_path_1.resolve)(file); // Check if file exists if (!(0, node_fs_1.existsSync)(filePath)) { throw new Error(`File not found: ${filePath}`); } const wsUrl = await session.browser.getCDPEndpoint(); const headlessRef = { value: true }; // Default to true // Use esbuild to bundle the file with chromium replacement plugin const result = await (0, esbuild_1.build)({ entryPoints: [filePath], bundle: true, write: false, // Don't write to disk, return the result platform: 'node', format: 'cjs', target: 'node18', minify: false, sourcemap: false, external: [ 'playwright', 'playwright-core', 'playwright-extra', 'puppeteer-extra-plugin-stealth', ], plugins: [createChromiumReplacePlugin(wsUrl, headlessRef)], }); // Get the bundled code from the result if (result.outputFiles && result.outputFiles.length > 0) { return { code: result.outputFiles[0].text, headless: headlessRef.value }; } else { throw new Error('No output generated from esbuild'); } } catch (error) { console.error('Error bundling code:', error); throw error; } } async function runProgram(session, file) { // Run the specified file const { code, headless } = await buildBundleCode(file, session); if (!headless) { const url = await session.browser.getLiveviewPageUrl(); if (url) { (0, open_1.default)(url); } } const result = await session.codeRunner.run(code); await session.close(); return result; } // Helper function to prompt user for input function promptForKey() { return new Promise(resolve => { const rl = readline.createInterface({ input: process.stdin, output: process.stdout, }); rl.question('Please enter your Cerevox API key: ', answer => { rl.close(); resolve(answer.trim()); }); }); } const program = new commander_1.Command(); program .name('cerevox-run') .description('Run a command in a Cerevox sandbox') .version(VERSION) .argument('<file>', 'JavaScript or TypeScript file to run with Cerevox') .option('-k, --key <string>', 'Cerevox API key (optional if CEREVOX_API_KEY env var is set)') .option('-l, --listen <number>', 'Port number to listen on', parseInt) .helpOption('-h, --help', 'Display help for command') .action(async (file, options) => { let { key } = options; // If no key provided via command line, check environment variable if (!key) { key = process.env.CEREVOX_API_KEY; if (key) { // console.log('Using Cerevox API key from environment variable.'); } else { // console.log('No Cerevox API key found in environment variable.'); key = await promptForKey(); if (!key) { console.error('Error: Cerevox API key is required to continue.'); process.exit(1); } } } // console.log(`File to run: ${file}`); try { const cerevox = new __1.default({ apiKey: key, logLevel: 'error', }); const logger = cerevox.getLogger().child({ className: 'CerevoxRun', }, { level: 'info', }); logger.info('🖥️ Launching cerevox...'); const session = await cerevox.launch({ timeout: 1440, }); logger.info('✅ Cerevox launched successfully.'); const { listen } = options; // Log listen parameter if provided if (listen !== undefined) { const host = session.sandbox.getProxyHost(listen); logger.info(`⌛️ Listening: ${host}`); testAndOpenUrl(`https://${host}`); } await runProgram(session, file); logger.debug('Program executed successfully.'); // Check and create .env.cerevox file const envFilePath = (0, node_path_1.join)(process.cwd(), '.env.cerevox'); if (!(0, node_fs_1.existsSync)(envFilePath)) { try { (0, node_fs_1.writeFileSync)(envFilePath, `CEREVOX_API_KEY=${key}\n`); logger.info('Created .env.cerevox file with your API key.'); } catch (error) { logger.error('Failed to create .env.cerevox file:', error); } } else { logger.debug('.env.cerevox file already exists, will not overwrite the file content.'); } } catch (error) { console.error('❌ Error running the program:', error); } }); function run() { program.parse(); } if (require.main === module) { run(); } //# sourceMappingURL=cerevox-run.js.map