adapt-authoring-adaptframework 2.2.0 → 2.3.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.
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import _ from 'lodash'
|
|
2
|
-
import { App,
|
|
2
|
+
import { App, ensureDir, writeJson } from 'adapt-authoring-core'
|
|
3
3
|
import { parseObjectId } from 'adapt-authoring-mongodb'
|
|
4
4
|
import { createWriteStream } from 'node:fs'
|
|
5
5
|
import AdaptCli from 'adapt-cli'
|
|
@@ -140,16 +140,6 @@ class AdaptFrameworkBuild {
|
|
|
140
140
|
* @type {Array<Object>}
|
|
141
141
|
*/
|
|
142
142
|
this.disabledPlugins = []
|
|
143
|
-
/**
|
|
144
|
-
* Invoked prior to a course being built.
|
|
145
|
-
* @type {Hook}
|
|
146
|
-
*/
|
|
147
|
-
this.preBuildHook = new Hook({ mutable: true })
|
|
148
|
-
/**
|
|
149
|
-
* Invoked after a course has been built.
|
|
150
|
-
* @type {Hook}
|
|
151
|
-
*/
|
|
152
|
-
this.postBuildHook = new Hook({ mutable: true })
|
|
153
143
|
}
|
|
154
144
|
|
|
155
145
|
/**
|
|
@@ -190,8 +180,6 @@ class AdaptFrameworkBuild {
|
|
|
190
180
|
linkNodeModules: !this.isExport
|
|
191
181
|
})
|
|
192
182
|
])
|
|
193
|
-
await this.preBuildHook.invoke(this)
|
|
194
|
-
|
|
195
183
|
await this.writeContentJson()
|
|
196
184
|
|
|
197
185
|
logDir('courseDir', this.courseDir)
|
|
@@ -218,8 +206,6 @@ class AdaptFrameworkBuild {
|
|
|
218
206
|
} else {
|
|
219
207
|
this.location = this.isPreview ? path.join(this.dir, 'build') : this.dir
|
|
220
208
|
}
|
|
221
|
-
await this.postBuildHook.invoke(this)
|
|
222
|
-
|
|
223
209
|
this.buildData = await this.recordBuildAttempt()
|
|
224
210
|
|
|
225
211
|
return this
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { App,
|
|
1
|
+
import { App, spawn, readJson, writeJson } from 'adapt-authoring-core'
|
|
2
2
|
import { parseObjectId } from 'adapt-authoring-mongodb'
|
|
3
3
|
import fs from 'node:fs/promises'
|
|
4
4
|
import { glob } from 'glob'
|
|
@@ -194,15 +194,6 @@ class AdaptFrameworkImport {
|
|
|
194
194
|
updatePlugins,
|
|
195
195
|
removeSource
|
|
196
196
|
}
|
|
197
|
-
/**
|
|
198
|
-
* Invoked before the import process has started
|
|
199
|
-
*/
|
|
200
|
-
this.preImportHook = new Hook()
|
|
201
|
-
/**
|
|
202
|
-
* Invoked once the import process has completed
|
|
203
|
-
*/
|
|
204
|
-
this.postImportHook = new Hook()
|
|
205
|
-
|
|
206
197
|
/**
|
|
207
198
|
* plugins on import that are of a lower version than the installed version
|
|
208
199
|
* @type {Array}
|
|
@@ -264,7 +255,6 @@ class AdaptFrameworkImport {
|
|
|
264
255
|
[this.prepare],
|
|
265
256
|
[this.loadAssetData],
|
|
266
257
|
[this.loadPluginData],
|
|
267
|
-
[() => this.preImportHook.invoke(this)],
|
|
268
258
|
[this.importTags, importContent],
|
|
269
259
|
[this.importCourseAssets, importContent],
|
|
270
260
|
[this.importCoursePlugins, isDryRun && importPlugins],
|
|
@@ -278,7 +268,6 @@ class AdaptFrameworkImport {
|
|
|
278
268
|
for (const task of tasks) {
|
|
279
269
|
if (task.length < 2 || task[1]) await task[0].call(this)
|
|
280
270
|
}
|
|
281
|
-
await this.postImportHook.invoke(this)
|
|
282
271
|
} catch (e) {
|
|
283
272
|
error = e
|
|
284
273
|
}
|
|
@@ -804,7 +793,7 @@ class AdaptFrameworkImport {
|
|
|
804
793
|
const schemaName = AdaptFrameworkImport.typeToSchema(data)
|
|
805
794
|
const schema = await this.content.getSchema(schemaName, insertData)
|
|
806
795
|
try {
|
|
807
|
-
this.
|
|
796
|
+
this.resolveAssets(schema, insertData)
|
|
808
797
|
} catch (e) {
|
|
809
798
|
log('error', `failed to extract asset data for attribute '${e.attribute}' of schema '${schemaName}', ${e}`)
|
|
810
799
|
}
|
|
@@ -833,27 +822,16 @@ class AdaptFrameworkImport {
|
|
|
833
822
|
}
|
|
834
823
|
|
|
835
824
|
/**
|
|
836
|
-
*
|
|
837
|
-
* @param {Object} schema Schema for the passed data
|
|
825
|
+
* Replaces asset paths in incoming JSON data with the _id for the corresponding asset, as used internally
|
|
826
|
+
* @param {Object} schema Schema instance for the passed data
|
|
838
827
|
* @param {Object} data Data to check
|
|
839
828
|
*/
|
|
840
|
-
|
|
841
|
-
if (!schema)
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
return
|
|
847
|
-
}
|
|
848
|
-
if (val.properties) {
|
|
849
|
-
this.extractAssets(val.properties, data[key])
|
|
850
|
-
} else if (val?.items?.properties) {
|
|
851
|
-
data[key].forEach(d => this.extractAssets(val.items.properties, d))
|
|
852
|
-
} else if (val?._backboneForms?.type === 'Asset' || val?._backboneForms === 'Asset') {
|
|
853
|
-
data[key] !== ''
|
|
854
|
-
? data[key] = this.assetMap[data[key]] ?? data[key]
|
|
855
|
-
: delete data[key]
|
|
856
|
-
}
|
|
829
|
+
resolveAssets (schema, data) {
|
|
830
|
+
if (!schema) return
|
|
831
|
+
schema.walk(data, field =>
|
|
832
|
+
field?._backboneForms?.type === 'Asset' || field?._backboneForms === 'Asset'
|
|
833
|
+
).forEach(({ data: parent, key, value }) => {
|
|
834
|
+
value ? parent[key] = this.assetMap[value] ?? value : delete parent[key]
|
|
857
835
|
})
|
|
858
836
|
}
|
|
859
837
|
|
|
@@ -52,6 +52,18 @@ class AdaptFrameworkModule extends AbstractModule {
|
|
|
52
52
|
* @type {Hook}
|
|
53
53
|
*/
|
|
54
54
|
this.postBuildHook = new Hook({ mutable: true })
|
|
55
|
+
/**
|
|
56
|
+
* Middleware hook wrapping the full import lifecycle (pre → import → post).
|
|
57
|
+
* Observers receive (next, importer) and must call next() to proceed.
|
|
58
|
+
* @type {Hook}
|
|
59
|
+
*/
|
|
60
|
+
this.importHook = new Hook({ type: Hook.Types.Middleware })
|
|
61
|
+
/**
|
|
62
|
+
* Middleware hook wrapping the full build lifecycle (pre → build → post).
|
|
63
|
+
* Observers receive (next, builder) and must call next() to proceed.
|
|
64
|
+
* @type {Hook}
|
|
65
|
+
*/
|
|
66
|
+
this.buildHook = new Hook({ type: Hook.Types.Middleware })
|
|
55
67
|
/**
|
|
56
68
|
* Content migration functions to be run on import
|
|
57
69
|
* @type {Array}
|
|
@@ -250,9 +262,16 @@ class AdaptFrameworkModule extends AbstractModule {
|
|
|
250
262
|
*/
|
|
251
263
|
async buildCourse (options) {
|
|
252
264
|
const builder = new AdaptFrameworkBuild(options)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
265
|
+
const core = async () => {
|
|
266
|
+
await this.preBuildHook.invoke(builder)
|
|
267
|
+
await builder.build()
|
|
268
|
+
await this.postBuildHook.invoke(builder)
|
|
269
|
+
return builder
|
|
270
|
+
}
|
|
271
|
+
if (this.buildHook.hasObservers) {
|
|
272
|
+
return this.buildHook.invoke(core, builder)
|
|
273
|
+
}
|
|
274
|
+
return core()
|
|
256
275
|
}
|
|
257
276
|
|
|
258
277
|
/**
|
|
@@ -262,9 +281,16 @@ class AdaptFrameworkModule extends AbstractModule {
|
|
|
262
281
|
*/
|
|
263
282
|
async importCourse (options) {
|
|
264
283
|
const importer = new AdaptFrameworkImport(options)
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
284
|
+
const core = async () => {
|
|
285
|
+
await this.preImportHook.invoke(importer)
|
|
286
|
+
await importer.import()
|
|
287
|
+
await this.postImportHook.invoke(importer)
|
|
288
|
+
return importer
|
|
289
|
+
}
|
|
290
|
+
if (this.importHook.hasObservers) {
|
|
291
|
+
return this.importHook.invoke(core, importer)
|
|
292
|
+
}
|
|
293
|
+
return core()
|
|
268
294
|
}
|
|
269
295
|
}
|
|
270
296
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "adapt-authoring-adaptframework",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.3.0",
|
|
4
4
|
"description": "Adapt framework integration for the Adapt authoring tool",
|
|
5
5
|
"homepage": "https://github.com/adapt-security/adapt-authoring-adaptframework",
|
|
6
6
|
"license": "GPL-3.0",
|
|
@@ -32,7 +32,7 @@
|
|
|
32
32
|
"peerDependencies": {
|
|
33
33
|
"adapt-authoring-assets": "^1.4.3",
|
|
34
34
|
"adapt-authoring-auth": "^2.0.0",
|
|
35
|
-
"adapt-authoring-jsonschema": "^1.
|
|
35
|
+
"adapt-authoring-jsonschema": "^1.4.0",
|
|
36
36
|
"adapt-authoring-middleware": "^1.0.1",
|
|
37
37
|
"adapt-authoring-server": "^2.1.0",
|
|
38
38
|
"adapt-authoring-tags": "^1.0.1"
|
|
@@ -148,54 +148,78 @@ describe('AdaptFrameworkImport', () => {
|
|
|
148
148
|
})
|
|
149
149
|
})
|
|
150
150
|
|
|
151
|
-
describe('#
|
|
151
|
+
describe('#resolveAssets()', () => {
|
|
152
152
|
function makeCtx (assetMap) {
|
|
153
153
|
const ctx = { assetMap }
|
|
154
|
-
ctx.
|
|
154
|
+
ctx.resolveAssets = AdaptFrameworkImport.prototype.resolveAssets.bind(ctx)
|
|
155
155
|
return ctx
|
|
156
156
|
}
|
|
157
157
|
|
|
158
|
+
function makeSchema (properties) {
|
|
159
|
+
return {
|
|
160
|
+
built: { properties },
|
|
161
|
+
walk (data, predicate, props, parentPath = '') {
|
|
162
|
+
props = props ?? this.built.properties
|
|
163
|
+
const matches = []
|
|
164
|
+
for (const [key, val] of Object.entries(props)) {
|
|
165
|
+
if (data[key] === undefined) continue
|
|
166
|
+
const currentPath = parentPath ? `${parentPath}/${key}` : key
|
|
167
|
+
if (val.properties) {
|
|
168
|
+
matches.push(...this.walk(data[key], predicate, val.properties, currentPath))
|
|
169
|
+
} else if (val?.items?.properties) {
|
|
170
|
+
data[key].forEach((item, i) => {
|
|
171
|
+
matches.push(...this.walk(item, predicate, val.items.properties, `${currentPath}/${i}`))
|
|
172
|
+
})
|
|
173
|
+
} else if (predicate(val)) {
|
|
174
|
+
matches.push({ path: currentPath, key, data, value: data[key] })
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
return matches
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
|
|
158
182
|
it('should replace asset paths with mapped IDs', () => {
|
|
159
183
|
const ctx = makeCtx({ 'course/en/assets/logo.png': 'asset123' })
|
|
160
|
-
const schema = {
|
|
184
|
+
const schema = makeSchema({
|
|
161
185
|
_graphic: {
|
|
162
186
|
properties: {
|
|
163
187
|
src: { _backboneForms: 'Asset' }
|
|
164
188
|
}
|
|
165
189
|
}
|
|
166
|
-
}
|
|
190
|
+
})
|
|
167
191
|
const data = { _graphic: { src: 'course/en/assets/logo.png' } }
|
|
168
|
-
ctx.
|
|
192
|
+
ctx.resolveAssets(schema, data)
|
|
169
193
|
assert.equal(data._graphic.src, 'asset123')
|
|
170
194
|
})
|
|
171
195
|
|
|
172
196
|
it('should delete empty string asset values', () => {
|
|
173
197
|
const ctx = makeCtx({})
|
|
174
|
-
const schema = {
|
|
198
|
+
const schema = makeSchema({
|
|
175
199
|
_graphic: {
|
|
176
200
|
properties: {
|
|
177
201
|
src: { _backboneForms: 'Asset' }
|
|
178
202
|
}
|
|
179
203
|
}
|
|
180
|
-
}
|
|
204
|
+
})
|
|
181
205
|
const data = { _graphic: { src: '' } }
|
|
182
|
-
ctx.
|
|
206
|
+
ctx.resolveAssets(schema, data)
|
|
183
207
|
assert.equal('src' in data._graphic, false)
|
|
184
208
|
})
|
|
185
209
|
|
|
186
210
|
it('should keep value when not in assetMap', () => {
|
|
187
211
|
const ctx = makeCtx({})
|
|
188
|
-
const schema = {
|
|
212
|
+
const schema = makeSchema({
|
|
189
213
|
img: { _backboneForms: { type: 'Asset' } }
|
|
190
|
-
}
|
|
214
|
+
})
|
|
191
215
|
const data = { img: 'unknown/path.png' }
|
|
192
|
-
ctx.
|
|
216
|
+
ctx.resolveAssets(schema, data)
|
|
193
217
|
assert.equal(data.img, 'unknown/path.png')
|
|
194
218
|
})
|
|
195
219
|
|
|
196
220
|
it('should recurse into nested properties', () => {
|
|
197
221
|
const ctx = makeCtx({ 'assets/bg.jpg': 'asset456' })
|
|
198
|
-
const schema = {
|
|
222
|
+
const schema = makeSchema({
|
|
199
223
|
_settings: {
|
|
200
224
|
properties: {
|
|
201
225
|
_background: {
|
|
@@ -205,15 +229,15 @@ describe('AdaptFrameworkImport', () => {
|
|
|
205
229
|
}
|
|
206
230
|
}
|
|
207
231
|
}
|
|
208
|
-
}
|
|
232
|
+
})
|
|
209
233
|
const data = { _settings: { _background: { src: 'assets/bg.jpg' } } }
|
|
210
|
-
ctx.
|
|
234
|
+
ctx.resolveAssets(schema, data)
|
|
211
235
|
assert.equal(data._settings._background.src, 'asset456')
|
|
212
236
|
})
|
|
213
237
|
|
|
214
238
|
it('should recurse into array items', () => {
|
|
215
239
|
const ctx = makeCtx({ 'assets/a.png': 'id1', 'assets/b.png': 'id2' })
|
|
216
|
-
const schema = {
|
|
240
|
+
const schema = makeSchema({
|
|
217
241
|
_items: {
|
|
218
242
|
items: {
|
|
219
243
|
properties: {
|
|
@@ -221,40 +245,40 @@ describe('AdaptFrameworkImport', () => {
|
|
|
221
245
|
}
|
|
222
246
|
}
|
|
223
247
|
}
|
|
224
|
-
}
|
|
248
|
+
})
|
|
225
249
|
const data = {
|
|
226
250
|
_items: [
|
|
227
251
|
{ src: 'assets/a.png' },
|
|
228
252
|
{ src: 'assets/b.png' }
|
|
229
253
|
]
|
|
230
254
|
}
|
|
231
|
-
ctx.
|
|
255
|
+
ctx.resolveAssets(schema, data)
|
|
232
256
|
assert.equal(data._items[0].src, 'id1')
|
|
233
257
|
assert.equal(data._items[1].src, 'id2')
|
|
234
258
|
})
|
|
235
259
|
|
|
236
260
|
it('should skip undefined data keys', () => {
|
|
237
261
|
const ctx = makeCtx({})
|
|
238
|
-
const schema = {
|
|
262
|
+
const schema = makeSchema({
|
|
239
263
|
_graphic: { _backboneForms: 'Asset' }
|
|
240
|
-
}
|
|
264
|
+
})
|
|
241
265
|
const data = {}
|
|
242
|
-
ctx.
|
|
266
|
+
ctx.resolveAssets(schema, data)
|
|
243
267
|
assert.equal('_graphic' in data, false)
|
|
244
268
|
})
|
|
245
269
|
|
|
246
270
|
it('should handle null schema gracefully', () => {
|
|
247
271
|
const ctx = makeCtx({})
|
|
248
|
-
ctx.
|
|
272
|
+
ctx.resolveAssets(null, { a: 1 })
|
|
249
273
|
})
|
|
250
274
|
|
|
251
275
|
it('should handle _backboneForms as object with type', () => {
|
|
252
276
|
const ctx = makeCtx({ 'path/img.png': 'mapped' })
|
|
253
|
-
const schema = {
|
|
277
|
+
const schema = makeSchema({
|
|
254
278
|
hero: { _backboneForms: { type: 'Asset' } }
|
|
255
|
-
}
|
|
279
|
+
})
|
|
256
280
|
const data = { hero: 'path/img.png' }
|
|
257
|
-
ctx.
|
|
281
|
+
ctx.resolveAssets(schema, data)
|
|
258
282
|
assert.equal(data.hero, 'mapped')
|
|
259
283
|
})
|
|
260
284
|
})
|