cerevox
Version:
TypeScript SDK for browser automation and secure command execution in highly available and scalable micro computer environments
278 lines • 11.8 kB
JavaScript
;
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