diff --git a/README.md b/README.md index debcc28..96bee91 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,6 @@ # HTML to PDF -A github action that converts HTML files to PDF [GitHub Actions](https://github.com/features/actions). - +A [GitHub Action](https://github.com/features/actions) that converts a HTML file to PDF. ## ✨ Example Usage @@ -11,4 +10,7 @@ A github action that converts HTML files to PDF [GitHub Actions](https://github. with: htmlFile: ./public/index.html outputFile: ./public/resume.pdf + pdfOptions: '{"format": "A4", "margin": {"top": "10mm", "left": "10mm", "right": "10mm", "bottom": "10mm"}}' ``` + +`pdfOptions` relate to [puppeteers page.pdf options](https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions) diff --git a/action.yml b/action.yml index d0fd50f..307bdf2 100644 --- a/action.yml +++ b/action.yml @@ -1,19 +1,27 @@ -name: 'HTML to PDF' -description: 'Node module that converts HTML files to PDFs.' -author: 'fifsky@gmail.com' +name: "HTML to PDF" +description: "Converts HTML file to PDF." +author: "fifsky@gmail.com" inputs: htmlFile: - description: 'html file path' + description: "html file path" required: true outputFile: - description: 'output file path' + description: "output file path" required: true + pdfOptions: + description: | + PDF options as described here: + https://github.com/GoogleChrome/puppeteer/blob/master/docs/api.md#pagepdfoptions - + Needs to be in JSON format, e.g. `{"format": "A4", "pageRanges": "1"}` + required: false + default: "{}" + runs: - using: 'docker' - image: 'Dockerfile' + using: "docker" + image: "Dockerfile" branding: color: "blue" - icon: "file" \ No newline at end of file + icon: "file" diff --git a/docker/Dockerfile b/docker/Dockerfile index 88bdef6..596f2df 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,38 +1,11 @@ -FROM node:10-alpine +FROM buildkite/puppeteer + LABEL MAINTAINER="Xudong Cai " -ENV TZ=Asia/Shanghai -ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true -#chromium@76.0.3809.132-r0 -RUN sed -i 's!http://dl-cdn.alpinelinux.org/!https://mirrors.aliyun.com/!g' /etc/apk/repositories \ - && apk update && apk upgrade && \ - echo @edge https://mirrors.aliyun.com/alpine/edge/community >> /etc/apk/repositories && \ - echo @edge https://mirrors.aliyun.com/alpine/edge/main >> /etc/apk/repositories && \ - apk --no-cache add \ - tzdata \ - curl \ - ca-certificates \ - chromium@edge \ - nss@edge \ - fontconfig \ - freetype \ - ttf-dejavu \ - ttf-droid \ - ttf-freefont \ - mesa-egl \ - mesa-gles \ - && mkdir /usr/lib/chromium/swiftshader/ \ - && cp /usr/lib/libGLESv2.so.2 /usr/lib/chromium/swiftshader/libGLESv2.so \ - && cp /usr/lib/libEGL.so.1 /usr/lib/chromium/swiftshader/libEGL.so \ - && ln -sf /usr/bin/chromium-browser /usr/local/bin/chrome \ - && ln -sf /usr/share/zoneinfo/$TZ /etc/localtime \ - && echo $TZ > /etc/timezone \ - && mkdir -p /usr/share/fonts/chinese/TrueType/ -COPY simsun.ttf /usr/share/fonts/chinese/TrueType/simsun.ttf -RUN fc-cache -fv +ENTRYPOINT ["node", "/lib/main.js"] COPY . . -RUN npm install --production +RUN mv simsun.ttf /usr/local/share/fonts/ -ENTRYPOINT ["node", "/lib/main.js"] +RUN npm install --production diff --git a/lib/main.js b/lib/main.js index 582b6ad..38fdcdf 100644 --- a/lib/main.js +++ b/lib/main.js @@ -5,17 +5,17 @@ async function run() { try { const htmlFile = core.getInput('htmlFile'); const outputFile = core.getInput('outputFile'); + const pdfOptions = core.getInput('pdfOptions'); - console.log("PWD:",__dirname); - console.log(`Start conver ${htmlFile} to PDF`); + console.log(`Start convert ${htmlFile} to PDF`); const html5ToPDF = new HTML5ToPDF({ launchOptions:{ - executablePath: '/usr/bin/chromium-browser', - args:['--no-sandbox', '--headless', '--disable-gpu']}, + executablePath: '/usr/bin/google-chrome-unstable', + args:['--no-sandbox', '--headless', '--disable-gpu']}, + pdfOptions: pdfOptions? JSON.parse(pdfOptions) : {}, inputPath: htmlFile, outputPath: outputFile, - }) await html5ToPDF.start() diff --git a/package.json b/package.json index 5d187a4..fac06d1 100644 --- a/package.json +++ b/package.json @@ -23,12 +23,11 @@ "@actions/io": "^1.0.0", "@actions/tool-cache": "^1.0.0", "auto-bind": "^2.1.0", - "colors": "^1.3.3", - "commander": "^3.0.0", - "lodash": "^4.17.15", - "puppeteer": "~1.17.0" + "serve-static": "^1.14.1", + "finalhandler": "^1.1.2" }, "devDependencies": { - "@types/node": "^10.10.4" + "@types/node": "^10.10.4", + "puppeteer": "~1.17.0" } } diff --git a/topdf/index.js b/topdf/index.js index 00f6ef7..d678af8 100755 --- a/topdf/index.js +++ b/topdf/index.js @@ -1,14 +1,6 @@ const autoBind = require("auto-bind") const puppeteer = require("puppeteer") -const map = require("lodash/map") -const pickBy = require("lodash/pickBy") const Server = require("./server") -const { - readBodyOrFile, - convertPath, - getTemplateFilePath, - convertIncludes, -} = require("./util") class HTML5ToPDF { constructor(options) { @@ -18,74 +10,30 @@ class HTML5ToPDF { parseOptions(options) { const { - inputBody, inputPath, outputPath, - templateUrl, - renderDelay, launchOptions, - include = [], + pdfOptions, } = options - const legacyOptions = options.options || {} - const pdf = pickBy( - options.pdf || { - landscape: legacyOptions.landscape, - format: legacyOptions.pageSize, - printBackground: legacyOptions.printBackground, - }, - ) - if (!pdf.path && outputPath) { - pdf.path = convertPath(outputPath) - } - const templatePath = getTemplateFilePath(options) - const body = readBodyOrFile(inputBody, inputPath) + + const pdf = pdfOptions + pdf.path = outputPath + return { - body, pdf, - templatePath, - templateUrl, launchOptions, - include: convertIncludes(include), - renderDelay, + inputPath } } - includeAssets() { - const includePromises = map(this.options.include, ({ type, filePath }) => { - if (type === "js") { - return this.page.addScriptTag({ - path: filePath, - }) - } - if (type === "css") { - return this.page.addStyleTag({ - path: filePath, - }) - } - }) - includePromises.push(() => { - return this.page.addStyleTag({ - path: getTemplateFilePath("pdf.css"), - }) - }) - includePromises.push(() => { - return this.page.addStyleTag({ - path: getTemplateFilePath("highlight.css"), - }) - }) - return Promise.all(includePromises) - } - async start() { - this.server = new Server(this.options) - await this.server.listen() + this.server = new Server(process.cwd()) this.browser = await puppeteer.launch(this.options.launchOptions) this.page = await this.browser.newPage() - await this.page.goto(this.server.address(), { - waitUntil: "networkidle2", + const url = "http://localhost:3000/" + this.options.inputPath + await this.page.goto(url, { + waitUntil: "networkidle0", }) - await this.page.setContent(this.options.body) - await this.includeAssets() if (this.options.renderDelay) { await this.page.waitFor(this.options.renderDelay) } @@ -101,7 +49,7 @@ class HTML5ToPDF { async close() { await this.browser.close() - await this.server.close() + this.server.close() } } diff --git a/topdf/server.js b/topdf/server.js index 1e88379..cb1ed9e 100755 --- a/topdf/server.js +++ b/topdf/server.js @@ -1,88 +1,20 @@ -const fs = require("fs") -const http = require("http") -const path = require("path") -const { URL, parse } = require("url") -const autoBind = require("auto-bind") +var finalhandler = require('finalhandler') +var http = require('http') +var serveStatic = require('serve-static') -class StaticServer { - constructor({ templatePath }) { - autoBind(this) - this.templatePath = templatePath - this.server = http.createServer(this._handleRequest) - } - - address() { - let { address, port } = this.server.address() - if (address === "::") { - address = "localhost" - } - return new URL(`http://${address}:${port}`).toString() - } - - listen() { - return new Promise((resolve, reject) => { - this.server.listen(err => { - if (err) { - return reject(err) - } - resolve() - }) - }) - } - - close() { - return new Promise((resolve, reject) => { - this.server.close(err => { - if (err) { - return reject(err) - } - resolve() - }) - }) - } - - _handleRequest(req, res) { - const { pathname } = parse(req.url) - let filePath = path.join(this.templatePath, pathname) - fs.exists(filePath, exists => { - if (!exists) { - res.statusCode = 404 - return res.end(`File ${filePath} not found!`) - } - - if (fs.statSync(filePath).isDirectory()) { - filePath += "/index.html" - } +class Server { + constructor (path) { + var serve = serveStatic(path, { 'index': ['index.html', 'index.htm'] }) - fs.readFile(filePath, (err, data) => { - if (err) { - res.statusCode = 500 - res.end(`Error getting the file: ${err}.`) - } else { - res.end(data) - } - }) + this.server = http.createServer(function onRequest (req, res) { + serve(req, res, finalhandler(req, res)) }) - } -} -class ExistingServer { - constructor({ templateUrl }) { - this.templateUrl = templateUrl - } - async listen() {} - address() { - return new URL(this.templateUrl).toString() + this.server.listen(3000) } - async close() {} -} -class Server { - constructor({ templatePath, templateUrl }) { - if (templateUrl) { - return new ExistingServer({ templateUrl }) - } - return new StaticServer({ templatePath }) + close () { + this.server.close() } } diff --git a/topdf/util.js b/topdf/util.js deleted file mode 100755 index a0f0785..0000000 --- a/topdf/util.js +++ /dev/null @@ -1,50 +0,0 @@ -const fs = require("fs") -const path = require("path") -const map = require("lodash/map") -const replace = require("lodash/replace") -const toString = require("lodash/toString") -const isString = require("lodash/isString") -const isPlainObject = require("lodash/isPlainObject") - -const convertPath = filePath => { - if (path.isAbsolute(filePath)) return filePath - return path.join(process.cwd(), filePath) -} - -const getTemplateFilePath = ({ templatePath, template = "html5bp" }) => { - if (templatePath) return templatePath - return path.resolve(path.join(__dirname, "..", "templates", template)) -} - -const readBodyOrFile = (body, filePath) => { - if (body) { - return toString(body) - } - if (!filePath) { - return - } - if (fs.statSync(filePath).isDirectory()) { - return - } - return fs.readFileSync(convertPath(filePath), "utf-8") -} - -const convertIncludes = includes => { - return map(includes, include => { - if (isString(include)) { - return { - type: replace(path.extname(include), ".", ""), - filePath: include, - } - } - if (isPlainObject(include)) { - return include - } - }) -} -module.exports = { - readBodyOrFile, - convertPath, - convertIncludes, - getTemplateFilePath, -}