adapt-authoring-core 3.0.1 → 3.0.2

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.
@@ -68,8 +68,9 @@ class DependencyLoader {
68
68
  const deps = files
69
69
  .map(d => d.replace(`${metadataFileName}`, ''))
70
70
  .sort((a, b) => {
71
- if (a.endsWith(corePathSegment)) return -1
72
- if (b.endsWith(corePathSegment)) return 1
71
+ const aIsCore = a.endsWith(corePathSegment)
72
+ const bIsCore = b.endsWith(corePathSegment)
73
+ if (aIsCore !== bIsCore) return aIsCore ? -1 : 1
73
74
  return a.length - b.length
74
75
  })
75
76
  for (const d of deps) {
@@ -133,11 +134,14 @@ class DependencyLoader {
133
134
  if (typeof instance.onReady !== 'function') {
134
135
  throw this.app.errors.DEP_NO_ONREADY.setData({ module: modName })
135
136
  }
137
+ let timer
136
138
  try {
137
139
  const timeout = this.app.getConfig('moduleLoadTimeout') ?? 10000
138
140
  await Promise.race([
139
141
  instance.onReady(),
140
- new Promise((resolve, reject) => setTimeout(() => reject(this.app.errors.DEP_TIMEOUT.setData({ module: modName, timeout })), timeout))
142
+ new Promise((resolve, reject) => {
143
+ timer = setTimeout(() => reject(this.app.errors.DEP_TIMEOUT.setData({ module: modName, timeout })), timeout)
144
+ })
141
145
  ])
142
146
  this.instances[modName] = instance
143
147
  await this.moduleLoadedHook.invoke(null, instance)
@@ -145,6 +149,8 @@ class DependencyLoader {
145
149
  } catch (e) {
146
150
  await this.moduleLoadedHook.invoke(e, { name: modName })
147
151
  throw e
152
+ } finally {
153
+ clearTimeout(timer)
148
154
  }
149
155
  }
150
156
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-core",
3
- "version": "3.0.1",
3
+ "version": "3.0.2",
4
4
  "description": "A bundle of reusable 'core' functionality",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-core",
6
6
  "license": "GPL-3.0",
@@ -4,7 +4,8 @@ import AdaptError from '../lib/AdaptError.js'
4
4
  import DependencyLoader from '../lib/DependencyLoader.js'
5
5
  import fs from 'fs-extra'
6
6
  import path from 'path'
7
- import { fileURLToPath } from 'url'
7
+ import { fileURLToPath, pathToFileURL } from 'url'
8
+ import { setTimeout as wait } from 'timers/promises'
8
9
 
9
10
  const __dirname = path.dirname(fileURLToPath(import.meta.url))
10
11
 
@@ -132,6 +133,39 @@ describe('DependencyLoader', () => {
132
133
  })
133
134
  })
134
135
 
136
+ describe('#loadConfigs() with nested duplicate', () => {
137
+ let testRootDir
138
+
139
+ before(async () => {
140
+ testRootDir = path.join(__dirname, 'data', 'loadconfigs-nested-root')
141
+ const topCoreDir = path.join(testRootDir, 'node_modules', 'adapt-authoring-core')
142
+ const otherDir = path.join(testRootDir, 'node_modules', 'adapt-authoring-other')
143
+ const nestedCoreDir = path.join(otherDir, 'node_modules', 'adapt-authoring-core')
144
+ await fs.ensureDir(topCoreDir)
145
+ await fs.ensureDir(nestedCoreDir)
146
+ await fs.writeJson(path.join(topCoreDir, 'package.json'), { name: 'adapt-authoring-core', version: '3.0.0' })
147
+ await fs.writeJson(path.join(topCoreDir, 'adapt-authoring.json'), { module: false })
148
+ await fs.writeJson(path.join(otherDir, 'package.json'), { name: 'adapt-authoring-other' })
149
+ await fs.writeJson(path.join(otherDir, 'adapt-authoring.json'), { module: true })
150
+ await fs.writeJson(path.join(nestedCoreDir, 'package.json'), { name: 'adapt-authoring-core', version: '1.9.2' })
151
+ await fs.writeJson(path.join(nestedCoreDir, 'adapt-authoring.json'), { module: false })
152
+ })
153
+
154
+ after(async () => {
155
+ await fs.remove(testRootDir)
156
+ })
157
+
158
+ it('should prefer the top-level core over a nested duplicate', async () => {
159
+ const mockApp = { rootDir: testRootDir, name: 'adapt-authoring-core' }
160
+ const loader = new DependencyLoader(mockApp)
161
+ await loader.loadConfigs()
162
+ const winningPath = loader.configs['adapt-authoring-core'].rootDir
163
+ const expected = path.join(testRootDir, 'node_modules', 'adapt-authoring-core') + path.sep
164
+ assert.equal(winningPath, expected)
165
+ assert.equal(loader.configs['adapt-authoring-core'].version, '3.0.0')
166
+ })
167
+ })
168
+
135
169
  describe('#loadModuleConfig()', () => {
136
170
  let testModuleDir
137
171
 
@@ -346,6 +380,26 @@ describe('DependencyLoader', () => {
346
380
 
347
381
  assert.equal(loader.instances['non-module'], undefined)
348
382
  })
383
+
384
+ it('should clear the load-timeout timer once onReady resolves', async () => {
385
+ let timeoutAccessed = false
386
+ const mockApp = {
387
+ rootDir: '/test',
388
+ getConfig: () => 25,
389
+ errors: {
390
+ get DEP_TIMEOUT () { timeoutAccessed = true; return new AdaptError('DEP_TIMEOUT') }
391
+ }
392
+ }
393
+ const loader = new DependencyLoader(mockApp)
394
+ const packageName = pathToFileURL(path.join(__dirname, 'data', 'fast-module.js')).href
395
+ loader.configs = { 'fast-module': { name: 'fast-module', module: true, packageName } }
396
+
397
+ await loader.loadModule('fast-module')
398
+ await wait(75)
399
+
400
+ assert.equal(timeoutAccessed, false, 'DEP_TIMEOUT should never be accessed after a successful load')
401
+ assert.ok(loader.instances['fast-module'])
402
+ })
349
403
  })
350
404
 
351
405
  describe('#waitForModule()', () => {
@@ -0,0 +1,13 @@
1
+ export default class FastModule {
2
+ constructor (app, config) {
3
+ this.app = app
4
+ this.config = config
5
+ this.name = config.name
6
+ this._isReady = false
7
+ }
8
+
9
+ async onReady () {
10
+ this._isReady = true
11
+ return this
12
+ }
13
+ }