chromancer
Version:
A powerful command-line interface for automating Chrome browser using Playwright. Perfect for web scraping, automation, testing, and browser workflows.
207 lines (206 loc) • 7.72 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;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
const core_1 = require("@oclif/core");
const base_js_1 = require("../base.js");
class Pdf extends base_js_1.BaseCommand {
static description = 'Save page as PDF';
static examples = [
'<%= config.bin %> <%= command.id %> --output page.pdf',
'<%= config.bin %> <%= command.id %> --output report.pdf --format A4',
'<%= config.bin %> <%= command.id %> --output doc.pdf --landscape --background',
'<%= config.bin %> <%= command.id %> --output print.pdf --css "@media print"',
];
static flags = {
...base_js_1.BaseCommand.baseFlags,
output: core_1.Flags.string({
char: 'o',
description: 'Output PDF file path',
default: 'page.pdf',
}),
format: core_1.Flags.string({
char: 'f',
description: 'Page format',
options: ['Letter', 'Legal', 'Tabloid', 'Ledger', 'A0', 'A1', 'A2', 'A3', 'A4', 'A5', 'A6'],
default: 'Letter',
}),
landscape: core_1.Flags.boolean({
description: 'Use landscape orientation',
default: false,
}),
scale: core_1.Flags.integer({
description: 'Scale of the webpage rendering (percentage)',
default: 100,
}),
background: core_1.Flags.boolean({
description: 'Print background graphics',
default: false,
}),
'display-header-footer': core_1.Flags.boolean({
description: 'Display header and footer',
default: false,
}),
'header-template': core_1.Flags.string({
description: 'HTML template for header',
}),
'footer-template': core_1.Flags.string({
description: 'HTML template for footer',
}),
margin: core_1.Flags.string({
description: 'Page margins (e.g., "20px" or "1in 2in 1in 2in")',
}),
'page-ranges': core_1.Flags.string({
description: 'Page ranges to print (e.g., "1-5, 8, 11-13")',
}),
css: core_1.Flags.string({
description: 'CSS media type to emulate',
}),
'wait-for': core_1.Flags.string({
description: 'Wait for selector before generating PDF',
}),
};
async run() {
const { flags } = await this.parse(Pdf);
await this.connectToChrome(flags.port, flags.host, flags.launch, flags.profile, flags.headless, flags.verbose, flags.keepOpen);
if (!this.page) {
this.error('Failed to connect to Chrome');
}
await this.generatePDF(this.page);
}
async generatePDF(page) {
const { flags } = await this.parse(Pdf);
try {
// Wait for specific element if requested
if (flags['wait-for']) {
this.log(`⏳ Waiting for: ${flags['wait-for']}`);
await page.waitForSelector(flags['wait-for'], { timeout: 30000 });
}
// Emulate CSS media type if specified
if (flags.css) {
await page.emulateMedia({ media: flags.css });
}
// Prepare PDF options
const pdfOptions = {
path: flags.output,
format: flags.format,
landscape: flags.landscape,
printBackground: flags.background,
displayHeaderFooter: flags['display-header-footer'],
};
// Scale
if (flags.scale !== 100) {
pdfOptions.scale = flags.scale / 100;
}
// Header and footer templates
if (flags['header-template']) {
pdfOptions.headerTemplate = flags['header-template'];
}
if (flags['footer-template']) {
pdfOptions.footerTemplate = flags['footer-template'];
}
// Margins
if (flags.margin) {
const margins = this.parseMargins(flags.margin);
pdfOptions.margin = margins;
}
// Page ranges
if (flags['page-ranges']) {
pdfOptions.pageRanges = flags['page-ranges'];
}
this.log('📄 Generating PDF...');
// Generate PDF
await page.pdf(pdfOptions);
this.log(`✅ PDF saved to: ${flags.output}`);
// Log details if verbose
if (flags.verbose) {
const stats = await this.getFileStats(flags.output);
this.logVerbose('PDF generated', {
format: flags.format,
orientation: flags.landscape ? 'landscape' : 'portrait',
scale: `${flags.scale}%`,
fileSize: stats ? `${(stats.size / 1024).toFixed(2)} KB` : 'unknown',
url: page.url(),
});
}
}
catch (error) {
this.error(`Failed to generate PDF: ${error.message}`);
}
}
parseMargins(margin) {
const parts = margin.trim().split(/\s+/);
if (parts.length === 1) {
// Single value for all margins
return {
top: parts[0],
right: parts[0],
bottom: parts[0],
left: parts[0],
};
}
else if (parts.length === 2) {
// Vertical and horizontal
return {
top: parts[0],
right: parts[1],
bottom: parts[0],
left: parts[1],
};
}
else if (parts.length === 4) {
// Top, right, bottom, left
return {
top: parts[0],
right: parts[1],
bottom: parts[2],
left: parts[3],
};
}
else {
this.error('Invalid margin format. Use "20px" or "1in 2in 1in 2in"');
}
}
async getFileStats(path) {
try {
const { promises: fs } = await Promise.resolve().then(() => __importStar(require('fs')));
const stats = await fs.stat(path);
return { size: stats.size };
}
catch {
return null;
}
}
}
exports.default = Pdf;