libreoffice-file-converter
Version:
Simple NodeJS wrapper for libreoffice CLI for converting office documents to different formats
276 lines (269 loc) • 9.98 kB
JavaScript
import { createReadStream, createWriteStream } from 'node:fs';
import { readFile, writeFile, access } from 'node:fs/promises';
import { setGracefulCleanup, dir } from 'tmp-promise';
import { Buffer } from 'node:buffer';
import { execFile } from 'node:child_process';
import process from 'node:process';
import { inspect } from 'node:util';
import { pathToFileURL } from 'node:url';
import { join, sep } from 'node:path';
import { pipeline } from 'node:stream';
var __defProp = Object.defineProperty;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __name = (target, value) => __defProp(target, "name", { value, configurable: true });
var __publicField = (obj, key, value) => __defNormalProp(obj, key + "" , value);
// src/constants/env.ts
var PROGRAM_FILES = "PROGRAMFILES";
var PROGRAM_FILES_86 = "PROGRAMFILES(X86)";
var DARWIN_PATHS = /* @__PURE__ */ __name(() => [
"/Applications/LibreOffice.app/Contents/MacOS/soffice"
], "DARWIN_PATHS");
var LINUX_PATHS = /* @__PURE__ */ __name(() => [
"/usr/bin/libreoffice",
"/usr/bin/soffice",
"/snap/bin/libreoffice",
"/opt/libreoffice/program/soffice",
"/opt/libreoffice7.6/program/soffice"
], "LINUX_PATHS");
var WIN32_PATHS = /* @__PURE__ */ __name(() => [
join(process.env[PROGRAM_FILES_86] || "", "LIBREO~1/program/soffice.exe"),
join(process.env[PROGRAM_FILES_86] || "", "LibreOffice/program/soffice.exe"),
join(process.env[PROGRAM_FILES] || "", "LibreOffice/program/soffice.exe")
], "WIN32_PATHS");
// src/helpers/libreoffice.ts
var getLibreOfficeExecutablePaths = /* @__PURE__ */ __name(() => {
if (process.platform === "darwin") {
return DARWIN_PATHS();
}
if (process.platform === "linux") {
return LINUX_PATHS();
}
if (process.platform === "win32") {
return WIN32_PATHS();
}
return [];
}, "getLibreOfficeExecutablePaths");
var getLibreOfficeExecutablePath = /* @__PURE__ */ __name(async (binaryPaths = []) => {
const paths = [
...binaryPaths,
...getLibreOfficeExecutablePaths()
];
const existingPaths = await Promise.all(paths.map(async (path2) => {
try {
await access(path2);
} catch {
return false;
}
return path2;
}));
const [path] = existingPaths.filter(Boolean);
if (!path) {
throw new Error("Could not find soffice binary");
}
return path;
}, "getLibreOfficeExecutablePath");
var getLibreOfficeCommandArgs = /* @__PURE__ */ __name((installationDir, inputPath, outputDir, format, inputFilter, outputFilter) => {
const filterSegment = outputFilter && outputFilter.length > 0 ? `:${outputFilter}` : "";
const args = [
`-env:UserInstallation=${pathToFileURL(installationDir).toString()}`,
"--headless"
];
if (inputFilter && inputFilter.length > 0) {
args.push(`--infilter=${inputFilter}`);
}
args.push("--convert-to", `${format}${filterSegment}`, "--outdir", outputDir, inputPath);
return args;
}, "getLibreOfficeCommandArgs");
var hasLibreOfficeError = /* @__PURE__ */ __name((stderr) => {
return stderr?.toLowerCase()?.includes("error:");
}, "hasLibreOfficeError");
// src/helpers/child-process.ts
var isBuffer = /* @__PURE__ */ __name((value) => {
return Buffer.isBuffer(value);
}, "isBuffer");
var processOutputToString = /* @__PURE__ */ __name((processOutput) => {
return isBuffer(processOutput) ? processOutput.toString("utf-8") : processOutput;
}, "processOutputToString");
var execFileAsync = /* @__PURE__ */ __name((path, args, options, debug) => {
return new Promise((resolve, reject) => {
execFile(path, args, options, (error, stdout, stderr) => {
const stderrString = processOutputToString(stderr);
const stdoutString = processOutputToString(stdout);
if (debug) {
const debugInfo = inspect({
args,
path,
stderr: stderrString,
stdout: stdoutString
}, {
colors: true,
sorted: true
});
process.stdout.write(`LibreOffice debug output:
${debugInfo}
`);
}
const libreOfficeError = hasLibreOfficeError(stderrString);
if (error || libreOfficeError) {
return reject(error || new Error(stderrString));
}
return resolve();
});
});
}, "execFileAsync");
var getTemporaryFilePath = /* @__PURE__ */ __name((temporaryDir) => {
return join(temporaryDir, "source");
}, "getTemporaryFilePath");
var getProcessedFilePath = /* @__PURE__ */ __name((temporaryDirPath, inputPath, format) => {
const insideTemporaryDir = inputPath.startsWith(temporaryDirPath);
if (insideTemporaryDir) {
return `${inputPath}.${format}`;
}
const inputPathSegments = inputPath.split(sep);
const inputFileNameSegment = inputPathSegments.length > 0 ? inputPathSegments[inputPathSegments.length - 1] : inputPathSegments[0];
const inputFileNameSegments = inputFileNameSegment?.split(".");
const inputFileName = inputFileNameSegments?.length && inputFileNameSegments.length > 1 ? inputFileNameSegments?.slice(0, -1).join(".") : inputFileNameSegments?.[0];
return `${temporaryDirPath}${sep}${inputFileName}.${format}`;
}, "getProcessedFilePath");
var writeStream = /* @__PURE__ */ __name((path, readStream) => {
return new Promise((resolve, reject) => {
const writeStream2 = createWriteStream(path);
pipeline(readStream, writeStream2, (error) => {
if (error) {
return reject(error);
}
return resolve();
});
});
}, "writeStream");
// src/helpers/primitives.ts
var isObject = /* @__PURE__ */ __name((item) => {
return Boolean(item && typeof item === "object" && !Array.isArray(item));
}, "isObject");
var deepMerge = /* @__PURE__ */ __name((...objects) => {
return objects.reduce((target, source) => {
Object.keys(source).forEach((key) => {
const targetValue = target[key];
const sourceValue = source[key];
if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
target[key] = [
...targetValue,
...sourceValue
];
return;
}
if (isObject(targetValue) && isObject(sourceValue)) {
target[key] = deepMerge(targetValue, sourceValue);
return;
}
target[key] = sourceValue;
});
return target;
}, {});
}, "deepMerge");
// src/libreoffice-file-converter.ts
var _LibreOfficeFileConverter = class _LibreOfficeFileConverter {
/**
* Create an instance of the LibreOfficeFileConverter.
*
* @example
* ```ts
* const libreOfficeFileConverter = new LibreOfficeFileConverter({ childProcessOptions: { timeout: 60 * 1000 } });
* ```
*
* @param options The LibreOfficeFileConverter options.
*
* @constructor
* @public
*/
constructor(options = {}) {
__publicField(this, "_options");
this._options = options;
setGracefulCleanup();
}
/**
* Converts provided input to the requested format.
*
* @example
* ```ts
* const outputBuffer = await libreOfficeFileConverter.convert({
* buffer: inputBuffer,
* format: 'pdf',
* input: 'buffer',
* output: 'buffer',
* });
* ```
*
* @param options Convert options: input and output type, format, filter, converter options.
*
* @returns Buffer, readable stream or void depending on the convert options.
*
* @public
*/
async convert(options) {
const { filter, format, inputFilter, options: callOptions = {}, outputFilter } = options;
const mergedOptions = this.mergeOptions(callOptions);
const { tmpOptions } = mergedOptions;
const temporaryDir = await dir({
prefix: "libreoffice-file-converter",
unsafeCleanup: true,
...tmpOptions
});
const inputPath = await this.write(options, temporaryDir.path);
await this.process(inputPath, temporaryDir.path, format, inputFilter || filter, outputFilter, mergedOptions);
return this.read(options, temporaryDir.path, inputPath);
}
mergeOptions(options = {}) {
return deepMerge(this._options, options);
}
async process(inputPath, outputDir, format, inputFilter, outputFilter, options = {}) {
const { binaryPaths, childProcessOptions, debug, tmpOptions } = options;
const libreOfficeExecutablePath = await getLibreOfficeExecutablePath(binaryPaths);
const installationDir = await dir({
prefix: "soffice",
unsafeCleanup: true,
...tmpOptions
});
const libreOfficeCommandArgs = getLibreOfficeCommandArgs(installationDir.path, inputPath, outputDir, format, inputFilter, outputFilter);
await execFileAsync(libreOfficeExecutablePath, libreOfficeCommandArgs, childProcessOptions, debug);
}
async read(options, temporaryDirPath, inputPath) {
const { format, output } = options;
const processedFilePath = getProcessedFilePath(temporaryDirPath, inputPath, format);
if (output === "buffer") {
return readFile(processedFilePath);
}
if (output === "file") {
const { outputPath } = options;
const stream = createReadStream(processedFilePath);
return writeStream(outputPath, stream);
}
if (output === "stream") {
return createReadStream(processedFilePath);
}
}
async write(options, temporaryDirPath) {
const { input } = options;
const temporaryFilePath = getTemporaryFilePath(temporaryDirPath);
if (input === "buffer") {
const { buffer } = options;
await writeFile(temporaryFilePath, buffer);
return temporaryFilePath;
}
if (input === "file") {
const { inputPath } = options;
return inputPath;
}
if (input === "stream") {
const { stream } = options;
await writeStream(temporaryFilePath, stream);
return temporaryFilePath;
}
return temporaryFilePath;
}
};
__name(_LibreOfficeFileConverter, "LibreOfficeFileConverter");
var LibreOfficeFileConverter = _LibreOfficeFileConverter;
export { LibreOfficeFileConverter };
//# sourceMappingURL=index.mjs.map
//# sourceMappingURL=index.mjs.map