adapt-authoring-contentplugin 1.6.0 → 1.7.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.
@@ -10,7 +10,8 @@ permissions:
10
10
  issues: write
11
11
  pull-requests: write
12
12
  id-token: write
13
+ packages: write
13
14
 
14
15
  jobs:
15
16
  release:
16
- uses: adaptlearning/semantic-release-config/.github/workflows/release.yml@master
17
+ uses: adaptlearning/semantic-release-config/.github/workflows/release.yml@master
@@ -24,8 +24,10 @@ class ContentPluginModule extends AbstractApiModule {
24
24
  /** @ignore */ this.root = 'contentplugins'
25
25
  /** @ignore */ this.schemaName = 'contentplugin'
26
26
  /**
27
- * Reference to all content plugin schemas, grouped by plugin
28
- * @type {Object}
27
+ * Maps plugin name to a map of schema $anchor file path. The file path
28
+ * lets us re-register schemas after JsonSchemaModule resets the registry
29
+ * on app ready (it only re-registers schemas owned by app.dependencies).
30
+ * @type {Object<string, Object<string, string>>}
29
31
  */
30
32
  this.pluginSchemas = {}
31
33
  /**
@@ -45,7 +47,7 @@ class ContentPluginModule extends AbstractApiModule {
45
47
  if (!process.env.ADAPT_ALLOW_PRERELEASE) {
46
48
  process.env.ADAPT_ALLOW_PRERELEASE = 'true'
47
49
  }
48
- const [framework, mongodb, content] = await this.app.waitForModule('adaptframework', 'mongodb', 'content')
50
+ const [framework, jsonschema, mongodb] = await this.app.waitForModule('adaptframework', 'jsonschema', 'mongodb')
49
51
 
50
52
  await mongodb.setIndex(this.collectionName, 'name', { unique: true })
51
53
  await mongodb.setIndex(this.collectionName, 'displayName', { unique: true })
@@ -56,14 +58,20 @@ class ContentPluginModule extends AbstractApiModule {
56
58
  */
57
59
  this.framework = framework
58
60
 
61
+ // JsonSchemaModule resets the registry at app ready and only re-registers
62
+ // schemas from app.dependencies — plugin schemas would otherwise be lost.
63
+ jsonschema.registerSchemasHook.tap(() => this.reregisterPluginSchemas())
64
+
59
65
  try {
60
66
  await this.initPlugins()
61
67
  } catch (e) {
62
68
  this.log('error', e)
63
69
  }
64
- content.preInsertHook.tap((...args) => addDefaultPlugins(this, ...args))
65
70
  this.framework.postInstallHook.tap(this.syncPluginData.bind(this))
66
71
  this.framework.postUpdateHook.tap(this.syncPluginData.bind(this))
72
+ this.app.waitForModule('content').then(content => {
73
+ content.preInsertHook.tap((...args) => addDefaultPlugins(this, ...args))
74
+ })
67
75
  }
68
76
 
69
77
  /** @override */
@@ -112,8 +120,8 @@ class ContentPluginModule extends AbstractApiModule {
112
120
  const pluginData = await this.findOne({ _id })
113
121
  // unregister any schemas
114
122
  const jsonschema = await this.app.waitForModule('jsonschema')
115
- const schemas = this.pluginSchemas[pluginData.name] ?? []
116
- schemas.forEach(s => jsonschema.deregisterSchema(s))
123
+ const schemas = this.pluginSchemas[pluginData.name] ?? {}
124
+ Object.keys(schemas).forEach(s => jsonschema.deregisterSchema(s))
117
125
  delete this.pluginSchemas[pluginData.name]
118
126
 
119
127
  await this.framework.runCliCommand('uninstallPlugins', { plugins: [pluginData.name] })
@@ -211,9 +219,9 @@ class ContentPluginModule extends AbstractApiModule {
211
219
  const jsonschema = await this.app.waitForModule('jsonschema')
212
220
  return Promise.all(pluginInfo.map(async plugin => {
213
221
  const name = plugin.name
214
- const oldSchemaPaths = this.pluginSchemas[name]
215
- if (oldSchemaPaths) {
216
- Object.values(oldSchemaPaths).forEach(s => jsonschema.deregisterSchema(s))
222
+ const existing = this.pluginSchemas[name]
223
+ if (existing) {
224
+ Object.keys(existing).forEach(s => jsonschema.deregisterSchema(s))
217
225
  delete this.pluginSchemas[name]
218
226
  }
219
227
  const schemaPaths = await plugin.getSchemaPaths()
@@ -221,15 +229,31 @@ class ContentPluginModule extends AbstractApiModule {
221
229
  const schema = await this.readJson(schemaPath)
222
230
  const source = schema?.$patch?.source?.$ref
223
231
  if (source) {
224
- if (!this.pluginSchemas[name]) this.pluginSchemas[name] = []
225
- if (this.pluginSchemas[name].includes(schema.$anchor)) jsonschema.deregisterSchema(this.pluginSchemas[name][source])
226
- this.pluginSchemas[name].push(schema.$anchor)
232
+ if (!this.pluginSchemas[name]) this.pluginSchemas[name] = {}
233
+ this.pluginSchemas[name][schema.$anchor] = schemaPath
227
234
  }
228
235
  return jsonschema.registerSchema(schemaPath, { replace: true })
229
236
  }))
230
237
  }))
231
238
  }
232
239
 
240
+ /**
241
+ * Re-registers tracked plugin schemas (called via JsonSchemaModule.registerSchemasHook)
242
+ * @return {Promise}
243
+ */
244
+ async reregisterPluginSchemas () {
245
+ const jsonschema = await this.app.waitForModule('jsonschema')
246
+ for (const schemas of Object.values(this.pluginSchemas)) {
247
+ for (const schemaPath of Object.values(schemas)) {
248
+ try {
249
+ jsonschema.registerSchema(schemaPath, { replace: true })
250
+ } catch (e) {
251
+ this.log('warn', `failed to re-register plugin schema ${schemaPath}`, e)
252
+ }
253
+ }
254
+ }
255
+ }
256
+
233
257
  /**
234
258
  * Returns whether a schema is registered by a plugin
235
259
  * @param {String} schemaName Name of the schema to check
@@ -237,17 +261,17 @@ class ContentPluginModule extends AbstractApiModule {
237
261
  */
238
262
  isPluginSchema (schemaName) {
239
263
  for (const p in this.pluginSchemas) {
240
- if (this.pluginSchemas[p].includes(schemaName)) return true
264
+ if (schemaName in this.pluginSchemas[p]) return true
241
265
  }
242
266
  }
243
267
 
244
268
  /**
245
269
  * Returns all schemas registered by a plugin
246
270
  * @param {String} pluginName Plugin name
247
- * @return {Array} List of the plugin's registered schemas
271
+ * @return {Array} List of the plugin's registered schema $anchors
248
272
  */
249
273
  getPluginSchemas (pluginName) {
250
- return this.pluginSchemas[pluginName] ?? []
274
+ return Object.keys(this.pluginSchemas[pluginName] ?? {})
251
275
  }
252
276
 
253
277
  /**
@@ -391,9 +415,20 @@ class ContentPluginModule extends AbstractApiModule {
391
415
  */
392
416
  async updatePlugin (_id) {
393
417
  const [{ name }] = await this.find({ _id })
418
+ const { readFrameworkPluginVersions } = await import('adapt-authoring-adaptframework')
419
+ const fromPlugins = await readFrameworkPluginVersions(this.framework.path)
394
420
  const [pluginData] = await this.framework.runCliCommand('updatePlugins', { plugins: [name] })
395
421
  const p = await this.update({ name }, pluginData._sourceInfo)
396
422
  await this.processPluginSchemas(pluginData)
423
+ const toPlugins = await readFrameworkPluginVersions(this.framework.path)
424
+ const courses = await this.getPluginUses(_id)
425
+ if (courses.length) {
426
+ await this.framework.migrateCourses({
427
+ fromPlugins,
428
+ toPlugins,
429
+ courseIds: courses.map(c => c._id)
430
+ })
431
+ }
397
432
  this.log('info', `successfully updated plugin ${p.name}@${p.version}`)
398
433
  return p
399
434
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-contentplugin",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "description": "Module for managing framework plugins",
5
5
  "homepage": "https://github.com/adapt-security/adapt-authoring-contentplugin",
6
6
  "repository": "github:adapt-security/adapt-authoring-contentplugin",