Compare commits

..

40 Commits

Author SHA1 Message Date
Victor 6b58a8b64e Merge pull request #63 from dooafus/patch-1
Update README.md
2023-12-19 19:33:42 +03:00
dooafus 7f1513f170 Update README.md
New location of Gotenberg homepage.
2023-12-19 09:57:09 -05:00
Victor Didenko c2c64b0be9 Add manual test for issue #51 2023-06-06 23:29:28 +03:00
Victor Didenko 1cf5c7a8c0 v0.7.4 2023-06-06 23:14:27 +03:00
Victor Didenko d1d7bbb526 Loosen filename test 2023-06-06 23:07:56 +03:00
yumauri c299b63f63 v0.7.3 2022-06-26 09:51:34 +03:00
yumauri 020778a9d4 Update size-limit's limit 2022-06-26 09:37:39 +03:00
yumauri 8a15bd69c8 Update transitive dependencies 2022-06-26 09:36:40 +03:00
yumauri 2677573022 Change success conditions to any 2×× code 2022-06-26 09:31:49 +03:00
yumauri 4167947a87 Add NB in README about Gotenberg 7 usage 2022-02-18 09:59:11 +03:00
yumauri efffe0851a v0.7.2 2021-12-27 13:52:17 +03:00
yumauri 62ce10ccb1 Fix #38: allow filenames with dots 2021-12-27 13:48:20 +03:00
yumauri fc775e68bb v0.7.1 2021-11-30 20:58:44 +03:00
yumauri 9a5a0f3800 Add URL imports for Node.js 2021-11-30 20:27:40 +03:00
yumauri 97d6934bed Adjust usage example with Gotenberg:7 2021-08-27 12:36:44 +03:00
yumauri 5ac1aba6a5 Fix lint 2021-08-11 12:23:37 +03:00
yumauri d24f641667 Update "Sponsored" README part 2021-08-11 11:57:45 +03:00
yumauri 1372c90f8e Drop node 10 support 2021-05-27 10:43:48 +03:00
yumauri a9a5372d49 Update dependencies 2021-05-27 10:40:53 +03:00
yumauri ea81f28d44 Update dependencies 2021-03-13 18:25:47 +03:00
yumauri 38a379afe7 v0.7.0 2021-02-07 20:43:33 +03:00
yumauri 65abf280a8 Introduce adjust function (issue #26) 2021-02-07 20:38:49 +03:00
yumauri 70885ee8d0 Fix headers merging (issue #26) 2021-02-07 19:20:11 +03:00
yumauri cd08c5e860 Update dependencies 2021-02-07 15:07:55 +03:00
yumauri 6cc5e292ad v0.6.3 2021-01-26 19:53:29 +03:00
yumauri b863239514 Update dependencies 2021-01-26 19:49:48 +03:00
yumauri 3be519955b Extend url typings (fix issue #25) 2021-01-26 19:44:28 +03:00
yumauri 51dcdc83a9 Update dependencies 2020-12-12 14:48:49 +03:00
yumauri 5cbd477051 Add manual ping tests 2020-12-12 12:27:25 +03:00
yumauri 9dc863dc7f v0.6.2 2020-09-05 14:49:06 +03:00
yumauri ad91db0ba5 Update dependencies / Increase size-limit 2020-09-05 14:45:01 +03:00
yumauri 9b3cba6ffa Add Arabic filename to isFileName test 2020-09-05 14:40:39 +03:00
Victor 8ab830945c Merge pull request #17 from xiaojian-hong/master
Supports Chinese file name
2020-09-05 14:32:27 +03:00
Hong Xiaojian 84cc655be3 fix: disable tslint for unicode regexp 2020-09-05 18:58:28 +08:00
Hong Xiaojian ac3b76346d Update src/internal/source-checkers.ts
match any characters in any language

Co-authored-by: Victor <yumaa.verdin@gmail.com>
2020-09-05 18:22:02 +08:00
Hong Xiaojian 26520ceaf5 test: add unit test for Chinese file name 2020-09-04 16:56:39 +08:00
Hong Xiaojian 9354644f55 fix: allow Chinese file name 2020-09-04 16:17:38 +08:00
yumauri f387ee8f42 Update dependencies / Add node 14 to actions / Fix tests 2020-08-02 00:32:01 +03:00
yumauri 9755dd3336 Update dependencies 2020-07-14 14:38:26 +03:00
yumauri 0eaaf4cbfd Update dependencies 2020-06-23 15:00:19 +03:00
40 changed files with 3802 additions and 4004 deletions
+8 -20
View File
@@ -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 }}
+1
View File
@@ -10,6 +10,7 @@
"(M|m)arkdown",
"Node(js|JS)",
"TODO",
"Setplex",
"toBe",
"toThrow",
+43 -3
View File
@@ -6,8 +6,8 @@
[![NPM](https://img.shields.io/npm/v/gotenberg-js-client)](https://www.npmjs.com/package/gotenberg-js-client)
![Made with Love](https://img.shields.io/badge/made%20with-❤-red.svg)
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 -19
View File
@@ -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,34 +63,32 @@
},
"homepage": "https://github.com/yumauri/gotenberg-js-client#readme",
"dependencies": {
"detect-node": "^2.0.4",
"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/detect-node": "^2.0.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"
}
}
+4 -6
View File
@@ -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
@@ -154,7 +152,7 @@ export type ConversionOptions =
////////////////////////////////////////////////////////////////////////////////
export type FileURI = string // TODO: https://github.com/microsoft/TypeScript/issues/6579
export type PlainSource = string | Buffer | FileURI | NodeJS.ReadableStream | Blob | File
export type PlainSource = string | Buffer | FileURI | NodeJS.ReadableStream
export type TupleSource = [string, PlainSource]
export type ObjectSource = { [name: string]: PlainSource }
export type Source =
@@ -164,7 +162,7 @@ export type Source =
| ObjectSource
| Array<PlainSource | TupleSource | ObjectSource>
| Iterable<PlainSource | TupleSource | ObjectSource>
export type TupleFormSource = [string, NodeJS.ReadableStream | Blob | File]
export type TupleStreamsSource = [string, NodeJS.ReadableStream]
////////////////////////////////////////////////////////////////////////////////
/// request headers ////////////////////////////////////////////////////////////
+12 -8
View File
@@ -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])(_)
}
}
}
+29
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
import { URL } from 'url' // tslint:disable-line no-circular-imports
import { client as native } from './client/node'
import {
GotenbergClient,
+1
View File
@@ -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'
+6 -4
View File
@@ -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,
})
+14 -23
View File
@@ -1,4 +1,4 @@
import isNode from 'detect-node'
import { URL } from 'url' // tslint:disable-line no-circular-imports
import { Readable } from 'stream'
import { FileURI, ObjectSource, PlainSource, Source, TupleSource } from '../_types'
@@ -11,25 +11,13 @@ export const isString = (x: Source | undefined | null): x is string => typeof x
* Check if argument is Buffer
*/
export const isBuffer = (x: Source | undefined | null): x is Buffer =>
x != null && isNode && typeof Buffer !== 'undefined' && x instanceof Buffer // tslint:disable-line strict-type-predicates
/**
* Check if argument is Blob
*/
export const isBlob = (x: Source | undefined | null): x is Blob =>
x != null && !isNode && typeof Blob !== 'undefined' && x instanceof Blob // tslint:disable-line strict-type-predicates
/**
* Check if argument is File
*/
export const isFile = (x: Source | undefined | null): x is File =>
x != null && !isNode && typeof File !== 'undefined' && x instanceof File // tslint:disable-line strict-type-predicates
x != null && x instanceof Buffer
/**
* Check if argument is Stream
*/
export const isStream = (x: Source | undefined | null): x is NodeJS.ReadableStream =>
x != null && isNode && x instanceof Readable
x != null && x instanceof Readable
/**
* Check if argument is URL
@@ -47,7 +35,7 @@ export const isFileUri = (x: Source | undefined | null): x is FileURI =>
* Check if argument is PlainSource - either String, Stream or Buffer
*/
export const isPlain = (x: Source | undefined | null): x is PlainSource =>
isString(x) || isStream(x) || isBuffer(x) || isBlob(x) // || isFileUri(x) || isFile(x) <- redundant checks
isString(x) || isStream(x) || isBuffer(x) // || isFileUri(x) <- redundant check
/**
* Check if argument is TupleSource - two-values array, like [key, PlainSource]
@@ -93,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)
+4 -4
View File
@@ -1,7 +1,7 @@
import { basename, extname } from 'path'
import { createReadStream, ReadStream } from 'fs'
import { Readable } from 'stream'
import { FileURI, PlainSource, Source, TupleFormSource, TupleSource } from '../_types'
import { FileURI, PlainSource, Source, TupleSource, TupleStreamsSource } from '../_types'
import {
isBuffer,
isFileName,
@@ -113,12 +113,12 @@ export const toStream = (source: PlainSource): NodeJS.ReadableStream =>
})
/**
* Convert any possible source to tuples array with streams (or blobs) only
* Convert any possible source to tuples array with streams only
*/
export const toFormSources = (source?: Source): TupleFormSource[] => {
export const toStreams = (source?: Source): TupleStreamsSource[] => {
if (!source) return []
const tuples = toTuples(source)
const ret: TupleFormSource[] = []
const ret: TupleStreamsSource[] = []
for (let i = 0; i < tuples.length; i++) {
ret.push([tuples[i][0], toStream(tuples[i][1])])
}
+14 -10
View File
@@ -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,
}
}
}
+8 -5
View File
@@ -1,12 +1,12 @@
import FormData from 'form-data'
import { PingRequest, RequestFields, RequestType, TupleFormSource, TypedRequest } from './_types'
import { DEFAULT_FILENAME, toFormSources } from './internal/source-converters'
import { PingRequest, RequestFields, RequestType, TupleStreamsSource, TypedRequest } from './_types'
import { DEFAULT_FILENAME, toStreams } from './internal/source-converters'
/**
* Helper function to convert fields and files to form data
* https://github.com/form-data/form-data
*/
const formdata = (fields: RequestFields, files: TupleFormSource[]) => {
const formdata = (fields: RequestFields, files: TupleStreamsSource[]) => {
const data = new FormData()
// append all form values
@@ -31,7 +31,10 @@ const formdata = (fields: RequestFields, files: TupleFormSource[]) => {
/**
* Validate sources' file names
*/
const validateSources = (type: RequestType, sources: TupleFormSource[]): TupleFormSource[] => {
const validateSources = (
type: RequestType,
sources: TupleStreamsSource[]
): TupleStreamsSource[] => {
const filenames = sources.map((source) => source[0])
// check for duplicates
@@ -88,7 +91,7 @@ export const please: {
}
// any other conversion request
const sources = validateSources(request.type, toFormSources(request.source))
const sources = validateSources(request.type, toStreams(request.source))
const form = formdata(request.fields, sources)
return request.client.post(request.url, form, request.headers)
}
+5 -15
View File
@@ -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
View File
@@ -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
View File
@@ -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
+59
View File
@@ -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' } })
})
+37 -1
View File
@@ -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')
})
+14 -33
View File
@@ -1,8 +1,7 @@
import { URL } from 'url'
import { Readable } from 'stream'
import {
isBlob,
isBuffer,
isFile,
isFileName,
isFileUri,
isIterable,
@@ -27,21 +26,6 @@ const buffer = Buffer.from('test')
const generator = function* gen() {} // tslint:disable-line no-empty
const iterator = { [Symbol.iterator]: generator }
test('Test `isBlob` function', function () {
expect(isBlob(undefined)).toBe(false)
expect(isBlob(null)).toBe(false)
expect(isBlob(string)).toBe(false)
expect(isBlob(url)).toBe(false)
expect(isBlob(array)).toBe(false)
expect(isBlob(map)).toBe(false)
expect(isBlob(set)).toBe(false)
expect(isBlob(arguments)).toBe(false)
expect(isBlob(object)).toBe(false)
expect(isBlob(buffer)).toBe(false)
expect(isBlob(generator())).toBe(false)
expect(isBlob(iterator)).toBe(false)
})
test('Test `isBuffer` function', function () {
expect(isBuffer(undefined)).toBe(false)
expect(isBuffer(null)).toBe(false)
@@ -57,21 +41,6 @@ test('Test `isBuffer` function', function () {
expect(isBuffer(iterator)).toBe(false)
})
test('Test `isFile` function', function () {
expect(isFile(undefined)).toBe(false)
expect(isFile(null)).toBe(false)
expect(isFile(string)).toBe(false)
expect(isFile(url)).toBe(false)
expect(isFile(array)).toBe(false)
expect(isFile(map)).toBe(false)
expect(isFile(set)).toBe(false)
expect(isFile(arguments)).toBe(false)
expect(isFile(object)).toBe(false)
expect(isFile(buffer)).toBe(false)
expect(isFile(generator())).toBe(false)
expect(isFile(iterator)).toBe(false)
})
test('Test `isFileName` function', function () {
expect(isFileName(undefined)).toBe(false)
expect(isFileName(null)).toBe(false)
@@ -79,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)
@@ -88,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 () {
@@ -115,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) // <-
+5 -4
View File
@@ -1,11 +1,12 @@
import { URL } from 'url'
import { Readable } from 'stream'
import { basename } from 'path'
import { createReadStream, ReadStream } from 'fs'
import {
DEFAULT_FILENAME,
fromFile,
toFormSources,
toStream,
toStreams,
toTuples,
} from '../../src/internal/source-converters'
@@ -73,10 +74,10 @@ test('Test `toStream` function', async () => {
expect(result).toEqual('test')
})
test('Test `toFormSources` function', () => {
expect(toFormSources()).toEqual([])
test('Test `toStreams` function', () => {
expect(toStreams()).toEqual([])
const result1 = toFormSources(Buffer.from('test'))
const result1 = toStreams(Buffer.from('test'))
expect(result1 instanceof Array).toBe(true)
expect(result1[0] instanceof Array).toBe(true)
expect(result1[0][0]).toEqual(DEFAULT_FILENAME)
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+15
View File
@@ -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)
+10
View File
@@ -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`))
})()
+42
View File
@@ -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)
+26
View File
@@ -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)
+29
View File
@@ -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)
+30
View File
@@ -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)
+8
View File
@@ -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))
+31
View File
@@ -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
View File
@@ -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
View File
@@ -1,3 +1,4 @@
import { URL } from 'url'
import { Request, RequestType, url } from '../src'
// dumb object to test purity
+3 -3
View File
@@ -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"]
}
+3302 -3835
View File
File diff suppressed because it is too large Load Diff