Compare commits
40 Commits
isomorphic
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 6b58a8b64e | |||
| 7f1513f170 | |||
| c2c64b0be9 | |||
| 1cf5c7a8c0 | |||
| d1d7bbb526 | |||
| c299b63f63 | |||
| 020778a9d4 | |||
| 8a15bd69c8 | |||
| 2677573022 | |||
| 4167947a87 | |||
| efffe0851a | |||
| 62ce10ccb1 | |||
| fc775e68bb | |||
| 9a5a0f3800 | |||
| 97d6934bed | |||
| 5ac1aba6a5 | |||
| d24f641667 | |||
| 1372c90f8e | |||
| a9a5372d49 | |||
| ea81f28d44 | |||
| 38a379afe7 | |||
| 65abf280a8 | |||
| 70885ee8d0 | |||
| cd08c5e860 | |||
| 6cc5e292ad | |||
| b863239514 | |||
| 3be519955b | |||
| 51dcdc83a9 | |||
| 5cbd477051 | |||
| 9dc863dc7f | |||
| ad91db0ba5 | |||
| 9b3cba6ffa | |||
| 8ab830945c | |||
| 84cc655be3 | |||
| ac3b76346d | |||
| 26520ceaf5 | |||
| 9354644f55 | |||
| f387ee8f42 | |||
| 9755dd3336 | |||
| 0eaaf4cbfd |
@@ -4,35 +4,23 @@ on: ['push', 'pull_request']
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-18.04
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
strategy:
|
||||
matrix:
|
||||
node: ['10', '12']
|
||||
node: ['12', '14']
|
||||
|
||||
name: Node ${{ matrix.node }}
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
|
||||
- name: Setup Node ${{ matrix.node }}
|
||||
uses: actions/setup-node@v1
|
||||
- uses: actions/setup-node@v1
|
||||
with:
|
||||
node-version: ${{ matrix.node }}
|
||||
|
||||
- name: Install dependencies
|
||||
run: yarn
|
||||
|
||||
- name: Linting and check spelling
|
||||
run: yarn lint
|
||||
|
||||
- name: Test and collect coverage
|
||||
run: yarn test
|
||||
|
||||
- name: Build package
|
||||
run: yarn build
|
||||
|
||||
- name: Push coverage to Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
- run: yarn
|
||||
- run: yarn lint
|
||||
- run: yarn test
|
||||
- run: yarn build
|
||||
- uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.github_token }}
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
"(M|m)arkdown",
|
||||
"Node(js|JS)",
|
||||
"TODO",
|
||||
"Setplex",
|
||||
|
||||
"toBe",
|
||||
"toThrow",
|
||||
|
||||
@@ -6,8 +6,8 @@
|
||||
[](https://www.npmjs.com/package/gotenberg-js-client)
|
||||

|
||||
|
||||
A simple JS/TS client for interacting with a [Gotenberg](https://thecodingmachine.github.io/gotenberg/) API.<br>
|
||||
[Gotenberg](https://thecodingmachine.github.io/gotenberg/) is a Docker-powered stateless API for converting HTML, Markdown and Office documents to PDF.
|
||||
A simple JS/TS client for interacting with a [Gotenberg](https://gotenberg.dev/) API.<br>
|
||||
[Gotenberg](https://gotenberg.dev/) is a Docker-powered stateless API for converting HTML, Markdown and Office documents to PDF.
|
||||
|
||||
- HTML and Markdown conversions using Google Chrome headless
|
||||
- Office conversions (.txt, .rtf, .docx, .doc, .odt, .pptx, .ppt, .odp and so on) using [unoconv](https://github.com/dagwieers/unoconv)
|
||||
@@ -26,6 +26,19 @@ Or using `npm`
|
||||
$ npm install --save gotenberg-js-client
|
||||
```
|
||||
|
||||
## NB ⚠️
|
||||
|
||||
This library is not yet fully compatible with Gotenberg 7.<br/>
|
||||
You can find more info in [this comment](https://github.com/yumauri/gotenberg-js-client/issues/32#issuecomment-981140727).
|
||||
|
||||
There are three main issues:
|
||||
|
||||
- Gotenberg 7 has introduced new concept of conversion modules, thus, changing conversion URLs, so now they are different, than ones, this library creates. This can be sidestepped using custom connection string or adjusting URL manually (see [this comment](https://github.com/yumauri/gotenberg-js-client/issues/32#issuecomment-981140727)).
|
||||
- New modules has some new possibilities/parameters, which are impossible to pass, using this library. This can be sidestepped using `adjust`, and casting to `any`, if you use TypeScript (see [this issue](https://github.com/yumauri/gotenberg-js-client/issues/33) for reference).
|
||||
- Gotenberg 7 can potentially has many many different custom conversion modules, you can even write your own one. You can combine p.1 and p.2 to use any module with any path with any parameters, but I guess it will look not good. But it should work nonetheless.
|
||||
|
||||
So, nothing you can live without, but there are some inconveniences. For a while ;)
|
||||
|
||||
## Usage
|
||||
|
||||
```typescript
|
||||
@@ -330,6 +343,31 @@ const toMergedPDF = pipe(
|
||||
)
|
||||
```
|
||||
|
||||
## Advanced fine adjustment
|
||||
|
||||
There is special function `adjust`, which you can use to modify _any_ field in prepared internal `Request` object. You can check internal `Request` object structure in types. Any object, passed to `adjust`, will be merged with prepared `Request`.
|
||||
|
||||
For example, you can modify `url`, if your Gotenberg instance is working behind reverse proxy with some weird url replacement rules:
|
||||
|
||||
```typescript
|
||||
import { pipe, gotenberg, convert, html, adjust, please } from 'gotenberg-js-client'
|
||||
|
||||
// Original Gotenberg HTML conversion endpoint is
|
||||
// -> /convert/html
|
||||
// But your reverse proxy uses location
|
||||
// -> /hidden/html/conversion
|
||||
const toPDF = pipe(
|
||||
gotenberg('http://localhost:3000'),
|
||||
convert,
|
||||
html,
|
||||
adjust({ url: '/hidden/html/conversion' }),
|
||||
please
|
||||
)
|
||||
```
|
||||
|
||||
But, using that function, remember about Peter Parker principle:
|
||||
> "With great power comes great responsibility"
|
||||
|
||||
## Bonus
|
||||
|
||||
If you happen to use this package from JavaScript, you will, obviously, lost type safety, but in return, you can use [proposed pipe operator](https://github.com/tc39/proposal-pipeline-operator) (with [Babel plugin](https://babeljs.io/docs/en/babel-plugin-proposal-pipeline-operator)), to get beauty like this:
|
||||
@@ -362,4 +400,6 @@ const toPDF = got.pipe(
|
||||
|
||||
## Sponsored
|
||||
|
||||
[<img src="https://setplex.com/img/logo.png" alt="Setplex" width="236">](https://setplex.com)
|
||||
[<img src="https://setplex.com/img/logo.png" alt="Setplex OTT Platform" width="236">](https://setplex.com/en/)
|
||||
|
||||
[Setplex OTT Platform](https://setplex.com/en/)
|
||||
|
||||
+17
-17
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gotenberg-js-client",
|
||||
"version": "0.6.1",
|
||||
"version": "0.7.4",
|
||||
"description": "A simple JS/TS for interacting with a Gotenberg API",
|
||||
"author": "Victor Didenko <yumaa.verdin@gmail.com> (https://yumaa.name)",
|
||||
"license": "MIT",
|
||||
@@ -25,7 +25,7 @@
|
||||
{
|
||||
"path": "pkg/dist-node/index.js",
|
||||
"webpack": false,
|
||||
"limit": "4100 B"
|
||||
"limit": "4293 B"
|
||||
}
|
||||
],
|
||||
"@pika/pack": {
|
||||
@@ -63,32 +63,32 @@
|
||||
},
|
||||
"homepage": "https://github.com/yumauri/gotenberg-js-client#readme",
|
||||
"dependencies": {
|
||||
"form-data": "^3.0.0"
|
||||
"form-data": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@pika/pack": "^0.5.0",
|
||||
"@pika/plugin-build-node": "^0.9.2",
|
||||
"@pika/plugin-ts-standard-pkg": "^0.9.2",
|
||||
"@size-limit/preset-small-lib": "^4.5.0",
|
||||
"@types/jest": "^25.2.2",
|
||||
"@types/node": "^13.13.4",
|
||||
"flowgen": "^1.10.0",
|
||||
"jest": "^26.0.1",
|
||||
"nock": "^12.0.3",
|
||||
"@size-limit/preset-small-lib": "^4.11.0",
|
||||
"@types/jest": "^26.0.23",
|
||||
"@types/node": "^15.6.1",
|
||||
"flowgen": "^1.14.1",
|
||||
"jest": "^27.0.1",
|
||||
"nock": "^13.0.11",
|
||||
"pika-plugin-package.json": "^1.0.2",
|
||||
"pika-plugin-typedefs-to-flow": "^0.0.2",
|
||||
"prettier": "^2.0.5",
|
||||
"size-limit": "^4.5.0",
|
||||
"ts-jest": "^26.0.0",
|
||||
"ts-node": "^8.10.1",
|
||||
"tslint": "^6.1.2",
|
||||
"pika-plugin-typedefs-to-flow": "^0.0.3",
|
||||
"prettier": "^2.3.0",
|
||||
"size-limit": "^4.11.0",
|
||||
"ts-jest": "^27.0.1",
|
||||
"ts-node": "^10.0.0",
|
||||
"tslint": "^6.1.3",
|
||||
"tslint-config-prettier": "^1.18.0",
|
||||
"tslint-config-security": "^1.16.0",
|
||||
"tslint-config-standard-plus": "^2.3.0",
|
||||
"typescript": "^3.9.2",
|
||||
"typescript": "^4.3.2",
|
||||
"yaspeller": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10.9.0"
|
||||
"node": ">=12.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
+2
-4
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url' // tslint:disable-line no-circular-imports
|
||||
import FormData from 'form-data'
|
||||
|
||||
////////////////////////////////////////////////////////////////////////////////
|
||||
@@ -110,11 +111,8 @@ export type OfficeRequestFields = {
|
||||
// Attention: when converting a website to PDF, you should remove all margins
|
||||
// If not, some of the content of the page might be hidden
|
||||
// https://thecodingmachine.github.io/gotenberg/#url
|
||||
export type UrlRequestFields = {
|
||||
export type UrlRequestFields = HtmlRequestFields & {
|
||||
remoteURL?: string
|
||||
|
||||
// https://thecodingmachine.github.io/gotenberg/#html.paper_size_margins_orientation_scaling
|
||||
scale?: number
|
||||
}
|
||||
|
||||
// merge conversion doesn't have any form fields
|
||||
|
||||
+12
-8
@@ -12,11 +12,13 @@ export const header = (name: string, value: number | string): HeadersModifier =>
|
||||
/**
|
||||
* Adds/Modifies many headers for Url conversion
|
||||
*/
|
||||
export const headers = (headers: HttpHeaders): HeadersModifier => (_) => {
|
||||
for (const name in headers) {
|
||||
header(name, headers[name])(_)
|
||||
export const headers =
|
||||
(headers: HttpHeaders): HeadersModifier =>
|
||||
(_) => {
|
||||
for (const name in headers) {
|
||||
header(name, headers[name])(_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// https://thecodingmachine.github.io/gotenberg/#webhook.custom_http_headers
|
||||
|
||||
@@ -29,8 +31,10 @@ export const webhookHeader = (name: string, value: number | string): HeadersModi
|
||||
/**
|
||||
* Adds/Modifies many headers for Webhook
|
||||
*/
|
||||
export const webhookHeaders = (headers: HttpHeaders): HeadersModifier => (_) => {
|
||||
for (const name in headers) {
|
||||
webhookHeader(name, headers[name])(_)
|
||||
export const webhookHeaders =
|
||||
(headers: HttpHeaders): HeadersModifier =>
|
||||
(_) => {
|
||||
for (const name in headers) {
|
||||
webhookHeader(name, headers[name])(_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,29 @@
|
||||
import { Request } from './_types'
|
||||
|
||||
/**
|
||||
* Recursively merge requests
|
||||
*/
|
||||
const merge = <RequestEx extends Request>(request: RequestEx, modify: Partial<Request>) => {
|
||||
const result = { ...request, ...modify }
|
||||
|
||||
for (const key in modify) {
|
||||
if (
|
||||
modify[key] &&
|
||||
request[key] &&
|
||||
typeof modify[key] === 'object' &&
|
||||
typeof request[key] === 'object'
|
||||
) {
|
||||
result[key] = merge(request[key], modify[key])
|
||||
}
|
||||
}
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
/**
|
||||
* Adjust any Request *object* fields, for any request
|
||||
* @return new typed Request, doesn't modify original Request
|
||||
*/
|
||||
export const adjust: {
|
||||
<RequestEx extends Request>(modify: Partial<Request>): (request: RequestEx) => RequestEx
|
||||
} = (modify) => (request) => merge(request, modify)
|
||||
+8
-3
@@ -1,6 +1,7 @@
|
||||
import http from 'http'
|
||||
import https from 'https'
|
||||
import FormData from 'form-data'
|
||||
import { URL } from 'url'
|
||||
import { GotenbergClientFunction } from '../_types'
|
||||
|
||||
/**
|
||||
@@ -26,13 +27,17 @@ export function post(
|
||||
return new Promise((resolve, reject) => {
|
||||
const req = request(_url, {
|
||||
method: 'POST',
|
||||
headers: { ...data.getHeaders(), ...headers },
|
||||
...this, // extends with config options
|
||||
headers: {
|
||||
...data.getHeaders(),
|
||||
...headers,
|
||||
...(this ? (this as any).headers : null), // extends with config headers
|
||||
},
|
||||
})
|
||||
|
||||
req.on('error', reject)
|
||||
req.on('response', (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res)
|
||||
} else {
|
||||
let error = res.statusCode + ' ' + res.statusMessage
|
||||
@@ -69,7 +74,7 @@ export function get(this: object | null, url: string): Promise<NodeJS.ReadableSt
|
||||
|
||||
req.on('error', reject)
|
||||
req.on('response', (res) => {
|
||||
if (res.statusCode === 200) {
|
||||
if (res.statusCode && res.statusCode >= 200 && res.statusCode < 300) {
|
||||
resolve(res)
|
||||
} else {
|
||||
res.resume() // ignore response body
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url' // tslint:disable-line no-circular-imports
|
||||
import { client as native } from './client/node'
|
||||
import {
|
||||
GotenbergClient,
|
||||
|
||||
@@ -12,6 +12,7 @@ export { html } from './html'
|
||||
export { url } from './url'
|
||||
|
||||
// export modifiers functions and constants
|
||||
export { adjust } from './adjust'
|
||||
export { add } from './add'
|
||||
export { set } from './set'
|
||||
export { to } from './to'
|
||||
|
||||
@@ -4,7 +4,9 @@ import { Request } from '../_types'
|
||||
* Adjust Request fields, by adding given `path` to it
|
||||
* @return new Request, doesn't modify original Request
|
||||
*/
|
||||
export const path = (path: string) => (request: Request): Request => ({
|
||||
...request,
|
||||
url: (request.url || '') + path,
|
||||
})
|
||||
export const path =
|
||||
(path: string) =>
|
||||
(request: Request): Request => ({
|
||||
...request,
|
||||
url: (request.url || '') + path,
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url' // tslint:disable-line no-circular-imports
|
||||
import { Readable } from 'stream'
|
||||
import { FileURI, ObjectSource, PlainSource, Source, TupleSource } from '../_types'
|
||||
|
||||
@@ -80,14 +81,17 @@ export const isIterable = (
|
||||
|
||||
/**
|
||||
* Check, if given argument is simple string, and presumably is is just file name
|
||||
* I hope no one will wants to use filename longer than 50 symbols :)
|
||||
*/
|
||||
const fileNameRE = /^[\w\s\(\),-]+\.[A-Za-z0-9]+$/
|
||||
const MIN_FILE_NAME_LENGTH = 3
|
||||
const MAX_FILE_NAME_LENGTH = 50
|
||||
const filenameRE = /.+\..+/
|
||||
const filenameReservedRE = /[<>:"/\\|?*\u0000-\u001F]/g
|
||||
const windowsReservedNameRE = /^(con|prn|aux|nul|com\d|lpt\d)$/i
|
||||
const MAX_FILE_NAME_LENGTH = 255
|
||||
export const isFileName = (x: Source | undefined | null) =>
|
||||
isString(x) &&
|
||||
!x.startsWith('file:') && // in ideal world there should be `!isFileUri(x)`, but TypeScript sucks here
|
||||
x.length >= MIN_FILE_NAME_LENGTH &&
|
||||
x.length <= MAX_FILE_NAME_LENGTH &&
|
||||
fileNameRE.test(x)
|
||||
x !== '.' &&
|
||||
x !== '..' &&
|
||||
!x.startsWith('file:') && // in ideal world there should be `!isFileUri(x)`, but TypeScript sucks here
|
||||
!filenameReservedRE.test(x) &&
|
||||
!windowsReservedNameRE.test(x) &&
|
||||
filenameRE.test(x)
|
||||
|
||||
+14
-10
@@ -20,15 +20,19 @@ export const type: {
|
||||
(type: RequestType.Merge): (request: Request) => MergeRequest
|
||||
(type: RequestType.Office): (request: Request) => OfficeRequest
|
||||
(type: RequestType.Markdown): (request: Request) => MarkdownRequest
|
||||
} = (type: RequestType) => (request: Request): any => {
|
||||
if ('type' in request && request.type !== RequestType.Undefined) {
|
||||
throw new Error(
|
||||
`Cannot set "${RequestType[type]}" conversion, already set to "${RequestType[request.type]}"`
|
||||
)
|
||||
}
|
||||
} =
|
||||
(type: RequestType) =>
|
||||
(request: Request): any => {
|
||||
if ('type' in request && request.type !== RequestType.Undefined) {
|
||||
throw new Error(
|
||||
`Cannot set "${RequestType[type]}" conversion, already set to "${
|
||||
RequestType[request.type]
|
||||
}"`
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
...request,
|
||||
type,
|
||||
return {
|
||||
...request,
|
||||
type,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +1,14 @@
|
||||
import {
|
||||
ConversionOptions,
|
||||
HtmlRequest,
|
||||
MarginOptions,
|
||||
MarkdownRequest,
|
||||
OfficeRequest,
|
||||
PaperOptions,
|
||||
RequestFields,
|
||||
} from './_types'
|
||||
import { ConversionOptions, MarginOptions, PaperOptions, Request, RequestFields } from './_types'
|
||||
import { fields } from './internal/fields'
|
||||
import { marginSizes, paperSize } from './to-helpers'
|
||||
|
||||
/**
|
||||
* Adjust Request fields, for html, markdown or office requests
|
||||
* @return new Request (Html|Markdown|Office), doesn't modify original Request
|
||||
* Adjust Request fields, for any request
|
||||
* @return new typed Request, doesn't modify original Request
|
||||
*/
|
||||
export const to: {
|
||||
(...opts: ConversionOptions[]): (request: HtmlRequest) => HtmlRequest
|
||||
(...opts: ConversionOptions[]): (request: OfficeRequest) => OfficeRequest
|
||||
(...opts: ConversionOptions[]): (request: MarkdownRequest) => MarkdownRequest
|
||||
} = (...opts: ConversionOptions[]): any => {
|
||||
<RequestEx extends Request>(...opts: ConversionOptions[]): (request: RequestEx) => RequestEx
|
||||
} = (...opts): any => {
|
||||
const options: RequestFields = {}
|
||||
|
||||
// page size and margins options
|
||||
|
||||
+7
-4
@@ -2,8 +2,11 @@
|
||||
* Some short useful functions
|
||||
*/
|
||||
|
||||
export const setProperty = (...fields: string[]) => (...values: any[]) => (object: any) => {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
object[fields[i]] = values[i]
|
||||
export const setProperty =
|
||||
(...fields: string[]) =>
|
||||
(...values: any[]) =>
|
||||
(object: any) => {
|
||||
for (let i = 0; i < fields.length; i++) {
|
||||
object[fields[i]] = values[i]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+2
-2
@@ -1,5 +1,5 @@
|
||||
import { Request, RequestType, UrlRequest } from './_types'
|
||||
import { isString, isURL } from './internal/source-checkers'
|
||||
import { isString, isURL } from './internal/source-checkers' // tslint:disable-line no-circular-imports
|
||||
import { pipe } from './tools/pipe'
|
||||
import { fields } from './internal/fields'
|
||||
import { path } from './internal/path'
|
||||
@@ -7,7 +7,7 @@ import { type } from './internal/type'
|
||||
|
||||
/**
|
||||
* Adjust Request url, by adding `/url` to it; Set `remoteURL` from source
|
||||
* @return new HtmlRequest, doesn't modify original Request
|
||||
* @return new UrlRequest, doesn't modify original Request
|
||||
*/
|
||||
export const url: {
|
||||
(request: Request): UrlRequest
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import { adjust, Request, RequestType } from '../src'
|
||||
|
||||
// dumb object to test purity
|
||||
const dumb: Request = {
|
||||
type: RequestType.Undefined,
|
||||
url: 'test',
|
||||
fields: {
|
||||
scale: 1,
|
||||
landscape: true,
|
||||
pageRanges: '1-2',
|
||||
},
|
||||
client: {
|
||||
post: () => {
|
||||
throw new Error('not implemented')
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
test('Should adjust flat fields', () => {
|
||||
expect(adjust({})(dumb)).toEqual({ ...dumb })
|
||||
expect(adjust({ url: 'changed' })(dumb)).toEqual({ ...dumb, url: 'changed' })
|
||||
})
|
||||
|
||||
test('Should adjust deep fields', () => {
|
||||
expect(adjust({ fields: { landscape: false } })(dumb)).toEqual({
|
||||
...dumb,
|
||||
fields: {
|
||||
...dumb.fields,
|
||||
landscape: false,
|
||||
},
|
||||
})
|
||||
expect(adjust({ headers: { Authorization: 'Bearer token' } })(dumb)).toEqual({
|
||||
...dumb,
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
},
|
||||
})
|
||||
expect(
|
||||
adjust({ headers: { Authorization: 'Bearer token' } })({
|
||||
...dumb,
|
||||
headers: { 'X-Header': 'test' },
|
||||
})
|
||||
).toEqual({
|
||||
...dumb,
|
||||
headers: {
|
||||
Authorization: 'Bearer token',
|
||||
'X-Header': 'test',
|
||||
},
|
||||
})
|
||||
})
|
||||
|
||||
test('Should replace deep fields', () => {
|
||||
expect(
|
||||
adjust({ headers: { Authorization: 'Bearer token' } })({
|
||||
...dumb,
|
||||
headers: { Authorization: 'Basic dXNlcjpwYXNzd29yZA==' },
|
||||
})
|
||||
).toEqual({ ...dumb, headers: { Authorization: 'Bearer token' } })
|
||||
})
|
||||
@@ -2,7 +2,7 @@ import nock from 'nock'
|
||||
import FormData from 'form-data'
|
||||
import { client } from '../../src/client/node'
|
||||
|
||||
// Helper function to get response JOSN body
|
||||
// Helper function to get response JSON body
|
||||
async function toJSON(response: any) {
|
||||
const chunks: any[] = []
|
||||
const text = await new Promise<string>((resolve, reject) => {
|
||||
@@ -78,3 +78,39 @@ test('Should handle http', async () => {
|
||||
const response = await clnt.get!('http://127.0.0.1:3000/ping')
|
||||
expect(await toJSON(response)).toEqual({ status: 'OK' })
|
||||
})
|
||||
|
||||
test('Should merge http.request options with config', async () => {
|
||||
let basicAuthHeader: string | null = null
|
||||
|
||||
nock('https://127.0.0.1:3000') //
|
||||
.post('/convert/html')
|
||||
.reply(200, function () {
|
||||
if (this.req.headers && this.req.headers.authorization) {
|
||||
basicAuthHeader = this.req.headers.authorization
|
||||
}
|
||||
return { status: 'OK' }
|
||||
})
|
||||
|
||||
const clnt = client({ auth: 'user:password' })
|
||||
const response = await clnt.post('https://127.0.0.1:3000/convert/html', new FormData())
|
||||
expect(await toJSON(response)).toEqual({ status: 'OK' })
|
||||
expect(basicAuthHeader).toEqual('Basic dXNlcjpwYXNzd29yZA==')
|
||||
})
|
||||
|
||||
test('Should merge headers with config', async () => {
|
||||
let tokenAuthHeader: string | null = null
|
||||
|
||||
nock('https://127.0.0.1:3000') //
|
||||
.post('/convert/html')
|
||||
.reply(200, function () {
|
||||
if (this.req.headers && this.req.headers.authorization) {
|
||||
tokenAuthHeader = this.req.headers.authorization
|
||||
}
|
||||
return { status: 'OK' }
|
||||
})
|
||||
|
||||
const clnt = client({ headers: { Authorization: 'Bearer token' } })
|
||||
const response = await clnt.post('https://127.0.0.1:3000/convert/html', new FormData())
|
||||
expect(await toJSON(response)).toEqual({ status: 'OK' })
|
||||
expect(tokenAuthHeader).toEqual('Bearer token')
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url'
|
||||
import { Readable } from 'stream'
|
||||
import {
|
||||
isBuffer,
|
||||
@@ -47,6 +48,13 @@ test('Test `isFileName` function', function () {
|
||||
expect(isFileName('index.html')).toBe(true) // <-
|
||||
expect(isFileName('test.md')).toBe(true) // <-
|
||||
expect(isFileName('image.gif')).toBe(true) // <-
|
||||
expect(isFileName('中文.gif')).toBe(true) // <-
|
||||
expect(isFileName('عربي.jpg')).toBe(true) // <-
|
||||
expect(isFileName('Screenshot 2021-12-24 at 09.16.20.png')).toBe(true) // <-
|
||||
expect(isFileName('.test.png')).toBe(true) // <-
|
||||
expect(isFileName('.png')).toBe(false)
|
||||
expect(isFileName('🙀.png')).toBe(true) // <-
|
||||
expect(isFileName('ces La esencia del cristianismo Dios es persona (jóvenes).docx')).toBe(true) // <-
|
||||
expect(isFileName(url)).toBe(false)
|
||||
expect(isFileName(array)).toBe(false)
|
||||
expect(isFileName(map)).toBe(false)
|
||||
@@ -56,6 +64,8 @@ test('Test `isFileName` function', function () {
|
||||
expect(isFileName(buffer)).toBe(false)
|
||||
expect(isFileName(generator())).toBe(false)
|
||||
expect(isFileName(iterator)).toBe(false)
|
||||
expect(isFileName('中文')).toBe(false)
|
||||
expect(isFileName('عربي')).toBe(false)
|
||||
})
|
||||
|
||||
test('Test `isFileUri` function', function () {
|
||||
@@ -83,7 +93,10 @@ test('Test `isIterable` function', function () {
|
||||
expect(isIterable(array)).toBe(true) // <-
|
||||
expect(isIterable(map)).toBe(true) // <-
|
||||
expect(isIterable(set)).toBe(true) // <-
|
||||
expect(isIterable(arguments)).toBe(true) // <-
|
||||
;(function () {
|
||||
// use new function to get empty arguments
|
||||
expect(isIterable(arguments)).toBe(true) // <-
|
||||
})()
|
||||
expect(isIterable(object)).toBe(false)
|
||||
expect(isIterable(buffer)).toBe(false)
|
||||
expect(isIterable(generator())).toBe(true) // <-
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url'
|
||||
import { Readable } from 'stream'
|
||||
import { basename } from 'path'
|
||||
import { createReadStream, ReadStream } from 'fs'
|
||||
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -0,0 +1,15 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { convert, gotenberg, landscape, pipe, please, to, url } from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 thecodingmachine/gotenberg:6
|
||||
|
||||
pipe(
|
||||
gotenberg('http://localhost:3500'),
|
||||
convert,
|
||||
url,
|
||||
to(landscape),
|
||||
please
|
||||
)('https://google.com')
|
||||
.then((pdf) => pdf.pipe(createWriteStream(`${__dirname}/google.pdf`)))
|
||||
.catch(console.error)
|
||||
@@ -0,0 +1,10 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { convert, gotenberg, html, pipe, please } from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 thecodingmachine/gotenberg:6
|
||||
;(async () => {
|
||||
const toPDF = pipe(gotenberg('http://localhost:3500'), convert, html, please)
|
||||
const pdf = await toPDF('<html><header></header><body>Make my day</body></html>')
|
||||
pdf.pipe(createWriteStream(`${__dirname}/test_issue_27.pdf`))
|
||||
})()
|
||||
@@ -0,0 +1,42 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { pipe, gotenberg, convert, url, to, please } from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 gotenberg/gotenberg:7
|
||||
|
||||
pipe(
|
||||
gotenberg(`http://localhost:3500/forms/chromium`),
|
||||
// gotenberg(`https://demo.gotenberg.dev/forms/chromium`),
|
||||
convert,
|
||||
url,
|
||||
to({ margins: [0, 0, 0, 0] }),
|
||||
(request) => {
|
||||
;(request.fields as any).url = request.fields.remoteURL
|
||||
delete request.fields.remoteURL
|
||||
return request
|
||||
},
|
||||
please
|
||||
)(`https://www.google.com`)
|
||||
.then((pdf) => pdf.pipe(createWriteStream(`${__dirname}/google.pdf`)))
|
||||
.catch(console.error)
|
||||
|
||||
// pipe(
|
||||
// gotenberg(''),
|
||||
// convert, // it is save to remove this line, if you want
|
||||
// html,
|
||||
// adjust({
|
||||
// // manually adjust endpoint, because
|
||||
// // gotenberg:7 has different conversion endpoints
|
||||
// url: 'http://localhost:3500/forms/chromium/convert/html',
|
||||
|
||||
// // manually adjust for fields
|
||||
// fields: {
|
||||
// printBackground: true,
|
||||
// // `printBackground` is not valid field for gotenberg:6
|
||||
// // so we have to cast to any, otherwise typescript will complain
|
||||
// } as any,
|
||||
// }),
|
||||
// please
|
||||
// )('<html><body bgcolor="blue">test</body></html>')
|
||||
// .then((pdf) => pdf.pipe(createWriteStream(`${__dirname}/html_with_bg.pdf`)))
|
||||
// .catch(console.error)
|
||||
@@ -0,0 +1,26 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { pipe, gotenberg, convert, html, adjust, please } from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 gotenberg/gotenberg:7
|
||||
|
||||
pipe(
|
||||
gotenberg(''),
|
||||
convert, // it is save to remove this line, if you want
|
||||
html,
|
||||
adjust({
|
||||
// manually adjust endpoint, because
|
||||
// gotenberg:7 has different conversion endpoints
|
||||
url: 'http://localhost:3500/forms/chromium/convert/html',
|
||||
|
||||
// manually adjust for fields
|
||||
fields: {
|
||||
printBackground: true,
|
||||
// `printBackground` is not valid field for gotenberg:6
|
||||
// so we have to cast to any, otherwise typescript will complain
|
||||
} as any,
|
||||
}),
|
||||
please
|
||||
)('<html><body bgcolor="blue">test</body></html>')
|
||||
.then((pdf) => pdf.pipe(createWriteStream(`${__dirname}/html_with_bg.pdf`)))
|
||||
.catch(console.error)
|
||||
@@ -0,0 +1,29 @@
|
||||
import { createWriteStream } from 'fs'
|
||||
import { pipe, gotenberg, convert, url, to, please } from '../../src'
|
||||
import got from 'got'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 gotenberg/gotenberg:7
|
||||
|
||||
// const got = await import('got')
|
||||
|
||||
// you can pass any config for your client
|
||||
// as third argument in `gotenberg` function
|
||||
const client = {
|
||||
post: (url, body, headers) => Promise.resolve(got.post({ url, body, headers, isStream: true })),
|
||||
}
|
||||
|
||||
pipe(
|
||||
gotenberg(`http://localhost:3500/forms/chromium`, client),
|
||||
convert,
|
||||
url,
|
||||
to({ margins: [0, 0, 0, 0] }),
|
||||
(request) => {
|
||||
;(request.fields as any).url = request.fields.remoteURL
|
||||
delete request.fields.remoteURL
|
||||
return request
|
||||
},
|
||||
please
|
||||
)(`https://www.google.com`)
|
||||
.then((pdf) => pdf.pipe(createWriteStream(`${__dirname}/google_via_got.pdf`)))
|
||||
.catch(console.error)
|
||||
@@ -0,0 +1,30 @@
|
||||
import fs from 'fs'
|
||||
import * as got from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 gotenberg/gotenberg:7
|
||||
|
||||
got
|
||||
.pipe(
|
||||
got.gotenberg(''),
|
||||
got.merge,
|
||||
got.adjust({
|
||||
// manually adjust endpoint, because
|
||||
// gotenberg:7 has different one
|
||||
url: 'http://localhost:3500/forms/libreoffice/convert',
|
||||
|
||||
// manually adjust for fields
|
||||
fields: {
|
||||
merge: true,
|
||||
// `merge` is not valid field for gotenberg:6
|
||||
// so we have to cast to any, otherwise typescript will complain
|
||||
} as any, // if you don't use typescript, just remove `as any` casting
|
||||
}),
|
||||
got.please
|
||||
)({
|
||||
'in1.docx': `file://${__dirname}/in1.docx`,
|
||||
'in2.docx': `file://${__dirname}/in2.docx`,
|
||||
'in3.docx': `file://${__dirname}/in3.docx`,
|
||||
})
|
||||
.then((pdf) => pdf.pipe(fs.createWriteStream(`${__dirname}/out1.pdf`)))
|
||||
.catch(console.error)
|
||||
@@ -0,0 +1,8 @@
|
||||
const { gotenberg, pipe, ping, please } = require('../../pkg')
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 thecodingmachine/gotenberg:6
|
||||
|
||||
pipe(gotenberg('http://localhost:3500'), ping, please)()
|
||||
.then(() => console.log('Gotenberg is up'))
|
||||
.catch((error) => console.error('Gotenberg is down:', error))
|
||||
@@ -0,0 +1,31 @@
|
||||
import { gotenberg, pipe, ping, please } from '../../src'
|
||||
|
||||
// need to run Gotenberg like this
|
||||
// docker run --rm -p 3500:3000 thecodingmachine/gotenberg:6
|
||||
|
||||
// pipe(
|
||||
// gotenberg('http://localhost:3000'),
|
||||
// ping,
|
||||
// please
|
||||
// )({}) // <- empty source object to satisfy typings
|
||||
// .then(() => console.log('Gotenberg is up'))
|
||||
// .catch((error) => console.error('Gotenberg is down:', error))
|
||||
|
||||
// ;(async () => {
|
||||
// try {
|
||||
// await pipe(gotenberg('http://localhost:3500'), ping, please)({})
|
||||
// console.log('Gotenberg is up')
|
||||
// } catch (error) {
|
||||
// console.error('Gotenberg is down:', error)
|
||||
// }
|
||||
// })()
|
||||
|
||||
//
|
||||
;(async () => {
|
||||
try {
|
||||
await please(ping(gotenberg('http://localhost:3500')({})))
|
||||
console.log('Gotenberg is up')
|
||||
} catch (error) {
|
||||
console.error('Gotenberg is down:', error)
|
||||
}
|
||||
})()
|
||||
+6
-2
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url'
|
||||
import { merge, Request, RequestType } from '../src'
|
||||
|
||||
// dumb object to test purity
|
||||
@@ -12,11 +13,14 @@ const dumb: Request = {
|
||||
},
|
||||
}
|
||||
|
||||
test('Should accept iterable as source', function () {
|
||||
test('Should accept iterable as source', () => {
|
||||
expect(() => merge({ ...dumb, source: [] })).not.toThrow()
|
||||
expect(() => merge({ ...dumb, source: new Map() })).not.toThrow()
|
||||
expect(() => merge({ ...dumb, source: new Set() })).not.toThrow()
|
||||
expect(() => merge({ ...dumb, source: arguments })).not.toThrow()
|
||||
;(function () {
|
||||
// use new function to get empty arguments
|
||||
expect(() => merge({ ...dumb, source: arguments })).not.toThrow()
|
||||
})()
|
||||
|
||||
function* generator() {} // tslint:disable-line no-empty
|
||||
const iterator = { [Symbol.iterator]: generator }
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { URL } from 'url'
|
||||
import { Request, RequestType, url } from '../src'
|
||||
|
||||
// dumb object to test purity
|
||||
|
||||
+3
-3
@@ -2,7 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"target": "es2020",
|
||||
"module": "esnext",
|
||||
// "lib": [], // there is problem with URL, when empty :( https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34960
|
||||
"lib": [], // there is problem with URL, when empty :( https://github.com/DefinitelyTyped/DefinitelyTyped/issues/34960
|
||||
"moduleResolution": "node",
|
||||
"esModuleInterop": true,
|
||||
"strict": true,
|
||||
@@ -13,6 +13,6 @@
|
||||
"declaration": true,
|
||||
"baseUrl": "./src"
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["**/*.spec.ts"]
|
||||
"include": ["src"],
|
||||
"exclude": ["**/node_modules", "**/.*/", "**/*.spec.ts"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user