prince
Version:
Node API for executing XML/HTML to PDF renderer PrinceXML via prince(1) CLI
333 lines (321 loc) • 16.9 kB
JavaScript
/*
** node-prince -- Node API for executing PrinceXML via prince(1) CLI
** Copyright (c) 2014-2024 Dr. Ralf S. Engelschall <rse@engelschall.com>
**
** Permission is hereby granted, free of charge, to any person obtaining
** a copy of this software and associated documentation files (the
** "Software"), to deal in the Software without restriction, including
** without limitation the rights to use, copy, modify, merge, publish,
** distribute, sublicense, and/or sell copies of the Software, and to
** permit persons to whom the Software is furnished to do so, subject to
** the following conditions:
**
** The above copyright notice and this permission notice shall be included
** in all copies or substantial portions of the Software.
**
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
** EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
** MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
** IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
** CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
** TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
** SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
/* global process: false */
/* global __dirname: false */
/* global require: false */
/* global console: false */
/* eslint no-console: 0 */
/*
* prince-npm.js: NPM install-time integration
*/
/* core requirements */
var child_process = require("child_process");
var fs = require("fs");
var path = require("path");
var zlib = require("zlib");
/* extra requirements */
var progress = require("progress");
var promise = require("promise");
var axios = require("axios");
var which = require("which");
var chalk = require("chalk");
var tar = require("tar");
var streamzip = require("node-stream-zip");
var rimraf = require("rimraf");
var mkdirp = require("mkdirp");
/* determine path and version of prince(1) */
var princeInfo = function () {
return new promise(function (resolve, reject) {
which("prince").then(function (filename) {
child_process.execFile(filename, [ "--version" ], function (error, stdout, stderr) {
if (error !== null) {
reject("prince(1) failed on \"--version\": " + error);
return;
}
var m = stdout.match(/^Prince\s+(\d+(?:\.\d+)?)/);
if (!(m !== null && typeof m[1] !== "undefined")) {
reject("prince(1) returned unexpected output on \"--version\":\n" + stdout + stderr);
return;
}
resolve({ command: filename, version: m[1] });
});
}).catch(function (error) {
reject("prince(1) not found in PATH: " + error);
return;
})
});
};
/* return download URL for latest PrinceXML distribution */
var princeDownloadURL = function () {
return new promise(function (resolve /*, reject */) {
var id = process.arch + "-" + process.platform;
if (id.match(/^ia32-win32$/))
resolve("https://www.princexml.com/download/prince-16-win32.zip");
else if (id.match(/^x64-win32$/))
resolve("https://www.princexml.com/download/prince-16-win64.zip");
else if (id.match(/^(?:x64|arm64)-darwin/))
resolve("https://www.princexml.com/download/prince-16-macos.zip");
else {
child_process.exec("sh \"" + __dirname + "/shtool\" platform -t binary", function (error, stdout /*, stderr */) {
if (error) {
console.log(chalk.red("ERROR: failed to determine platform details on platform \"" + id + "\": " + error));
process.exit(1);
}
var platform = stdout.toString().replace(/^(\S+).*\n?$/, "$1");
if (id.match(/^x64-linux/)) {
if (platform.match(/^amd64-ubuntu1[89](?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-ubuntu20.04-amd64.tar.gz");
else if (platform.match(/^amd64-ubuntu2[23](?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-ubuntu22.04-amd64.tar.gz");
else if (platform.match(/^amd64-ubuntu24(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-ubuntu24.04-amd64.tar.gz");
else if (platform.match(/^amd64-debian12(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-debian12-amd64.tar.gz");
else if (platform.match(/^amd64-debian11(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-debian11-amd64.tar.gz");
else if (platform.match(/^amd64-almalinux9(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-almalinux9-x86_64.tar.gz");
else if (platform.match(/^amd64-almalinux8(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-almalinux8-x86_64.tar.gz");
else if (platform.match(/^amd64-alpine3\.21(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.21-x86_64.tar.gz");
else if (platform.match(/^amd64-alpine3\.20(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.20-x86_64.tar.gz");
else if (platform.match(/^amd64-alpine3\.19(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.19-x86_64.tar.gz");
else if (platform.match(/^amd64-alpine3\.18(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.18-x86_64.tar.gz");
else if (platform.match(/^amd64-opensuse15.6(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-opensuse15.6-x86_64.tar.gz");
else if (id.match(/^x64-/))
resolve("https://www.princexml.com/download/prince-16-linux-generic-x86_64.tar.gz");
}
else if (id.match(/^arm64-linux/)) {
if (platform.match(/^arm64-ubuntu24(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-ubuntu24.04-arm64.tar.gz");
else if (platform.match(/^arm64-ubuntu2[23](?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-ubuntu22.04-arm64.tar.gz");
else if (platform.match(/^arm64-debian12(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-debian12-arm64.tar.gz");
else if (platform.match(/^arm64-debian11(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-debian11-arm64.tar.gz");
else if (platform.match(/^arm64-alpine3\.21(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.21-aarch64.tar.gz");
else if (platform.match(/^arm64-alpine3\.20(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.20-aarch64.tar.gz");
else if (platform.match(/^arm64-alpine3\.19(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.19-aarch64.tar.gz");
else if (platform.match(/^arm64-alpine3\.18(?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-alpine3.18-aarch64.tar.gz");
else if (platform.match(/^aarch64-alpine[23](?:\.\d+)*$/))
resolve("https://www.princexml.com/download/prince-16-linux-generic-aarch64-musl.tar.gz");
else
resolve("https://www.princexml.com/download/prince-16-linux-generic-aarch64.tar.gz");
}
else if (platform.match(/^amd64-freebsd13(?:\.\d+)*/))
resolve("https://www.princexml.com/download/prince-16-freebsd13.0-amd64.tar.gz");
else if (platform.match(/^amd64-freebsd14(?:\.\d+)*/))
resolve("https://www.princexml.com/download/prince-16-freebsd14.0-amd64.tar.gz");
else {
console.log(chalk.red("ERROR: PrinceXML not available for platform \"" + platform + "\" (\"" + id + "\")"));
process.exit(1);
}
});
}
});
};
/* download data from URL */
var downloadData = function (url) {
return new promise(function (resolve, reject) {
var progress_bar = null;
var options = {
method: "GET",
url: url,
encoding: null,
headers: {
"User-Agent": "node-prince (prince-npm.js:install)"
},
responseType: "arraybuffer",
onDownloadProgress: function(progressEvent) {
if (!progress_bar) {
progress_bar = new progress(
"-- download: [:bar] :percent (ETA: :etas)", {
complete: "#",
incomplete: "=",
width: 40,
total: progressEvent.total
});
}
progress_bar.tick(progressEvent.loaded);
}
};
(new promise(function (resolve /*, reject */) {
if (typeof process.env.http_proxy === "string" && process.env.http_proxy !== "") {
options.proxy = process.env.http_proxy;
console.log("-- using proxy ($http_proxy): " + options.proxy);
resolve();
}
else {
child_process.exec("npm config get proxy", function (error, stdout /*, stderr */) {
if (error === null) {
stdout = stdout.toString().replace(/\r?\n$/, "");
if (stdout.match(/^https?:\/\/.+/)) {
options.proxy = stdout;
console.log("-- using proxy (npm config get proxy): " + options.proxy);
}
}
resolve();
});
}
})).then(function () {
console.log("-- download: " + url);
axios(options).then(function (response) {
if (response.status === 200) {
console.log("-- download: " + response.data.length + " bytes received.");
resolve(response.data);
}
}).catch(function (error) {
reject("download failed: " + error);
});
});
});
};
/* extract a zipfile (*.zip) */
var extractZipfile = function (zipfile, stripdir, destdir) {
return new promise(function (resolve, reject) {
var zip = new streamzip({ file: zipfile });
zip.on("ready", function () {
zip.extract(stripdir, destdir, function (error) {
zip.close();
if (error) {
reject(error);
} else {
setTimeout(function () { resolve(); }, 500);
}
});
});
});
};
/* extract a tarball (*.tar.gz) */
var extractTarball = function (tarball, destdir, stripdirs) {
return new promise(function (resolve, reject) {
fs.createReadStream(tarball)
.pipe(zlib.createGunzip())
.pipe(tar.extract({ cwd: destdir, strip: stripdirs }))
.on("error", function (error) { reject(error); })
.on("close", function () { /* global setTimeout: true */ setTimeout(function () { resolve(); }, 500); });
});
};
/* main procedure */
if (process.argv.length !== 3) {
console.log(chalk.red("ERROR: invalid number of arguments"));
process.exit(1);
}
var destdir;
if (process.argv[2] === "install") {
/* installation procedure */
console.log("++ checking for globally installed PrinceXML");
princeInfo().then(function (prince) {
console.log("-- found prince(1) command: " + chalk.blue(prince.command));
console.log("-- found prince(1) version: " + chalk.blue(prince.version));
}, function (/* error */) {
console.log("++ downloading PrinceXML distribution");
princeDownloadURL().then(function (url) {
downloadData(url).then(function (data) {
console.log("++ locally unpacking PrinceXML distribution");
destdir = path.join(__dirname, "prince");
var destfile;
var id = process.arch + "-" + process.platform;
if (id.match(/^ia32-win32$/)) {
destfile = path.join(__dirname, "prince.zip");
fs.writeFileSync(destfile, data, { encoding: null });
mkdirp.sync(destdir);
extractZipfile(destfile, "prince-16-win32", destdir).then(function () {
fs.chmodSync(path.join(destdir, "lib/prince/bin/prince"), fs.constants.S_IRWXU
| fs.constants.S_IRGRP | fs.constants.S_IXGRP | fs.constants.S_IROTH | fs.constants.S_IXOTH);
fs.unlinkSync(destfile);
console.log("-- OK: local PrinceXML installation now available");
}, function (error) {
console.log(chalk.red("** ERROR: failed to extract: " + error));
});
}
else if (id.match(/^x64-win32$/)) {
destfile = path.join(__dirname, "prince.zip");
fs.writeFileSync(destfile, data, { encoding: null });
mkdirp.sync(destdir);
extractZipfile(destfile, "prince-16-win64", destdir).then(function () {
fs.chmodSync(path.join(destdir, "lib/prince/bin/prince"), fs.constants.S_IRWXU
| fs.constants.S_IRGRP | fs.constants.S_IXGRP | fs.constants.S_IROTH | fs.constants.S_IXOTH);
fs.unlinkSync(destfile);
console.log("-- OK: local PrinceXML installation now available");
}, function (error) {
console.log(chalk.red("** ERROR: failed to extract: " + error));
});
}
else if (process.platform === "darwin") {
destfile = path.join(__dirname, "prince.zip");
fs.writeFileSync(destfile, data, { encoding: null });
mkdirp.sync(destdir);
extractZipfile(destfile, "prince-16-macos", destdir).then(function () {
fs.chmodSync(path.join(destdir, "lib/prince/bin/prince"), fs.constants.S_IRWXU
| fs.constants.S_IRGRP | fs.constants.S_IXGRP | fs.constants.S_IROTH | fs.constants.S_IXOTH);
fs.unlinkSync(destfile);
console.log("-- OK: local PrinceXML installation now available");
}, function (error) {
console.log(chalk.red("** ERROR: failed to extract: " + error));
});
}
else {
destfile = path.join(__dirname, "prince.tgz");
fs.writeFileSync(destfile, data, { encoding: null });
mkdirp.sync(destdir);
extractTarball(destfile, destdir, 1).then(function () {
fs.unlinkSync(destfile);
console.log("-- OK: local PrinceXML installation now available");
}, function (error) {
console.log(chalk.red("** ERROR: failed to extract: " + error));
});
}
}, function (error) {
console.log(chalk.red("** ERROR: failed to download: " + error));
});
});
});
}
else if (process.argv[2] === "uninstall") {
/* uninstallation procedure */
destdir = path.join(__dirname, "prince");
if (fs.existsSync(destdir)) {
console.log("++ deleting locally unpacked PrinceXML distribution");
rimraf(destdir).then(function () {
console.log("-- OK: done");
}).catch(function (error) {
console.log(chalk.red("** ERROR: " + error));
});
}
}
else {
console.log(chalk.red("ERROR: invalid argument"));
process.exit(1);
}