adapt-authoring-core 3.1.0 → 3.2.0
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/adapt-authoring.json +5 -1
- package/docs/writing-a-module.md +11 -0
- package/lib/AbstractModule.js +2 -1
- package/lib/App.js +9 -0
- package/lib/DependencyLoader.js +22 -3
- package/lib/Utils.js +1 -0
- package/lib/utils/toShortName.js +9 -0
- package/package.json +1 -1
- package/tests/DependencyLoader.spec.js +32 -0
- package/tests/utils-toShortName.spec.js +30 -0
package/adapt-authoring.json
CHANGED
|
@@ -7,17 +7,21 @@
|
|
|
7
7
|
"sourceIndex": "docs/index-backend.md",
|
|
8
8
|
"manualPages": {
|
|
9
9
|
"binscripts.md": "reference",
|
|
10
|
+
"configure-environment.md": "getting-started",
|
|
10
11
|
"contributing.md": "contributing",
|
|
11
12
|
"contributing-code.md": "contributing",
|
|
12
13
|
"coremodules.md": "reference",
|
|
13
14
|
"customising.md": "development",
|
|
15
|
+
"developer-workflow.md": "contributing",
|
|
16
|
+
"error-handling.md": "concepts",
|
|
14
17
|
"folder-structure.md": "getting-started",
|
|
15
18
|
"hooks.md": "concepts",
|
|
16
19
|
"licensing.md": "reference",
|
|
17
20
|
"releasing.md": "contributing",
|
|
18
21
|
"request-response.md": "concepts",
|
|
19
22
|
"run.md": "getting-started",
|
|
20
|
-
"writing-a-module.md": "development"
|
|
23
|
+
"writing-a-module.md": "development",
|
|
24
|
+
"writing-tests.md": "development"
|
|
21
25
|
},
|
|
22
26
|
"manualPlugins": [
|
|
23
27
|
"docs/plugins/binscripts.js",
|
package/docs/writing-a-module.md
CHANGED
|
@@ -98,6 +98,17 @@ export default class MyModule extends AbstractModule {
|
|
|
98
98
|
}
|
|
99
99
|
```
|
|
100
100
|
|
|
101
|
+
`waitForModule` rejects if the module isn't installed, so it's for **required** dependencies. For an **optional** integration, probe first with `App#isModuleAvailable` (which never throws) and only wait when it's present — don't `try/catch` `waitForModule`, as that conflates "not installed" with "installed but failed to load":
|
|
102
|
+
|
|
103
|
+
```javascript
|
|
104
|
+
if (this.app.isModuleAvailable('websocket')) {
|
|
105
|
+
const websocket = await this.app.waitForModule('websocket');
|
|
106
|
+
// wire up the optional integration
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Both accept short names (without the `adapt-authoring-` prefix).
|
|
111
|
+
|
|
101
112
|
### _Optional task: add a configuration schema_
|
|
102
113
|
|
|
103
114
|
If you plan to add user-configurable settings to your module, you can add a `config.schema.json` to define which settings users need to add. See [this page](defining-config) for more information.
|
package/lib/AbstractModule.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import Hook from './Hook.js'
|
|
2
|
+
import { toShortName } from './utils/toShortName.js'
|
|
2
3
|
/**
|
|
3
4
|
* Abstract class for authoring tool modules. All custom modules must extend this class.
|
|
4
5
|
* @memberof core
|
|
@@ -114,7 +115,7 @@ class AbstractModule {
|
|
|
114
115
|
* @param {...*} rest Arguments to log
|
|
115
116
|
*/
|
|
116
117
|
log (level, ...rest) {
|
|
117
|
-
this.app.logger?.log(level, this.name
|
|
118
|
+
this.app.logger?.log(level, toShortName(this.name), ...rest)
|
|
118
119
|
}
|
|
119
120
|
}
|
|
120
121
|
|
package/lib/App.js
CHANGED
|
@@ -146,6 +146,15 @@ class App extends AbstractModule {
|
|
|
146
146
|
const results = await Promise.all(modNames.map(m => this.dependencyloader.waitForModule(m)))
|
|
147
147
|
return results.length > 1 ? results : results[0]
|
|
148
148
|
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Whether a dependency is installed and loadable as an Adapt module. Use to guard optional integrations without throwing (`waitForModule` rejects when a module is missing).
|
|
152
|
+
* @param {string} modName Name of the module (short or full)
|
|
153
|
+
* @return {boolean}
|
|
154
|
+
*/
|
|
155
|
+
isModuleAvailable (modName) {
|
|
156
|
+
return this.dependencyloader.isAvailable(modName)
|
|
157
|
+
}
|
|
149
158
|
}
|
|
150
159
|
|
|
151
160
|
export default App
|
package/lib/DependencyLoader.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { glob } from 'glob'
|
|
2
2
|
import path from 'path'
|
|
3
3
|
import Hook from './Hook.js'
|
|
4
|
-
import { metadataFileName, packageFileName, stripScope, readJson } from './Utils.js'
|
|
4
|
+
import { metadataFileName, packageFileName, stripScope, toShortName, readJson } from './Utils.js'
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Handles the loading of Adapt authoring tool module dependencies.
|
|
@@ -179,6 +179,25 @@ class DependencyLoader {
|
|
|
179
179
|
}))
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
+
/**
|
|
183
|
+
* Resolves a module name to the canonical key used in {@link DependencyLoader#configs}: short names (without the `adapt-authoring-` prefix) are expanded to the full form. Config keys are scope-stripped at load time, so a scoped package is reached by its unscoped name.
|
|
184
|
+
* @param {string} modName Module name (short or full)
|
|
185
|
+
* @return {string} The canonical config key
|
|
186
|
+
*/
|
|
187
|
+
resolveModuleName (modName) {
|
|
188
|
+
return modName.startsWith('adapt-authoring-') ? modName : `adapt-authoring-${modName}`
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
/**
|
|
192
|
+
* Whether a dependency is installed and loadable as an Adapt module. Lets callers guard optional integrations without throwing — unlike {@link DependencyLoader#waitForModule}, which rejects when a module is missing.
|
|
193
|
+
* @param {string} modName Module name (short or full)
|
|
194
|
+
* @return {boolean} `true` when the module is present and not declared `module: false`
|
|
195
|
+
*/
|
|
196
|
+
isAvailable (modName) {
|
|
197
|
+
const config = this.configs[this.resolveModuleName(modName)]
|
|
198
|
+
return Boolean(config) && config.module !== false
|
|
199
|
+
}
|
|
200
|
+
|
|
182
201
|
/**
|
|
183
202
|
* Waits for a single module to load. Returns the instance (if loaded), or hooks into moduleLoadedHook to wait for it.
|
|
184
203
|
* @param {string} modName Name of module to wait for (accepts short names without 'adapt-authoring-' prefix)
|
|
@@ -189,7 +208,7 @@ class DependencyLoader {
|
|
|
189
208
|
if (!this._configsLoaded) {
|
|
190
209
|
await this.configsLoadedHook.onInvoke()
|
|
191
210
|
}
|
|
192
|
-
|
|
211
|
+
modName = this.resolveModuleName(modName)
|
|
193
212
|
if (!this.configs[modName]) {
|
|
194
213
|
throw this.app.errors.DEP_MISSING.setData({ module: modName })
|
|
195
214
|
}
|
|
@@ -216,7 +235,7 @@ class DependencyLoader {
|
|
|
216
235
|
logProgress (error, instance) {
|
|
217
236
|
if (error) return
|
|
218
237
|
|
|
219
|
-
const toShort = names => names.map(
|
|
238
|
+
const toShort = names => names.map(toShortName).join(', ')
|
|
220
239
|
const loaded = []
|
|
221
240
|
const notLoaded = []
|
|
222
241
|
let totalCount = 0
|
package/lib/Utils.js
CHANGED
|
@@ -9,4 +9,5 @@ export { ensureDir } from './utils/ensureDir.js'
|
|
|
9
9
|
export { escapeRegExp } from './utils/escapeRegExp.js'
|
|
10
10
|
export { stringifyValues } from './utils/stringifyValues.js'
|
|
11
11
|
export { stripScope } from './utils/stripScope.js'
|
|
12
|
+
export { toShortName } from './utils/toShortName.js'
|
|
12
13
|
export { loadDependencyFiles } from './utils/loadDependencyFiles.js'
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Strips the `adapt-authoring-` prefix from a module name to give its short, display form (e.g. 'adapt-authoring-server' becomes 'server'). Inverse of the canonicalisation in {@link DependencyLoader#resolveModuleName}.
|
|
3
|
+
* @param {string} name - The module name
|
|
4
|
+
* @returns {string} The name without the prefix
|
|
5
|
+
*/
|
|
6
|
+
export function toShortName (name) {
|
|
7
|
+
if (typeof name !== 'string') return name
|
|
8
|
+
return name.replace(/^adapt-authoring-/, '')
|
|
9
|
+
}
|
package/package.json
CHANGED
|
@@ -506,6 +506,38 @@ describe('DependencyLoader', () => {
|
|
|
506
506
|
})
|
|
507
507
|
})
|
|
508
508
|
|
|
509
|
+
describe('#resolveModuleName()', () => {
|
|
510
|
+
it('should expand a short name to the canonical key', () => {
|
|
511
|
+
const loader = new DependencyLoader({ rootDir: '/test' })
|
|
512
|
+
assert.equal(loader.resolveModuleName('server'), 'adapt-authoring-server')
|
|
513
|
+
})
|
|
514
|
+
|
|
515
|
+
it('should leave a full name unchanged', () => {
|
|
516
|
+
const loader = new DependencyLoader({ rootDir: '/test' })
|
|
517
|
+
assert.equal(loader.resolveModuleName('adapt-authoring-server'), 'adapt-authoring-server')
|
|
518
|
+
})
|
|
519
|
+
})
|
|
520
|
+
|
|
521
|
+
describe('#isAvailable()', () => {
|
|
522
|
+
it('should return true for a present loadable module (short name)', () => {
|
|
523
|
+
const loader = new DependencyLoader({ rootDir: '/test' })
|
|
524
|
+
loader.configs = { 'adapt-authoring-server': { name: 'adapt-authoring-server', module: true } }
|
|
525
|
+
assert.equal(loader.isAvailable('server'), true)
|
|
526
|
+
})
|
|
527
|
+
|
|
528
|
+
it('should return false for a missing module', () => {
|
|
529
|
+
const loader = new DependencyLoader({ rootDir: '/test' })
|
|
530
|
+
loader.configs = {}
|
|
531
|
+
assert.equal(loader.isAvailable('server'), false)
|
|
532
|
+
})
|
|
533
|
+
|
|
534
|
+
it('should return false for a dependency declared module: false', () => {
|
|
535
|
+
const loader = new DependencyLoader({ rootDir: '/test' })
|
|
536
|
+
loader.configs = { 'adapt-authoring-docs': { name: 'adapt-authoring-docs', module: false } }
|
|
537
|
+
assert.equal(loader.isAvailable('docs'), false)
|
|
538
|
+
})
|
|
539
|
+
})
|
|
540
|
+
|
|
509
541
|
describe('#logProgress()', () => {
|
|
510
542
|
it('should not throw when called with valid instance', () => {
|
|
511
543
|
const mockApp = { rootDir: '/test' }
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
|
|
4
|
+
import { toShortName } from '../lib/utils/toShortName.js'
|
|
5
|
+
|
|
6
|
+
describe('toShortName()', () => {
|
|
7
|
+
it('should strip the adapt-authoring- prefix', () => {
|
|
8
|
+
assert.equal(toShortName('adapt-authoring-server'), 'server')
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
it('should only strip an anchored prefix', () => {
|
|
12
|
+
assert.equal(toShortName('x-adapt-authoring-y'), 'x-adapt-authoring-y')
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should return a name without the prefix unchanged', () => {
|
|
16
|
+
assert.equal(toShortName('server'), 'server')
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should return an empty string unchanged', () => {
|
|
20
|
+
assert.equal(toShortName(''), '')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('should return undefined unchanged', () => {
|
|
24
|
+
assert.equal(toShortName(undefined), undefined)
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
it('should return null unchanged', () => {
|
|
28
|
+
assert.equal(toShortName(null), null)
|
|
29
|
+
})
|
|
30
|
+
})
|