diff --git a/README.md b/README.md index f7a3f49..07bb98a 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,33 @@ This library is inspired by the [PHP library PDFMerger](https://github.com/myoky `npm install --save pdf-merger-js` +of global installation if you want to use the cli tool: + +`npm install -g pdf-merger-js` + ## Usage +### CLI + +``` +Options: + -V, --version output the version number + -o, --output Merged PDF output file path + -v, --verbose Print verbose output + -s, --silent do not print any output to stdout. Overwrites --verbose + -h, --help display help for command +``` + +#### Example calls + +Merge pages 1-2 from the first input with pages 1,2 and 5-7 from the second pdf document: + +`pdf-merge --output ./merged.pdf ./input1.pdf#1-2 ./input2.pdf#1,2,5-7` + +Get two pdf files from the an url and merge the first one with pages 2-3 from the second one: + +`pdf-merge --verbose --output ./sample.pdf Testfile.pdf https://pdfobject.com/pdf/sample.pdf https://upload.wikimedia.org/wikipedia/commons/1/13/Example.pdf#2-3` + ### node.js The node.js version has the following export functions: diff --git a/cli.js b/cli.js new file mode 100644 index 0000000..042fb3c --- /dev/null +++ b/cli.js @@ -0,0 +1,68 @@ +#!/usr/bin/env node + +import fs from 'fs-extra' +import { program } from 'commander' + +import PDFMerger from './index.js' +import { parsePagesString } from './parsePagesString.js' + +function main (packageJson) { + program + .version(packageJson.version) + .description(packageJson.description) + .option('-o, --output ', 'Merged PDF output file path') + .option('-v, --verbose', 'Print verbose output') + .option('-s, --silent', 'do not print any output to stdout. Overwrites --verbose') + .arguments('') + .action(async (inputFiles, cmd) => { + const outputFile = cmd.output + const verbose = cmd.verbose && !cmd.silent + const silent = cmd.silent + + if (!outputFile) { + console.error('Please provide an output file using the --output flag') + return + } + + if (!inputFiles || !inputFiles.length) { + console.error('Please provide at least one input file') + return + } + + try { + const merger = new PDFMerger() + + for (const inputFile of inputFiles) { + const [filePath, pagesString] = inputFile.split('#') + const pages = pagesString ? parsePagesString(pagesString) : null + if (verbose) { + if (pages && pages.length) { + console.log(`adding page${pages.length > 1 ? 's' : ''} ${pages.join(',')} from ${filePath} to output...`) + } else { + console.log(`adding all pages from ${filePath} to output...`) + } + } + await merger.add(filePath, pages) + } + + if (verbose) { + console.log(`Saving merged output to ${outputFile}...`) + } + + await merger.save(outputFile) + + if (!silent) { + console.log(`Merged pages successfully into ${outputFile}`) + } + } catch (error) { + console.error('An error occurred while merging the PDFs:', error) + } + }) + + program.parse(process.argv) +} + +(() => { + const packageJson = fs.readJsonSync(new URL('./package.json', import.meta.url)) + main(packageJson) +})() diff --git a/package-lock.json b/package-lock.json index ade5dec..1e8f133 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,12 @@ "version": "5.0.0", "license": "MIT", "dependencies": { + "commander": "^11.1.0", "pdf-lib": "^1.17.1" }, + "bin": { + "pdf-merger-js": "cli.js" + }, "devDependencies": { "fs-extra": "^11.2.0", "jest": "^29.7.0", @@ -2054,6 +2058,14 @@ "node": ">= 0.8" } }, + "node_modules/commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==", + "engines": { + "node": ">=16" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -9152,6 +9164,11 @@ "delayed-stream": "~1.0.0" } }, + "commander": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-11.1.0.tgz", + "integrity": "sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==" + }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", diff --git a/package.json b/package.json index eaa6d14..60e452e 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,9 @@ "main": "./index.js", "types": "./index.d.ts", "browser": "./browser.js", + "bin": { + "pdf-merge": "cli.js" + }, "scripts": { "standard": "standard", "standard:fix": "standard --fix", @@ -29,6 +32,7 @@ "author": "nbesli", "license": "MIT", "dependencies": { + "commander": "^11.1.0", "pdf-lib": "^1.17.1" }, "devDependencies": { diff --git a/test/cli.test.js b/test/cli.test.js new file mode 100644 index 0000000..ed4d4f4 --- /dev/null +++ b/test/cli.test.js @@ -0,0 +1,109 @@ +import path from 'path' +import fs from 'fs-extra' +import util from 'util' +import { exec } from 'child_process' +import { jest } from '@jest/globals' +import pdfDiff from 'pdf-diff' + +const asyncExec = util.promisify(exec) + +const __dirname = path.dirname(new URL(import.meta.url).pathname) +const FIXTURES_DIR = path.join(__dirname, 'fixtures') +const TMP_DIR = path.join(__dirname, 'tmp') + +jest.setTimeout(10000) + +async function mergePDFsCli (outputFile, inputFiles) { + const { stdout, stderr } = await asyncExec(`node ./cli.js --output ${outputFile} ${inputFiles.join(' ')}`) + return { stdout, stderr } +} + +describe('issues', () => { + beforeAll(async () => { + await fs.ensureDir(TMP_DIR) + }) + + test('should merge two pdfs', async () => { + await mergePDFsCli(path.join(TMP_DIR, 'Testfile_AB.pdf'), [ + path.join(FIXTURES_DIR, 'Testfile_A.pdf'), + path.join(FIXTURES_DIR, 'Testfile_B.pdf') + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, 'Testfile_AB.pdf'), + path.join(TMP_DIR, 'Testfile_AB.pdf') + ) + expect(diff).toBeFalsy() + }) + + test('should merge two pdfs from URL', async () => { + await mergePDFsCli(path.join(TMP_DIR, 'Testfile_AB.pdf'), [ + 'https://github.com/nbesli/pdf-merger-js/raw/master/test/fixtures/Testfile_A.pdf', + 'https://github.com/nbesli/pdf-merger-js/raw/master/test/fixtures/Testfile_B.pdf' + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, 'Testfile_AB.pdf'), + path.join(TMP_DIR, 'Testfile_AB.pdf') + ) + expect(diff).toBeFalsy() + }) + + test('combine single pages from multiple pdfs', async () => { + await mergePDFsCli(path.join(TMP_DIR, '2468.pdf'), [ + path.join(FIXTURES_DIR, '123456789.pdf#2'), + path.join(FIXTURES_DIR, '123456789.pdf#4'), + path.join(FIXTURES_DIR, '123456789.pdf#6'), + path.join(FIXTURES_DIR, '123456789.pdf#8') + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, '2468.pdf'), + path.join(TMP_DIR, '2468.pdf') + ) + expect(diff).toBeFalsy() + }) + + test('combine pages from multiple pdfs (list)', async () => { + await mergePDFsCli(path.join(TMP_DIR, '2468.pdf'), [ + path.join(FIXTURES_DIR, '123456789.pdf#2,4'), + path.join(FIXTURES_DIR, '123456789.pdf#6,8') + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, '2468.pdf'), + path.join(TMP_DIR, '2468.pdf') + ) + expect(diff).toBeFalsy() + }) + + test('combine pages from multipel pdfs (rang)', async () => { + await mergePDFsCli(path.join(TMP_DIR, '123456789.pdf'), [ + path.join(FIXTURES_DIR, '123456789.pdf#1-5'), + path.join(FIXTURES_DIR, '123456789.pdf#6-9') + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, '123456789.pdf'), + path.join(TMP_DIR, '123456789.pdf') + ) + expect(diff).toBeFalsy() + }) + + test('combine pages from multipel pdfs (combined list and range)', async () => { + await mergePDFsCli(path.join(TMP_DIR, '123456789.pdf'), [ + path.join(FIXTURES_DIR, '123456789.pdf#1,2,3-4'), + path.join(FIXTURES_DIR, '123456789.pdf#5-7,8to9') + ]) + const diff = await pdfDiff( + path.join(FIXTURES_DIR, '123456789.pdf'), + path.join(TMP_DIR, '123456789.pdf') + ) + expect(diff).toBeFalsy() + }) + + afterEach(async () => { + for (const file of await fs.readdir(TMP_DIR)) { + await fs.unlink(path.join(TMP_DIR, file)) + } + }) + + afterAll(async () => { + await fs.remove(TMP_DIR) + }) +}) diff --git a/test/fixtures/123.pdf b/test/fixtures/123.pdf new file mode 100644 index 0000000..90032ed Binary files /dev/null and b/test/fixtures/123.pdf differ diff --git a/test/fixtures/123456789.pdf b/test/fixtures/123456789.pdf new file mode 100644 index 0000000..d226a4c Binary files /dev/null and b/test/fixtures/123456789.pdf differ diff --git a/test/fixtures/2468.pdf b/test/fixtures/2468.pdf new file mode 100644 index 0000000..33b72a7 Binary files /dev/null and b/test/fixtures/2468.pdf differ