@jsreport/jsreport-cli 3.0.0-beta.2 → 3.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (41) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +16 -12
  3. package/cli.js +93 -93
  4. package/example.config.json +43 -43
  5. package/example.server.js +14 -14
  6. package/index.js +20 -22
  7. package/jsreport.config.js +8 -8
  8. package/lib/cliExtension.js +59 -59
  9. package/lib/{createCommandParser.js → commander/createCommandParser.js} +44 -45
  10. package/lib/commander/createCustomCommandBuilder.js +150 -0
  11. package/lib/commander/createLogger.js +51 -0
  12. package/lib/commander/executeCommand.js +37 -0
  13. package/lib/commander/getCommandEventName.js +4 -0
  14. package/lib/commander/index.js +279 -0
  15. package/lib/commander/jsreportInstance.js +98 -0
  16. package/lib/commander/registerCommand.js +91 -0
  17. package/lib/commander/startCommand.js +234 -0
  18. package/lib/commander/startProcessing.js +208 -0
  19. package/lib/commands/_initializeApp.js +206 -208
  20. package/lib/commands/configure.js +393 -395
  21. package/lib/commands/help.js +453 -454
  22. package/lib/commands/init.js +76 -77
  23. package/lib/commands/kill.js +85 -86
  24. package/lib/commands/render.js +490 -488
  25. package/lib/commands/repair.js +55 -56
  26. package/lib/commands/start.js +72 -74
  27. package/lib/commands/win-install.js +124 -126
  28. package/lib/commands/win-uninstall.js +100 -102
  29. package/lib/daemonHandler.js +164 -164
  30. package/lib/daemonInstance.js +225 -225
  31. package/lib/{registerExtensionsCommands.js → detectAndRegisterExtensionsCommands.js} +74 -74
  32. package/lib/instanceHandler.js +316 -316
  33. package/lib/keepAliveProcess.js +205 -205
  34. package/lib/{errorUtils.js → utils/error.js} +149 -127
  35. package/lib/{getTempPaths.js → utils/getTempPaths.js} +39 -39
  36. package/lib/{normalizePathOptionOrArg.js → utils/normalizePathOptionOrArg.js} +41 -41
  37. package/lib/{normalizeSocketPath.js → utils/normalizeSocketPath.js} +9 -9
  38. package/lib/{startSocketServer.js → utils/startSocketServer.js} +53 -53
  39. package/package.json +105 -106
  40. package/test/testUtils.js +197 -198
  41. package/lib/commander.js +0 -1108
@@ -1,488 +1,490 @@
1
- 'use strict'
2
-
3
- const util = require('util')
4
- const fs = require('fs')
5
- const jsreportClient = require('@jsreport/client')
6
- const normalizePathOptionOrArg = require('../normalizePathOptionOrArg')
7
-
8
- const writeFileAsync = util.promisify(fs.writeFile)
9
-
10
- const description = 'Invoke a rendering process'
11
- const command = 'render'
12
-
13
- exports.command = command
14
- exports.description = description
15
-
16
- exports.builder = (yargs) => {
17
- const cwd = process.cwd()
18
-
19
- const commandOptions = {
20
- request: {
21
- alias: 'r',
22
- description: 'Specifies a path to a json file containing option for the entire rendering request',
23
- requiresArg: true,
24
- coerce: (value) => {
25
- return normalizePathOptionOrArg(cwd, 'request', value, { json: true, strict: true })
26
- }
27
- },
28
- keepAlive: {
29
- alias: 'k',
30
- description: 'Specifies that the process should stay open (handled by the maintainer) for future renders',
31
- type: 'boolean'
32
- },
33
- template: {
34
- alias: 't',
35
- description: 'Specifies a path to a json file containing options for template input or you can specify singular options doing --template.[option_name] value',
36
- requiresArg: true,
37
- coerce: (value) => {
38
- if (typeof value !== 'string') {
39
- if (value.content != null) {
40
- value.content = normalizePathOptionOrArg(cwd, 'template.content', value.content, { strict: true })
41
- }
42
-
43
- if (value.helpers != null) {
44
- value.helpers = normalizePathOptionOrArg(cwd, 'template.helpers', value.helpers, { strict: true })
45
- }
46
-
47
- return value
48
- }
49
-
50
- return normalizePathOptionOrArg(cwd, 'template', value, { json: true, strict: true })
51
- }
52
- },
53
- data: {
54
- alias: 'd',
55
- description: 'Specifies a path to a json file containing options for data input',
56
- requiresArg: true,
57
- coerce: (value) => {
58
- return normalizePathOptionOrArg(cwd, 'data', value, { json: true, strict: false })
59
- }
60
- },
61
- out: {
62
- alias: 'o',
63
- description: 'Save rendering result into a file path',
64
- type: 'string',
65
- demandOption: true,
66
- requiresArg: true,
67
- coerce: (value) => {
68
- return normalizePathOptionOrArg(cwd, 'out', value, { read: false, strict: true })
69
- }
70
- },
71
- meta: {
72
- alias: 'm',
73
- description: 'Save response meta information into a file path',
74
- type: 'string',
75
- demandOption: false,
76
- requiresArg: true,
77
- coerce: (value) => {
78
- return normalizePathOptionOrArg(cwd, 'meta', value, { read: false, strict: true })
79
- }
80
- }
81
- }
82
-
83
- const options = Object.keys(commandOptions)
84
-
85
- const examples = getExamples('jsreport ' + command)
86
-
87
- examples.forEach((examp) => {
88
- yargs.example(examp[0], examp[1])
89
- })
90
-
91
- return (
92
- yargs
93
- .usage(`${description}\n\n${getUsage('jsreport ' + command)}`)
94
- .group(options, 'Command options:')
95
- .options(commandOptions)
96
- .check((argv, hash) => {
97
- if (argv.user && !argv.serverUrl) {
98
- throw new Error('user option needs to be used with --serverUrl option')
99
- }
100
-
101
- if (argv.password && !argv.serverUrl) {
102
- throw new Error('password option needs to be used with --serverUrl option')
103
- }
104
-
105
- if (argv.user && !argv.password) {
106
- throw new Error('user option needs to be used with --password option')
107
- }
108
-
109
- if (argv.password && !argv.user) {
110
- throw new Error('password option needs to be used with --user option')
111
- }
112
-
113
- if (!argv.request && !argv.template) {
114
- throw new Error('render command need at least --request or --template option')
115
- }
116
-
117
- return true
118
- })
119
- )
120
- }
121
-
122
- exports.configuration = {
123
- globalOptions: ['serverUrl', 'user', 'password']
124
- }
125
-
126
- exports.handler = async (argv) => {
127
- const output = argv.out
128
- const meta = argv.meta
129
- const context = argv.context
130
- const logger = context.logger
131
- const verbose = argv.verbose
132
- const options = getOptions(argv)
133
-
134
- // connect to a remote server
135
- if (argv.serverUrl) {
136
- logger.info('starting rendering process in ' + argv.serverUrl + '..')
137
-
138
- try {
139
- const result = await startRender(null, {
140
- logger: logger,
141
- request: options.render,
142
- meta: meta,
143
- output: output,
144
- remote: options.remote
145
- })
146
- result.fromRemote = true
147
- return result
148
- } catch (e) {
149
- return onCriticalError(e)
150
- }
151
- }
152
-
153
- const cwd = context.cwd
154
- const sockPath = context.sockPath
155
- const workerSockPath = context.workerSockPath
156
- const getInstance = context.getInstance
157
- const initInstance = context.initInstance
158
- const daemonExec = context.daemonExec
159
- const daemonHandler = context.daemonHandler
160
- const keepAliveProcess = context.keepAliveProcess
161
- const findProcessByCWD = daemonHandler.findProcessByCWD
162
-
163
- // start a new daemonized process and then connect to it
164
- // to render
165
- if (argv.keepAlive) {
166
- logger.debug('looking for previously daemonized instance in:', workerSockPath, 'cwd:', cwd)
167
-
168
- // first, try to look up if there is an existing process
169
- // "daemonized" before in the CWD
170
- let processInfo
171
-
172
- try {
173
- processInfo = await findProcessByCWD(workerSockPath, cwd)
174
- } catch (processLookupErr) {
175
- return onCriticalError(processLookupErr)
176
- }
177
-
178
- // if process was found, just connect to it,
179
- // otherwise just continue processing
180
- if (processInfo) {
181
- logger.debug('using instance daemonized previously (pid: ' + processInfo.pid + ')..')
182
-
183
- const adminAuthentication = processInfo.adminAuthentication || {}
184
-
185
- try {
186
- const result = await startRender(null, {
187
- logger: logger,
188
- request: options.render,
189
- output: output,
190
- meta: meta,
191
- remote: {
192
- url: processInfo.url,
193
- user: adminAuthentication.username,
194
- password: adminAuthentication.password
195
- }
196
- })
197
-
198
- result.fromDaemon = true
199
- return result
200
- } catch (e) {
201
- return onCriticalError(e)
202
- }
203
- }
204
-
205
- logger.debug('there is no previously daemonized instance in:', workerSockPath, 'cwd:', cwd)
206
-
207
- let childProc
208
-
209
- try {
210
- // we try to start the daemon process
211
- processInfo = await keepAliveProcess({
212
- daemonExec: daemonExec,
213
- mainSockPath: sockPath,
214
- workerSockPath: workerSockPath,
215
- cwd: cwd,
216
- verbose: verbose
217
- })
218
-
219
- const remoteUrl = processInfo.url
220
- const adminAuthentication = processInfo.adminAuthentication || {}
221
-
222
- childProc = processInfo.proc
223
-
224
- logger.info(`instance has been daemonized and initialized successfully${childProc == null ? '*' : ''} (pid: ${processInfo.pid})`)
225
-
226
- const result = await startRender(null, {
227
- logger: logger,
228
- request: options.render,
229
- output: output,
230
- meta: meta,
231
- remote: {
232
- url: remoteUrl,
233
- user: adminAuthentication.username,
234
- password: adminAuthentication.password
235
- }
236
- })
237
-
238
- // make sure to unref() the child process after the first render
239
- // to allow the exit of the current process
240
- if (childProc) {
241
- childProc.unref()
242
- }
243
-
244
- if (childProc) {
245
- result.daemonProcess = processInfo
246
- }
247
-
248
- result.fromDaemon = true
249
-
250
- return result
251
- } catch (err) {
252
- if (childProc) {
253
- childProc.unref()
254
- }
255
-
256
- return onCriticalError(err)
257
- }
258
- }
259
-
260
- try {
261
- // look up for an instance in CWD
262
- const _instance = await getInstance(cwd)
263
- let jsreportInstance
264
-
265
- logger.debug('disabling express extension..')
266
-
267
- if (typeof _instance === 'function') {
268
- jsreportInstance = _instance()
269
- } else {
270
- jsreportInstance = _instance
271
- }
272
-
273
- jsreportInstance.options = jsreportInstance.options || {}
274
- jsreportInstance.options.extensions = jsreportInstance.options.extensions || {}
275
- jsreportInstance.options.extensions.express = Object.assign(
276
- {},
277
- jsreportInstance.options.extensions.express,
278
- { enabled: false }
279
- )
280
-
281
- await initInstance(jsreportInstance)
282
-
283
- logger.info('starting rendering process..')
284
-
285
- logger.debug('Output configured to:', output)
286
-
287
- if (meta) {
288
- logger.debug('Meta configured to:', meta)
289
- }
290
-
291
- return (await startRender(jsreportInstance, {
292
- logger: logger,
293
- request: options.render,
294
- output: output,
295
- meta: meta
296
- }))
297
- } catch (e) {
298
- return onCriticalError(e)
299
- }
300
-
301
- function onCriticalError (err) {
302
- const error = new Error(`A critical error occurred while trying to execute the ${command} command`)
303
- error.originalError = err
304
- throw error
305
- }
306
- }
307
-
308
- async function startRender (jsreportInstance, { remote, request, output, meta, logger }) {
309
- if (remote) {
310
- logger.debug('remote server options:')
311
- logger.debug(JSON.stringify(remote, null, 2))
312
- }
313
-
314
- logger.debug('rendering with options:')
315
- logger.debug(JSON.stringify(request, null, 2))
316
-
317
- if (remote) {
318
- try {
319
- const response = await jsreportClient(remote.url, remote.user, remote.password).render(request)
320
- return (await saveResponse(logger, response, response.headers, output, meta, remote))
321
- } catch (err) {
322
- let customError
323
-
324
- if (err.remoteStack) {
325
- // delete extra noise in the error message (the remoteStack is already on err.stack)
326
- delete err.remoteStack
327
- }
328
-
329
- if (err.code === 'ECONNREFUSED') {
330
- customError = new Error(`Couldn't connect to remote jsreport server in: ${
331
- remote.url
332
- } , Please verify that a jsreport server is running`)
333
- }
334
-
335
- if (!customError && err.response && err.response.statusCode != null) {
336
- if (err.response.statusCode === 404) {
337
- customError = new Error(`Couldn't connect to remote jsreport server in: ${
338
- remote.url
339
- } , Please verify that a jsreport server is running`)
340
- } else if (err.response.statusCode === 401) {
341
- customError = new Error(`Couldn't connect to remote jsreport server in: ${
342
- remote.url
343
- } , Authentication error, Please pass correct --user and --password options`)
344
- }
345
- }
346
-
347
- if (customError) {
348
- customError.originalError = err
349
- throw onRenderingError(customError, logger)
350
- }
351
-
352
- throw onRenderingError(err, logger)
353
- }
354
- }
355
-
356
- try {
357
- const out = await jsreportInstance.render(request)
358
- return (await saveResponse(logger, out.stream, out.meta, output, meta))
359
- } catch (err) {
360
- throw onRenderingError(err, logger)
361
- }
362
- }
363
-
364
- async function saveResponse (logger, stream, metaData, output, meta, remote) {
365
- const outputStream = writeFileFromStream(stream, output)
366
-
367
- // eslint-disable-next-line no-async-promise-executor
368
- return new Promise(async (resolve, reject) => {
369
- listenOutputStream(outputStream, logger, () => {
370
- if (!meta) {
371
- return resolve({
372
- output: output
373
- })
374
- }
375
-
376
- const responseMeta = JSON.stringify(remote ? responseHeadersToMetaPOCO(metaData) : metaData)
377
-
378
- logger.debug('saving response meta: ' + responseMeta)
379
-
380
- writeFileAsync(meta, responseMeta, 'utf8').then(() => {
381
- resolve({
382
- output: output,
383
- meta: meta
384
- })
385
- }).catch(reject)
386
- }, reject)
387
- })
388
- }
389
-
390
- function responseHeadersToMetaPOCO (headers) {
391
- const meta = {}
392
-
393
- for (const property in headers) {
394
- if (Object.prototype.hasOwnProperty.call(headers, property)) {
395
- const val = headers[property]
396
- let p = property.replace(/-.{1}/g, (s) => s[1].toUpperCase())
397
-
398
- p = p[0].toLowerCase() + p.substring(1)
399
- meta[p] = val
400
- }
401
- }
402
-
403
- return meta
404
- }
405
-
406
- function listenOutputStream (outputStream, logger, onFinish, onError) {
407
- let writeFinished = false
408
- let error = false
409
-
410
- outputStream.on('finish', () => {
411
- writeFinished = true
412
- })
413
-
414
- outputStream.on('close', () => {
415
- if (writeFinished && !error) {
416
- logger.info('rendering has finished successfully and saved in:', outputStream.path)
417
- onFinish()
418
- }
419
- })
420
-
421
- outputStream.on('error', (err) => {
422
- error = true
423
- onError(onRenderingError(err, logger))
424
- })
425
- }
426
-
427
- function writeFileFromStream (stream, output) {
428
- const outputStream = fs.createWriteStream(output)
429
-
430
- stream.pipe(outputStream)
431
-
432
- return outputStream
433
- }
434
-
435
- function onRenderingError (error, logger) {
436
- logger.error('rendering has finished with errors:')
437
- return error
438
- }
439
-
440
- function getOptions (argv) {
441
- const renderingOpts = argv.request || {}
442
- let remote = null
443
-
444
- if (argv.serverUrl) {
445
- remote = {
446
- url: argv.serverUrl
447
- }
448
- }
449
-
450
- if (argv.user && argv.serverUrl) {
451
- remote.user = argv.user
452
- }
453
-
454
- if (argv.password && argv.serverUrl) {
455
- remote.password = argv.password
456
- }
457
-
458
- if (argv.template) {
459
- renderingOpts.template = Object.assign({}, renderingOpts.template, argv.template)
460
- }
461
-
462
- if (argv.data) {
463
- renderingOpts.data = Object.assign({}, renderingOpts.data, argv.data)
464
- }
465
-
466
- return {
467
- render: renderingOpts,
468
- remote: remote
469
- }
470
- }
471
-
472
- function getUsage (command) {
473
- return [
474
- `Usage:\n\n${command} --request <file> --out <file>`,
475
- `${command} --template <file> --out <file>`,
476
- `${command} --template <file> --data <file> --out <file>`,
477
- `${command} --template <file> --out <file> --meta <file>`
478
- ].join('\n')
479
- }
480
-
481
- function getExamples (command) {
482
- return [
483
- [`${command} --request request.json --out output.pdf`, 'Start rendering with options in request.json'],
484
- [`${command} --template template.json --out output.pdf`, 'Start rendering with options for template input in template.json'],
485
- [`${command} --template.recipe phantom-pdf --template.engine handlebars --template.content template.html --out output.pdf`, 'Start rendering with inline options for template input'],
486
- [`${command} --template template.json --data data.json --out output.pdf`, 'Start rendering with options for template and data input']
487
- ]
488
- }
1
+ const util = require('util')
2
+ const fs = require('fs')
3
+ const jsreportClient = require('@jsreport/nodejs-client')
4
+ const normalizePathOptionOrArg = require('../utils/normalizePathOptionOrArg')
5
+
6
+ const writeFileAsync = util.promisify(fs.writeFile)
7
+
8
+ const description = 'Invoke a rendering process'
9
+ const command = 'render'
10
+
11
+ exports.command = command
12
+ exports.description = description
13
+
14
+ exports.builder = (yargs) => {
15
+ const cwd = process.cwd()
16
+
17
+ const commandOptions = {
18
+ request: {
19
+ alias: 'r',
20
+ description: 'Specifies a path to a json file containing option for the entire rendering request',
21
+ requiresArg: true
22
+ },
23
+ keepAlive: {
24
+ alias: 'k',
25
+ description: 'Specifies that the process should stay open (handled by the maintainer) for future renders',
26
+ type: 'boolean'
27
+ },
28
+ template: {
29
+ alias: 't',
30
+ description: 'Specifies a path to a json file containing options for template input or you can specify singular options doing --template.[option_name] value',
31
+ requiresArg: true
32
+ },
33
+ data: {
34
+ alias: 'd',
35
+ description: 'Specifies a path to a json file containing options for data input',
36
+ requiresArg: true
37
+ },
38
+ out: {
39
+ alias: 'o',
40
+ description: 'Save rendering result into a file path',
41
+ type: 'string',
42
+ demandOption: true,
43
+ requiresArg: true
44
+ },
45
+ meta: {
46
+ alias: 'm',
47
+ description: 'Save response meta information into a file path',
48
+ type: 'string',
49
+ demandOption: false,
50
+ requiresArg: true
51
+ }
52
+ }
53
+
54
+ const options = Object.keys(commandOptions)
55
+
56
+ const examples = getExamples('jsreport ' + command)
57
+
58
+ examples.forEach((examp) => {
59
+ yargs.example(examp[0], examp[1])
60
+ })
61
+
62
+ return (
63
+ yargs
64
+ .usage(`${description}\n\n${getUsage('jsreport ' + command)}`)
65
+ .group(options, 'Command options:')
66
+ .options(commandOptions)
67
+ .middleware((argv) => {
68
+ if (argv.request != null) {
69
+ argv.request = normalizePathOptionOrArg(cwd, 'request', argv.request, { json: true, strict: true })
70
+ }
71
+
72
+ if (argv.template != null) {
73
+ if (typeof argv.template !== 'string') {
74
+ if (argv.template.content != null) {
75
+ argv.template.content = normalizePathOptionOrArg(cwd, 'template.content', argv.template.content, { strict: true })
76
+ }
77
+
78
+ if (argv.template.helpers != null) {
79
+ argv.template.helpers = normalizePathOptionOrArg(cwd, 'template.helpers', argv.template.helpers, { strict: true })
80
+ }
81
+ } else {
82
+ argv.template = normalizePathOptionOrArg(cwd, 'template', argv.template, { json: true, strict: true })
83
+ }
84
+ }
85
+
86
+ if (argv.data != null) {
87
+ argv.data = normalizePathOptionOrArg(cwd, 'data', argv.data, { json: true, strict: false })
88
+ }
89
+
90
+ if (argv.out != null) {
91
+ argv.out = normalizePathOptionOrArg(cwd, 'out', argv.out, { read: false, strict: true })
92
+ }
93
+
94
+ if (argv.meta != null) {
95
+ argv.meta = normalizePathOptionOrArg(cwd, 'meta', argv.meta, { read: false, strict: true })
96
+ }
97
+ }, true)
98
+ .check((argv, hash) => {
99
+ if (argv.user && !argv.serverUrl) {
100
+ throw new Error('user option needs to be used with --serverUrl option')
101
+ }
102
+
103
+ if (argv.password && !argv.serverUrl) {
104
+ throw new Error('password option needs to be used with --serverUrl option')
105
+ }
106
+
107
+ if (argv.user && !argv.password) {
108
+ throw new Error('user option needs to be used with --password option')
109
+ }
110
+
111
+ if (argv.password && !argv.user) {
112
+ throw new Error('password option needs to be used with --user option')
113
+ }
114
+
115
+ if (!argv.request && !argv.template) {
116
+ throw new Error('render command need at least --request or --template option')
117
+ }
118
+
119
+ return true
120
+ })
121
+ )
122
+ }
123
+
124
+ exports.configuration = {
125
+ globalOptions: ['serverUrl', 'user', 'password']
126
+ }
127
+
128
+ exports.handler = async (argv) => {
129
+ const output = argv.out
130
+ const meta = argv.meta
131
+ const context = argv.context
132
+ const logger = context.logger
133
+ const verbose = argv.verbose
134
+ const options = getOptions(argv)
135
+
136
+ // connect to a remote server
137
+ if (argv.serverUrl) {
138
+ logger.info('starting rendering process in ' + argv.serverUrl + '..')
139
+
140
+ try {
141
+ const result = await startRender(null, {
142
+ logger: logger,
143
+ request: options.render,
144
+ meta: meta,
145
+ output: output,
146
+ remote: options.remote
147
+ })
148
+ result.fromRemote = true
149
+ return result
150
+ } catch (e) {
151
+ return onCriticalError(e)
152
+ }
153
+ }
154
+
155
+ const cwd = context.cwd
156
+ const sockPath = context.sockPath
157
+ const workerSockPath = context.workerSockPath
158
+ const getInstance = context.getInstance
159
+ const initInstance = context.initInstance
160
+ const daemonExec = context.daemonExec
161
+ const daemonHandler = context.daemonHandler
162
+ const keepAliveProcess = context.keepAliveProcess
163
+ const findProcessByCWD = daemonHandler.findProcessByCWD
164
+
165
+ // start a new daemonized process and then connect to it
166
+ // to render
167
+ if (argv.keepAlive) {
168
+ logger.debug('looking for previously daemonized instance in:', workerSockPath, 'cwd:', cwd)
169
+
170
+ // first, try to look up if there is an existing process
171
+ // "daemonized" before in the CWD
172
+ let processInfo
173
+
174
+ try {
175
+ processInfo = await findProcessByCWD(workerSockPath, cwd)
176
+ } catch (processLookupErr) {
177
+ return onCriticalError(processLookupErr)
178
+ }
179
+
180
+ // if process was found, just connect to it,
181
+ // otherwise just continue processing
182
+ if (processInfo) {
183
+ logger.debug('using instance daemonized previously (pid: ' + processInfo.pid + ')..')
184
+
185
+ const adminAuthentication = processInfo.adminAuthentication || {}
186
+
187
+ try {
188
+ const result = await startRender(null, {
189
+ logger: logger,
190
+ request: options.render,
191
+ output: output,
192
+ meta: meta,
193
+ remote: {
194
+ url: processInfo.url,
195
+ user: adminAuthentication.username,
196
+ password: adminAuthentication.password
197
+ }
198
+ })
199
+
200
+ result.fromDaemon = true
201
+ return result
202
+ } catch (e) {
203
+ return onCriticalError(e)
204
+ }
205
+ }
206
+
207
+ logger.debug('there is no previously daemonized instance in:', workerSockPath, 'cwd:', cwd)
208
+
209
+ let childProc
210
+
211
+ try {
212
+ // we try to start the daemon process
213
+ processInfo = await keepAliveProcess({
214
+ daemonExec: daemonExec,
215
+ mainSockPath: sockPath,
216
+ workerSockPath: workerSockPath,
217
+ cwd: cwd,
218
+ verbose: verbose
219
+ })
220
+
221
+ const remoteUrl = processInfo.url
222
+ const adminAuthentication = processInfo.adminAuthentication || {}
223
+
224
+ childProc = processInfo.proc
225
+
226
+ logger.info(`instance has been daemonized and initialized successfully${childProc == null ? '*' : ''} (pid: ${processInfo.pid})`)
227
+
228
+ const result = await startRender(null, {
229
+ logger: logger,
230
+ request: options.render,
231
+ output: output,
232
+ meta: meta,
233
+ remote: {
234
+ url: remoteUrl,
235
+ user: adminAuthentication.username,
236
+ password: adminAuthentication.password
237
+ }
238
+ })
239
+
240
+ // make sure to unref() the child process after the first render
241
+ // to allow the exit of the current process
242
+ if (childProc) {
243
+ childProc.unref()
244
+ }
245
+
246
+ if (childProc) {
247
+ result.daemonProcess = processInfo
248
+ }
249
+
250
+ result.fromDaemon = true
251
+
252
+ return result
253
+ } catch (err) {
254
+ if (childProc) {
255
+ childProc.unref()
256
+ }
257
+
258
+ return onCriticalError(err)
259
+ }
260
+ }
261
+
262
+ try {
263
+ // look up for an instance in CWD
264
+ const _instance = await getInstance(cwd)
265
+ let jsreportInstance
266
+
267
+ logger.debug('disabling express extension..')
268
+
269
+ if (typeof _instance === 'function') {
270
+ jsreportInstance = _instance()
271
+ } else {
272
+ jsreportInstance = _instance
273
+ }
274
+
275
+ jsreportInstance.options = jsreportInstance.options || {}
276
+ jsreportInstance.options.extensions = jsreportInstance.options.extensions || {}
277
+ jsreportInstance.options.extensions.express = Object.assign(
278
+ {},
279
+ jsreportInstance.options.extensions.express,
280
+ { enabled: false }
281
+ )
282
+
283
+ await initInstance(jsreportInstance)
284
+
285
+ logger.info('starting rendering process..')
286
+
287
+ logger.debug('Output configured to:', output)
288
+
289
+ if (meta) {
290
+ logger.debug('Meta configured to:', meta)
291
+ }
292
+
293
+ return (await startRender(jsreportInstance, {
294
+ logger: logger,
295
+ request: options.render,
296
+ output: output,
297
+ meta: meta
298
+ }))
299
+ } catch (e) {
300
+ return onCriticalError(e)
301
+ }
302
+
303
+ function onCriticalError (err) {
304
+ const error = new Error(`A critical error occurred while trying to execute the ${command} command`)
305
+ error.originalError = err
306
+ throw error
307
+ }
308
+ }
309
+
310
+ async function startRender (jsreportInstance, { remote, request, output, meta, logger }) {
311
+ if (remote) {
312
+ logger.debug('remote server options:')
313
+ logger.debug(JSON.stringify(remote, null, 2))
314
+ }
315
+
316
+ logger.debug('rendering with options:')
317
+ logger.debug(JSON.stringify(request, null, 2))
318
+
319
+ if (remote) {
320
+ try {
321
+ const response = await jsreportClient(remote.url, remote.user, remote.password).render(request)
322
+ return (await saveResponse(logger, response, response.headers, output, meta, remote))
323
+ } catch (err) {
324
+ let customError
325
+
326
+ if (err.remoteStack) {
327
+ // delete extra noise in the error message (the remoteStack is already on err.stack)
328
+ delete err.remoteStack
329
+ }
330
+
331
+ if (err.code === 'ECONNREFUSED') {
332
+ customError = new Error(`Couldn't connect to remote jsreport server in: ${
333
+ remote.url
334
+ } , Please verify that a jsreport server is running`)
335
+ }
336
+
337
+ if (!customError && err.response && err.response.statusCode != null) {
338
+ if (err.response.statusCode === 404) {
339
+ customError = new Error(`Couldn't connect to remote jsreport server in: ${
340
+ remote.url
341
+ } , Please verify that a jsreport server is running`)
342
+ } else if (err.response.statusCode === 401) {
343
+ customError = new Error(`Couldn't connect to remote jsreport server in: ${
344
+ remote.url
345
+ } , Authentication error, Please pass correct --user and --password options`)
346
+ }
347
+ }
348
+
349
+ if (customError) {
350
+ customError.originalError = err
351
+ throw onRenderingError(customError, logger)
352
+ }
353
+
354
+ throw onRenderingError(err, logger)
355
+ }
356
+ }
357
+
358
+ try {
359
+ const out = await jsreportInstance.render(request)
360
+ return (await saveResponse(logger, out.stream, out.meta, output, meta))
361
+ } catch (err) {
362
+ throw onRenderingError(err, logger)
363
+ }
364
+ }
365
+
366
+ async function saveResponse (logger, stream, metaData, output, meta, remote) {
367
+ const outputStream = writeFileFromStream(stream, output)
368
+
369
+ // eslint-disable-next-line no-async-promise-executor
370
+ return new Promise(async (resolve, reject) => {
371
+ listenOutputStream(outputStream, logger, () => {
372
+ if (!meta) {
373
+ return resolve({
374
+ output: output
375
+ })
376
+ }
377
+
378
+ const responseMeta = JSON.stringify(remote ? responseHeadersToMetaPOCO(metaData) : metaData)
379
+
380
+ logger.debug('saving response meta: ' + responseMeta)
381
+
382
+ writeFileAsync(meta, responseMeta, 'utf8').then(() => {
383
+ resolve({
384
+ output: output,
385
+ meta: meta
386
+ })
387
+ }).catch(reject)
388
+ }, reject)
389
+ })
390
+ }
391
+
392
+ function responseHeadersToMetaPOCO (headers) {
393
+ const meta = {}
394
+
395
+ for (const property in headers) {
396
+ if (Object.prototype.hasOwnProperty.call(headers, property)) {
397
+ const val = headers[property]
398
+ let p = property.replace(/-.{1}/g, (s) => s[1].toUpperCase())
399
+
400
+ p = p[0].toLowerCase() + p.substring(1)
401
+ meta[p] = val
402
+ }
403
+ }
404
+
405
+ return meta
406
+ }
407
+
408
+ function listenOutputStream (outputStream, logger, onFinish, onError) {
409
+ let writeFinished = false
410
+ let error = false
411
+
412
+ outputStream.on('finish', () => {
413
+ writeFinished = true
414
+ })
415
+
416
+ outputStream.on('close', () => {
417
+ if (writeFinished && !error) {
418
+ logger.info('rendering has finished successfully and saved in:', outputStream.path)
419
+ onFinish()
420
+ }
421
+ })
422
+
423
+ outputStream.on('error', (err) => {
424
+ error = true
425
+ onError(onRenderingError(err, logger))
426
+ })
427
+ }
428
+
429
+ function writeFileFromStream (stream, output) {
430
+ const outputStream = fs.createWriteStream(output)
431
+
432
+ stream.pipe(outputStream)
433
+
434
+ return outputStream
435
+ }
436
+
437
+ function onRenderingError (error, logger) {
438
+ logger.error('rendering has finished with errors:')
439
+ return error
440
+ }
441
+
442
+ function getOptions (argv) {
443
+ const renderingOpts = argv.request || {}
444
+ let remote = null
445
+
446
+ if (argv.serverUrl) {
447
+ remote = {
448
+ url: argv.serverUrl
449
+ }
450
+ }
451
+
452
+ if (argv.user && argv.serverUrl) {
453
+ remote.user = argv.user
454
+ }
455
+
456
+ if (argv.password && argv.serverUrl) {
457
+ remote.password = argv.password
458
+ }
459
+
460
+ if (argv.template) {
461
+ renderingOpts.template = Object.assign({}, renderingOpts.template, argv.template)
462
+ }
463
+
464
+ if (argv.data) {
465
+ renderingOpts.data = Object.assign({}, renderingOpts.data, argv.data)
466
+ }
467
+
468
+ return {
469
+ render: renderingOpts,
470
+ remote: remote
471
+ }
472
+ }
473
+
474
+ function getUsage (command) {
475
+ return [
476
+ `Usage:\n\n${command} --request <file> --out <file>`,
477
+ `${command} --template <file> --out <file>`,
478
+ `${command} --template <file> --data <file> --out <file>`,
479
+ `${command} --template <file> --out <file> --meta <file>`
480
+ ].join('\n')
481
+ }
482
+
483
+ function getExamples (command) {
484
+ return [
485
+ [`${command} --request request.json --out output.pdf`, 'Start rendering with options in request.json'],
486
+ [`${command} --template template.json --out output.pdf`, 'Start rendering with options for template input in template.json'],
487
+ [`${command} --template.recipe phantom-pdf --template.engine handlebars --template.content template.html --out output.pdf`, 'Start rendering with inline options for template input'],
488
+ [`${command} --template template.json --data data.json --out output.pdf`, 'Start rendering with options for template and data input']
489
+ ]
490
+ }