adapt-authoring-adaptframework 2.2.0 → 2.3.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.
@@ -1,4 +1,4 @@
1
- name: Standard.js formatting check
1
+ name: Lint
2
2
  on: push
3
3
  jobs:
4
4
  default:
@@ -9,4 +9,4 @@ jobs:
9
9
  with:
10
10
  node-version: 'lts/*'
11
11
  - run: npm install
12
- - run: npx standard
12
+ - run: npx standard
@@ -804,7 +804,7 @@ class AdaptFrameworkImport {
804
804
  const schemaName = AdaptFrameworkImport.typeToSchema(data)
805
805
  const schema = await this.content.getSchema(schemaName, insertData)
806
806
  try {
807
- this.extractAssets(schema.built.properties, insertData)
807
+ this.resolveAssets(schema, insertData)
808
808
  } catch (e) {
809
809
  log('error', `failed to extract asset data for attribute '${e.attribute}' of schema '${schemaName}', ${e}`)
810
810
  }
@@ -833,27 +833,16 @@ class AdaptFrameworkImport {
833
833
  }
834
834
 
835
835
  /**
836
- * Infers the presence of any assets in incoming JSON data
837
- * @param {Object} schema Schema for the passed data
836
+ * Replaces asset paths in incoming JSON data with the _id for the corresponding asset, as used internally
837
+ * @param {Object} schema Schema instance for the passed data
838
838
  * @param {Object} data Data to check
839
839
  */
840
- extractAssets (schema, data) {
841
- if (!schema) {
842
- return
843
- }
844
- Object.entries(schema).forEach(([key, val]) => {
845
- if (data[key] === undefined) {
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
- }
840
+ resolveAssets (schema, data) {
841
+ if (!schema) return
842
+ schema.walk(data, field =>
843
+ field?._backboneForms?.type === 'Asset' || field?._backboneForms === 'Asset'
844
+ ).forEach(({ data: parent, key, value }) => {
845
+ value ? parent[key] = this.assetMap[value] ?? value : delete parent[key]
857
846
  })
858
847
  }
859
848
 
@@ -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}
@@ -252,6 +264,9 @@ class AdaptFrameworkModule extends AbstractModule {
252
264
  const builder = new AdaptFrameworkBuild(options)
253
265
  builder.preBuildHook.tap(() => this.preBuildHook.invoke(builder))
254
266
  builder.postBuildHook.tap(() => this.postBuildHook.invoke(builder))
267
+ if (this.buildHook.hasObservers) {
268
+ return this.buildHook.invoke(async () => builder.build(), builder)
269
+ }
255
270
  return builder.build()
256
271
  }
257
272
 
@@ -264,6 +279,9 @@ class AdaptFrameworkModule extends AbstractModule {
264
279
  const importer = new AdaptFrameworkImport(options)
265
280
  importer.preImportHook.tap(() => this.preImportHook.invoke(importer))
266
281
  importer.postImportHook.tap(() => this.postImportHook.invoke(importer))
282
+ if (this.importHook.hasObservers) {
283
+ return this.importHook.invoke(async () => importer.import(), importer)
284
+ }
267
285
  return importer.import()
268
286
  }
269
287
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-adaptframework",
3
- "version": "2.2.0",
3
+ "version": "2.3.1",
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.1.5",
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('#extractAssets()', () => {
151
+ describe('#resolveAssets()', () => {
152
152
  function makeCtx (assetMap) {
153
153
  const ctx = { assetMap }
154
- ctx.extractAssets = AdaptFrameworkImport.prototype.extractAssets.bind(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.extractAssets(schema, data)
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.extractAssets(schema, data)
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.extractAssets(schema, data)
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.extractAssets(schema, data)
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.extractAssets(schema, data)
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.extractAssets(schema, data)
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.extractAssets(null, { a: 1 })
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.extractAssets(schema, data)
281
+ ctx.resolveAssets(schema, data)
258
282
  assert.equal(data.hero, 'mapped')
259
283
  })
260
284
  })