@jsreport/jsreport-core 4.3.1 → 4.4.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.
package/README.md CHANGED
@@ -282,6 +282,17 @@ jsreport.documentStore.collection('templates')
282
282
 
283
283
  ## Changelog
284
284
 
285
+ ### 4.4.1
286
+
287
+ - fix timestamp shown in logging
288
+
289
+ ### 4.4.0
290
+
291
+ - trigger async report using header to avoid allocating worker
292
+ - fix require('..') and require('.') in sandbox
293
+ - fix component rendering in loop with async work on helpers
294
+ - update @jsreport/ses to 1.1.0
295
+
285
296
  ### 4.3.1
286
297
 
287
298
  - fix `waitForAsyncHelper`, `waitForAsyncHelpers` not working with trustUserCode: true
@@ -4,8 +4,17 @@ const winston = require('winston')
4
4
 
5
5
  module.exports = (options = {}) => {
6
6
  return winston.format((info) => {
7
- const { level, message, ...meta } = info
8
- info[MESSAGE] = `${options.timestamp === true ? `${new Date().toISOString()} - ` : ''}${level}: ${info.userLevel === true ? colors.cyan(message) : message}`
7
+ const { level, message, timestamp, ...meta } = info
8
+ let logDate
9
+
10
+ if (timestamp == null) {
11
+ logDate = new Date()
12
+ info.timestamp = logDate.getTime()
13
+ } else {
14
+ logDate = new Date(timestamp)
15
+ }
16
+
17
+ info[MESSAGE] = `${options.timestamp === true ? `${logDate.toISOString()} - ` : ''}${level}: ${info.userLevel === true ? colors.cyan(message) : message}`
9
18
 
10
19
  const metaKeys = Object.keys(meta)
11
20
 
@@ -1,15 +1,32 @@
1
+ const omit = require('lodash.omit')
1
2
  const winston = require('winston')
3
+ const { LEVEL, MESSAGE, SPLAT } = require('triple-beam')
2
4
  const normalizeMetaFromLogs = require('../shared/normalizeMetaFromLogs')
3
5
 
4
6
  module.exports = () => {
5
7
  return winston.format((info) => {
6
- const { level, message, ...meta } = info
7
- const newMeta = normalizeMetaFromLogs(level, message, meta)
8
+ const { level, message, timestamp: _timestamp, ...meta } = info
9
+
10
+ const symbolProps = [LEVEL, MESSAGE, SPLAT]
11
+ const originalSymbolProps = {}
12
+
13
+ for (const symbolProp of symbolProps) {
14
+ if (info[symbolProp] != null) {
15
+ originalSymbolProps[symbolProp] = info[symbolProp]
16
+ }
17
+ }
18
+
19
+ const timestamp = _timestamp ?? new Date().getTime()
20
+
21
+ const targetMeta = omit(meta, symbolProps)
22
+ const newMeta = normalizeMetaFromLogs(level, message, timestamp, targetMeta)
8
23
 
9
24
  if (newMeta != null) {
10
25
  return {
11
26
  level,
12
27
  message,
28
+ timestamp,
29
+ ...originalSymbolProps,
13
30
  ...newMeta
14
31
  }
15
32
  }
@@ -171,6 +171,7 @@ function configureLogger (logger, _transports) {
171
171
  if (options.silent) {
172
172
  continue
173
173
  }
174
+
174
175
  const transportInstance = new TransportClass(options)
175
176
 
176
177
  const existingTransport = logger.transports.find((t) => t.name === transportInstance.name)
@@ -238,7 +239,6 @@ class DebugTransport extends Transport {
238
239
 
239
240
  this.format = options.format || winston.format.combine(
240
241
  winston.format.colorize(),
241
- normalizeMetaLoggerFormat(),
242
242
  defaultLoggerFormat()
243
243
  )
244
244
 
@@ -159,6 +159,10 @@ module.exports = (reporter) => {
159
159
  reporter.beforeRenderWorkerAllocatedListeners.add('profiler', async (req) => {
160
160
  req.context.profiling = req.context.profiling || {}
161
161
 
162
+ if (req.context.profiling.enabled === false) {
163
+ return
164
+ }
165
+
162
166
  if (req.context.profiling.mode == null) {
163
167
  const profilerSettings = await reporter.settings.findValue('profiler', req)
164
168
  const defaultMode = reporter.options.profiler.defaultMode || 'standard'
@@ -382,7 +382,12 @@ class MainReporter extends Reporter {
382
382
  const res = Response(this, req.context.id)
383
383
 
384
384
  try {
385
- await this.beforeRenderWorkerAllocatedListeners.fire(req)
385
+ await this.beforeRenderWorkerAllocatedListeners.fire(req, res)
386
+ if (req.context.clientNotification) {
387
+ const r = req.context.clientNotification
388
+ delete req.context.clientNotification
389
+ return r
390
+ }
386
391
 
387
392
  worker = await this._workersManager.allocate(req, {
388
393
  timeout: this.getReportTimeout(req)
@@ -1,6 +1,8 @@
1
1
  const omit = require('lodash.omit')
2
2
 
3
- module.exports = (level, msg, meta) => {
3
+ module.exports = (level, msg, timestamp, meta) => {
4
+ let result = meta
5
+
4
6
  // detecting if meta is jsreport request object
5
7
  if (meta != null && meta.context) {
6
8
  meta.context.logs = meta.context.logs || []
@@ -8,7 +10,7 @@ module.exports = (level, msg, meta) => {
8
10
  meta.context.logs.push({
9
11
  level: level,
10
12
  message: msg,
11
- timestamp: meta.timestamp || new Date().getTime()
13
+ timestamp
12
14
  })
13
15
 
14
16
  // TODO adding cancel looks bad, its before script is adding req.cancel()
@@ -23,8 +25,8 @@ module.exports = (level, msg, meta) => {
23
25
  newMeta.id = meta.context.id
24
26
  }
25
27
 
26
- return newMeta
28
+ result = newMeta
27
29
  }
28
30
 
29
- return meta
31
+ return result != null && Object.keys(result).length > 0 ? result : null
30
32
  }
@@ -35,7 +35,7 @@ function logFn (level, profiler, ...args) {
35
35
  message: util.format.apply(util, msgArgs)
36
36
  }
37
37
 
38
- const meta = normalizeMetaFromLogs(level, log.message, lastArg)
38
+ const meta = normalizeMetaFromLogs(level, log.message, log.timestamp, lastArg)
39
39
 
40
40
  if (meta != null) {
41
41
  log.meta = meta
@@ -229,98 +229,103 @@ module.exports = (reporter) => {
229
229
  executionFinishListenersMap.set(executionId, reporter.createListenerCollection())
230
230
  executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).resolve({ require, console, topLevelFunctions, context })
231
231
 
232
- const key = engine.buildTemplateCacheKey
233
- ? engine.buildTemplateCacheKey({ content }, req)
234
- : `template:${content}:${engine.name}`
235
-
236
- if (!templatesCache.has(key)) {
237
- try {
238
- templatesCache.set(key, engine.compile(content, { require }))
239
- } catch (e) {
240
- e.property = 'content'
241
- throw e
232
+ try {
233
+ const key = engine.buildTemplateCacheKey
234
+ ? engine.buildTemplateCacheKey({ content }, req)
235
+ : `template:${content}:${engine.name}`
236
+
237
+ if (!templatesCache.has(key)) {
238
+ try {
239
+ templatesCache.set(key, engine.compile(content, { require }))
240
+ } catch (e) {
241
+ e.property = 'content'
242
+ throw e
243
+ }
242
244
  }
243
- }
244
245
 
245
- const compiledTemplate = templatesCache.get(key)
246
- const wrappedTopLevelFunctions = {}
246
+ const compiledTemplate = templatesCache.get(key)
247
+ const wrappedTopLevelFunctions = {}
247
248
 
248
- for (const h of Object.keys(topLevelFunctions)) {
249
- // extra wrapping for enhance the error with the helper name
250
- wrappedTopLevelFunctions[h] = wrapHelperForHelperNameWhenError(topLevelFunctions[h], h, () => executionFnParsedParamsMap.has(req.context.id))
249
+ for (const h of Object.keys(topLevelFunctions)) {
250
+ // extra wrapping for enhance the error with the helper name
251
+ wrappedTopLevelFunctions[h] = wrapHelperForHelperNameWhenError(topLevelFunctions[h], h, () => executionFnParsedParamsMap.has(req.context.id))
251
252
 
252
- if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
253
- wrappedTopLevelFunctions[h] = engine.wrapHelper(wrappedTopLevelFunctions[h], { context })
254
- } else {
255
- wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], asyncResultMap, asyncCallChainSet)
253
+ if (engine.getWrappingHelpersEnabled && engine.getWrappingHelpersEnabled(req) === false) {
254
+ wrappedTopLevelFunctions[h] = engine.wrapHelper(wrappedTopLevelFunctions[h], { context })
255
+ } else {
256
+ wrappedTopLevelFunctions[h] = wrapHelperForAsyncSupport(wrappedTopLevelFunctions[h], asyncResultMap, asyncCallChainSet)
257
+ }
256
258
  }
257
- }
258
-
259
- let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
260
259
 
261
- const resolvedResultsMap = new Map()
260
+ let contentResult = await engine.execute(compiledTemplate, wrappedTopLevelFunctions, data, { require })
262
261
 
263
- // we need to use the cloned map, because there can be a waitForAsyncHelper pending that needs the asyncResultMap values
264
- let clonedMap = new Map(asyncResultMap)
262
+ const resolvedResultsMap = new Map()
265
263
 
266
- while (clonedMap.size > 0) {
267
- const keysEvaluated = [...clonedMap.keys()]
264
+ // we need to use the cloned map, because there can be a waitForAsyncHelper pending that needs the asyncResultMap values
265
+ let clonedMap = new Map(asyncResultMap)
268
266
 
269
- await Promise.all(keysEvaluated.map(async (k) => {
270
- const result = await clonedMap.get(k)
271
- asyncCallChainSet.delete(k)
272
- resolvedResultsMap.set(k, `${result}`)
273
- clonedMap.delete(k)
274
- }))
267
+ while (clonedMap.size > 0) {
268
+ const keysEvaluated = [...clonedMap.keys()]
275
269
 
276
- // we need to remove the keys processed from the original map at this point
277
- // (after the await) because during the async work the asyncResultMap will be read
278
- for (const k of keysEvaluated) {
279
- asyncResultMap.delete(k)
280
- }
270
+ await Promise.all(keysEvaluated.map(async (k) => {
271
+ const result = await clonedMap.get(k)
272
+ asyncCallChainSet.delete(k)
273
+ resolvedResultsMap.set(k, `${result}`)
274
+ clonedMap.delete(k)
275
+ }))
281
276
 
282
- // we want to process the new generated pending async results
283
- if (asyncResultMap.size > 0) {
284
- clonedMap = new Map(asyncResultMap)
285
- }
286
- }
277
+ // we need to remove the keys processed from the original map at this point
278
+ // (after the await) because during the async work the asyncResultMap will be read
279
+ for (const k of keysEvaluated) {
280
+ asyncResultMap.delete(k)
281
+ }
287
282
 
288
- while (contentResult.includes('{#asyncHelperResult')) {
289
- contentResult = contentResult.replace(/{#asyncHelperResult ([^{}]+)}/g, (str, p1) => {
290
- const asyncResultId = p1
291
- // this can happen if a child jsreport.templatingEngines.evaluate receives an async value from outer scope
292
- // because every evaluate uses a unique map of async results
293
- // example is the case when component receives as a value async thing
294
- // instead of returning "undefined" we let the outer eval to do the replace
295
- if (!resolvedResultsMap.has(asyncResultId)) {
296
- // returning asyncUnresolvedHelperResult just to avoid endless loop, after replace we put it back to asyncHelperResult
297
- return `{#asyncUnresolvedHelperResult ${asyncResultId}}`
283
+ // we want to process the new generated pending async results
284
+ if (asyncResultMap.size > 0) {
285
+ clonedMap = new Map(asyncResultMap)
298
286
  }
299
- return `${resolvedResultsMap.get(asyncResultId)}`
300
- })
301
- }
287
+ }
302
288
 
303
- contentResult = contentResult.replace(/asyncUnresolvedHelperResult/g, 'asyncHelperResult')
289
+ while (contentResult.includes('{#asyncHelperResult')) {
290
+ contentResult = contentResult.replace(/{#asyncHelperResult ([^{}]+)}/g, (str, p1) => {
291
+ const asyncResultId = p1
292
+ // this can happen if a child jsreport.templatingEngines.evaluate receives an async value from outer scope
293
+ // because every evaluate uses a unique map of async results
294
+ // example is the case when component receives as a value async thing
295
+ // instead of returning "undefined" we let the outer eval to do the replace
296
+ if (!resolvedResultsMap.has(asyncResultId)) {
297
+ // returning asyncUnresolvedHelperResult just to avoid endless loop, after replace we put it back to asyncHelperResult
298
+ return `{#asyncUnresolvedHelperResult ${asyncResultId}}`
299
+ }
300
+ return `${resolvedResultsMap.get(asyncResultId)}`
301
+ })
302
+ }
304
303
 
305
- await executionFinishListenersMap.get(executionId).fire()
304
+ contentResult = contentResult.replace(/asyncUnresolvedHelperResult/g, 'asyncHelperResult')
306
305
 
307
- contextExecutionChainMap.set(sandboxId, contextExecutionChainMap.get(sandboxId).filter((id) => id !== executionId))
306
+ await executionFinishListenersMap.get(executionId).fire()
308
307
 
309
- return {
310
- // handlebars escapes single brackets before execution to prevent errors on {#asset}
311
- // we need to unescape them later here, because at the moment the engine.execute finishes
312
- // the async helpers aren't executed yet
313
- content: engine.unescape ? engine.unescape(contentResult) : contentResult
308
+ return {
309
+ // handlebars escapes single brackets before execution to prevent errors on {#asset}
310
+ // we need to unescape them later here, because at the moment the engine.execute finishes
311
+ // the async helpers aren't executed yet
312
+ content: engine.unescape ? engine.unescape(contentResult) : contentResult
313
+ }
314
+ } finally {
315
+ // ensure we clean the execution from the chain always, even on errors
316
+ contextExecutionChainMap.set(sandboxId, contextExecutionChainMap.get(sandboxId).filter((id) => id !== executionId))
314
317
  }
315
318
  }
316
319
 
317
- // executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
318
- // we store there for the particular request and component a promise and only the first component gets compiled
319
- if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
320
- const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
320
+ try {
321
+ // executionFnParsedParamsMap is there to cache parsed components helpers to speed up longer loops
322
+ // we store there for the particular request and component a promise and only the first component gets compiled
323
+ if (executionFnParsedParamsMap.get(req.context.id).has(executionFnParsedParamsKey)) {
324
+ const { require, console, topLevelFunctions, context } = await (executionFnParsedParamsMap.get(req.context.id).get(executionFnParsedParamsKey).promise)
325
+
326
+ return await executionFn({ require, console, topLevelFunctions, context })
327
+ }
321
328
 
322
- return executionFn({ require, console, topLevelFunctions, context })
323
- } else {
324
329
  const awaiter = {}
325
330
 
326
331
  awaiter.promise = new Promise((resolve) => {
@@ -328,69 +333,69 @@ module.exports = (reporter) => {
328
333
  })
329
334
 
330
335
  executionFnParsedParamsMap.get(req.context.id).set(executionFnParsedParamsKey, awaiter)
331
- }
332
336
 
333
- if (reporter.options.sandbox.cache && reporter.options.sandbox.cache.enabled === false) {
334
- templatesCache.reset()
335
- }
337
+ if (reporter.options.sandbox.cache && reporter.options.sandbox.cache.enabled === false) {
338
+ templatesCache.reset()
339
+ }
336
340
 
337
- try {
338
- return await reporter.runInSandbox({
339
- context: {
340
- ...(engine.createContext ? engine.createContext(req) : {})
341
- },
342
- userCode: normalizedHelpers,
343
- initFn,
344
- executionFn,
345
- currentPath: entityPath,
346
- onRequire: (moduleName, { context }) => {
347
- if (engine.onRequire) {
348
- return engine.onRequire(moduleName, { context })
341
+ try {
342
+ return await reporter.runInSandbox({
343
+ context: {
344
+ ...(engine.createContext ? engine.createContext(req) : {})
345
+ },
346
+ userCode: normalizedHelpers,
347
+ initFn,
348
+ executionFn,
349
+ currentPath: entityPath,
350
+ onRequire: (moduleName, { context }) => {
351
+ if (engine.onRequire) {
352
+ return engine.onRequire(moduleName, { context })
353
+ }
349
354
  }
355
+ }, req)
356
+ } catch (e) {
357
+ if (!handleErrors) {
358
+ throw e
350
359
  }
351
- }, req)
352
- } catch (e) {
353
- if (!handleErrors) {
354
- throw e
355
- }
356
360
 
357
- const nestedErrorWithEntity = e.entity != null
361
+ const nestedErrorWithEntity = e.entity != null
358
362
 
359
- const templatePath = req.template._id ? await reporter.folders.resolveEntityPath(req.template, 'templates', req) : 'anonymous'
363
+ const templatePath = req.template._id ? await reporter.folders.resolveEntityPath(req.template, 'templates', req) : 'anonymous'
360
364
 
361
- const newError = reporter.createError(`Error when evaluating engine ${engine.name} for template ${templatePath}`, { original: e })
365
+ const newError = reporter.createError(`Error when evaluating engine ${engine.name} for template ${templatePath}`, { original: e })
362
366
 
363
- if (templatePath !== 'anonymous' && !nestedErrorWithEntity) {
364
- const templateFound = await reporter.folders.resolveEntityFromPath(templatePath, 'templates', req)
367
+ if (templatePath !== 'anonymous' && !nestedErrorWithEntity) {
368
+ const templateFound = await reporter.folders.resolveEntityFromPath(templatePath, 'templates', req)
365
369
 
366
- if (templateFound != null) {
367
- newError.entity = {
368
- shortid: templateFound.entity.shortid,
369
- name: templateFound.entity.name,
370
- content
370
+ if (templateFound != null) {
371
+ newError.entity = {
372
+ shortid: templateFound.entity.shortid,
373
+ name: templateFound.entity.name,
374
+ content
375
+ }
371
376
  }
372
377
  }
373
- }
374
378
 
375
- if (!nestedErrorWithEntity && e.property !== 'content') {
376
- newError.property = 'helpers'
377
- }
379
+ if (!nestedErrorWithEntity && e.property !== 'content') {
380
+ newError.property = 'helpers'
381
+ }
378
382
 
379
- if (nestedErrorWithEntity) {
380
- // errors from nested assets evals needs an unwrap for some reason
381
- newError.entity = { ...e.entity }
382
- }
383
+ if (nestedErrorWithEntity) {
384
+ // errors from nested assets evals needs an unwrap for some reason
385
+ newError.entity = { ...e.entity }
386
+ }
383
387
 
384
- // we remove the decoratedSuffix (created from sandbox) from the stack trace (if it is there) because it
385
- // just creates noise and duplication when printing the error,
386
- // we just want the decoration on the message not in the stack trace
387
- if (e.decoratedSuffix != null && newError.stack.includes(e.decoratedSuffix)) {
388
- newError.stack = newError.stack.replace(e.decoratedSuffix, '')
389
- }
388
+ // we remove the decoratedSuffix (created from sandbox) from the stack trace (if it is there) because it
389
+ // just creates noise and duplication when printing the error,
390
+ // we just want the decoration on the message not in the stack trace
391
+ if (e.decoratedSuffix != null && newError.stack.includes(e.decoratedSuffix)) {
392
+ newError.stack = newError.stack.replace(e.decoratedSuffix, '')
393
+ }
390
394
 
391
- throw newError
395
+ throw newError
396
+ }
392
397
  } finally {
393
- if (sandboxId != null) {
398
+ if (sandboxId != null && contextExecutionChainMap.get(sandboxId)?.length === 0) {
394
399
  contextExecutionChainMap.delete(sandboxId)
395
400
  }
396
401
  }
@@ -88,7 +88,7 @@ class WorkerReporter extends Reporter {
88
88
  require('@jsreport/ses')
89
89
 
90
90
  // eslint-disable-next-line
91
- lockdown({
91
+ repairIntrinsics({
92
92
  // don't change locale based methods which users may be using in their templates
93
93
  localeTaming: 'unsafe',
94
94
  errorTaming: 'unsafe',
@@ -108,6 +108,8 @@ class WorkerReporter extends Reporter {
108
108
  overrideTaming: 'severe'
109
109
  })
110
110
 
111
+ // NOTE: we need to add these overrides between repairIntrinsics and hardenIntrinsics
112
+ // for them to be valid.
111
113
  // in this mode we alias the unsafe methods to safe ones
112
114
  Buffer.allocUnsafe = function allocUnsafe (size) {
113
115
  return Buffer.alloc(size)
@@ -117,6 +119,9 @@ class WorkerReporter extends Reporter {
117
119
  return Buffer.alloc(size)
118
120
  }
119
121
 
122
+ // eslint-disable-next-line
123
+ hardenIntrinsics()
124
+
120
125
  // we also harden Buffer because we expose it to sandbox
121
126
  // eslint-disable-next-line
122
127
  harden(Buffer)
@@ -12,7 +12,7 @@ const ISOLATED_PACKAGE_JSON_CACHE = new Map()
12
12
  // to bring isolated modules across renders and without memory leaks.
13
13
  // most of the code is copied from node.js source code and adapted a bit
14
14
  // (you will see in some parts specific links to node.js source code counterpart for reference)
15
- function isolatedRequire (_moduleId, modulesMeta, requireFromRootDirectory) {
15
+ function isolatedRequire (_moduleId, modulesMeta, resolveModule) {
16
16
  const parentModule = typeof _moduleId !== 'string' ? _moduleId.parent : null
17
17
  const moduleId = parentModule ? _moduleId.moduleId : _moduleId
18
18
 
@@ -32,10 +32,14 @@ function isolatedRequire (_moduleId, modulesMeta, requireFromRootDirectory) {
32
32
  }
33
33
 
34
34
  const { rootModule, modulesCache, requireExtensions } = modulesMeta
35
-
36
- const fullModulePath = resolveFilename(ISOLATED_REQUIRE_RESOLVE_CACHE, requireFromRootDirectory.resolve, moduleId, { parentModulePath: parentModule?.path })
35
+ const fullModulePath = resolveFilename(ISOLATED_REQUIRE_RESOLVE_CACHE, resolveModule, moduleId, { parentModulePath: parentModule?.path })
37
36
 
38
37
  if (modulesCache[fullModulePath]) {
38
+ // if module was already tried to be loaded and ended with error we rethrow the error
39
+ if (modulesCache[fullModulePath].loadingError != null) {
40
+ throw modulesCache[fullModulePath].loadingError
41
+ }
42
+
39
43
  return modulesCache[fullModulePath].exports
40
44
  }
41
45
 
@@ -50,29 +54,33 @@ function isolatedRequire (_moduleId, modulesMeta, requireFromRootDirectory) {
50
54
  // https://github.com/nodejs/node/blob/v18.14.2/lib/internal/modules/cjs/loader.js#L1133
51
55
  // we can not add this to the IsolatedModule.prototype because we need access to other variables
52
56
  mod.require = function (id) {
53
- return isolatedRequire({ parent: this, moduleId: id }, modulesMeta, requireFromRootDirectory)
57
+ return isolatedRequire({ parent: this, moduleId: id }, modulesMeta, resolveModule)
54
58
  }
55
59
 
56
60
  modulesCache[fullModulePath] = mod
57
61
 
58
- mod.filename = fullModulePath
59
- mod.paths = Module._nodeModulePaths(path.dirname(fullModulePath))
62
+ try {
63
+ mod.filename = fullModulePath
64
+ mod.paths = Module._nodeModulePaths(path.dirname(fullModulePath))
60
65
 
61
- const extension = findLongestRegisteredExtension(fullModulePath, requireExtensions)
66
+ const extension = findLongestRegisteredExtension(fullModulePath, requireExtensions)
62
67
 
63
- // https://github.com/nodejs/node/blob/v18.14.2/lib/internal/modules/cjs/loader.js#L1113
64
- // allow .mjs to be overridden
65
- if (fullModulePath.endsWith('.mjs') && !requireExtensions['.mjs']) {
66
- throw createRequireESMError(fullModulePath)
67
- }
68
-
69
- const moduleResolver = requireExtensions[extension]
68
+ // https://github.com/nodejs/node/blob/v18.14.2/lib/internal/modules/cjs/loader.js#L1113
69
+ // allow .mjs to be overridden
70
+ if (fullModulePath.endsWith('.mjs') && !requireExtensions['.mjs']) {
71
+ throw createRequireESMError(fullModulePath)
72
+ }
70
73
 
71
- moduleResolver(mod, fullModulePath)
74
+ const moduleResolver = requireExtensions[extension]
72
75
 
73
- mod.loaded = true
76
+ moduleResolver(mod, fullModulePath)
74
77
 
75
- return mod.exports
78
+ mod.loaded = true
79
+ return mod.exports
80
+ } catch (error) {
81
+ mod.loadingError = error
82
+ throw error
83
+ }
76
84
  }
77
85
 
78
86
  function setDefaultRequireExtensions (currentExtensions, requireFromRootDirectory, compileScript) {
@@ -80,11 +80,24 @@ function doRequire (moduleId, requireFromRootDirectory, _requirePaths, modulesMe
80
80
  const _require = isolateModules ? isolatedRequire : requireFromRootDirectory
81
81
  const extraRequireParams = []
82
82
 
83
- if (isolateModules) {
84
- extraRequireParams.push(modulesMeta, requireFromRootDirectory)
83
+ const resolveModule = (mId, ...args) => {
84
+ let normalizedModuleId = mId
85
+
86
+ if (normalizedModuleId === '..' || normalizedModuleId === '.') {
87
+ // NOTE: we need to manually normalize because node has a bug
88
+ // https://github.com/nodejs/node/issues/47000
89
+ // when using require.resolve and using options.paths, it does not recognize for "..", "."
90
+ // to be relative, just other cases work like "../", "..\\",
91
+ // so when we detect this case we normalize it in order for node to resolve correctly
92
+ normalizedModuleId = normalizedModuleId + path.sep
93
+ }
94
+
95
+ return requireFromRootDirectory.resolve(normalizedModuleId, ...args)
85
96
  }
86
97
 
87
- const resolveModule = requireFromRootDirectory.resolve
98
+ if (isolateModules) {
99
+ extraRequireParams.push(modulesMeta, resolveModule)
100
+ }
88
101
 
89
102
  let result = executeRequire(_require, resolveModule, moduleId, searchedPaths, ...extraRequireParams)
90
103
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@jsreport/jsreport-core",
3
- "version": "4.3.1",
3
+ "version": "4.4.1",
4
4
  "description": "javascript based business reporting",
5
5
  "keywords": [
6
6
  "report",
@@ -44,7 +44,7 @@
44
44
  "@jsreport/mingo": "2.4.1",
45
45
  "@jsreport/reap": "0.1.0",
46
46
  "@jsreport/serializator": "1.0.0",
47
- "@jsreport/ses": "1.0.1",
47
+ "@jsreport/ses": "1.1.0",
48
48
  "ajv": "6.12.6",
49
49
  "app-root-path": "3.0.0",
50
50
  "bytes": "3.1.2",
@@ -78,7 +78,7 @@
78
78
  },
79
79
  "devDependencies": {
80
80
  "@node-rs/jsonwebtoken": "0.2.0",
81
- "jsdom": "17.0.0",
81
+ "jsdom": "24.1.0",
82
82
  "mocha": "10.1.0",
83
83
  "should": "13.2.3",
84
84
  "standard": "16.0.4",