adapt-authoring-adaptframework 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.
@@ -40,6 +40,7 @@ class AdaptFrameworkBuild {
40
40
  * @property {String} courseId The course to build
41
41
  * @property {String} userId The user executing the build
42
42
  * @property {String} expiresAt When the build expires
43
+ * @property {Boolean} compress Whether output files should be compressed into an archive file
43
44
  *
44
45
  * @constructor
45
46
  * @param {AdaptFrameworkBuildOptions} options
@@ -4,6 +4,7 @@ import { glob } from 'glob'
4
4
  import octopus from 'adapt-octopus'
5
5
  import path from 'upath'
6
6
  import semver from 'semver'
7
+ import { unzip } from 'zipper'
7
8
 
8
9
  import FWUtils from './AdaptFrameworkUtils.js'
9
10
 
@@ -61,7 +62,7 @@ class AdaptFrameworkImport {
61
62
  /**
62
63
  * Options to be passed to AdaptFrameworkBuild
63
64
  * @typedef {Object} AdaptFrameworkImportOptions
64
- * @property {String} unzipPath Path to unzipped import
65
+ * @property {String} importPath Path to the import package
65
66
  * @property {String} userId Owner of the imported course
66
67
  * @property {String} language Language of course files to be imported (if not specified, _defaultLanguage will be used)
67
68
  * @property {Array<String>} assetFolders List of non-standard asset directories
@@ -75,124 +76,122 @@ class AdaptFrameworkImport {
75
76
  * @constructor
76
77
  * @param {AdaptFrameworkImportOptions} options
77
78
  */
78
- constructor ({ unzipPath, userId, language, assetFolders, tags, isDryRun, importContent = true, importPlugins = true, migrateContent = true, updatePlugins = false, removeSource = true }) {
79
- try {
80
- if (!unzipPath || !userId) throw new Error()
81
- /**
82
- * Reference to the package.json data
83
- * @type {Object}
84
- */
85
- this.pkg = undefined
86
- /**
87
- * Path that the import will be unzipped to
88
- * @type {String}
89
- */
90
- this.unzipPath = unzipPath.replaceAll('\\', '/')
91
- /**
92
- * Language code for the langauge to import. Used for locating course content.
93
- * @type {String}
94
- */
95
- this.language = language
96
- /**
97
- * List of asset folders to check
98
- * @type {Array<String>}
99
- */
100
- this.assetFolders = assetFolders ?? ['assets']
101
- /**
102
- * List of asset metadata
103
- * @type {Array}
104
- */
105
- this.assetData = []
106
- /**
107
- * List of tags to apply to the course
108
- * @type {Array<String>}
109
- */
110
- this.tags = tags
111
- /**
112
- * Path to the import course folder
113
- * @type {String}
114
- */
115
- this.coursePath = undefined
116
- /**
117
- * Path to the import course language folder
118
- * @type {String}
119
- */
120
- this.langPath = undefined
121
- /**
122
- * A cache of the import's content JSON file data (note this is not the DB data used by the application)
123
- * @type {Object}
124
- */
125
- this.contentJson = {
126
- course: {},
127
- contentObjects: []
128
- }
129
- /**
130
- * Key/value store of the installed content plugins
131
- * @type {Object}
132
- */
133
- this.usedContentPlugins = {}
134
- /**
135
- * All plugins installed during the import as a name -> metadata map
136
- * @type {Object}
137
- */
138
- this.newContentPlugins = {}
139
- /**
140
- * Key/value store mapping old component keys to component names
141
- * @type {Object}
142
- */
143
- this.componentNameMap = {}
144
- /**
145
- * A key/value map of asset file names to new asset ids
146
- * @type {Object}
147
- */
148
- this.assetMap = {}
149
- /**
150
- * A key/value map of old ids to new ids
151
- * @type {Object}
152
- */
153
- this.idMap = {}
154
- /**
155
- * The _id of the user initiating the import
156
- * @type {String}
157
- */
158
- this.userId = userId
159
- /**
160
- * Contains non-fatal infomation messages regarding import status which can be return as response data. Fatal errors are thrown in the usual way.
161
- * @type {Object}
162
- */
163
- this.statusReport = {
164
- info: [],
165
- warn: []
166
- }
167
- /**
168
- * User-defined settings related to what is included with the import
169
- * @type {Object}
170
- */
171
- this.settings = {
172
- isDryRun,
173
- importContent,
174
- importPlugins,
175
- migrateContent,
176
- updatePlugins,
177
- removeSource
178
- }
179
- /**
180
- * Invoked before the import process has started
181
- */
182
- this.preImportHook = new Hook()
183
- /**
184
- * Invoked once the import process has completed
185
- */
186
- this.postImportHook = new Hook()
187
-
188
- /**
189
- * plugins on import that are of a lower version than the installed version
190
- * @type {Array}
191
- */
192
- this.pluginsToMigrate = ['core']
193
- } catch (e) {
194
- throw App.instance.errors.FW_IMPORT_INVALID_COURSE
79
+ constructor ({ importPath, userId, language, assetFolders, tags, isDryRun, importContent = true, importPlugins = true, migrateContent = true, updatePlugins = false, removeSource = true }) {
80
+ const e = App.instance.errors.INVALID_PARAMS
81
+ if (!importPath) throw e.setData({ params: ['importPath'] })
82
+ if (!userId) throw e.INVALID_PARAMS.setData({ params: ['userId'] })
83
+ /**
84
+ * Reference to the package.json data
85
+ * @type {Object}
86
+ */
87
+ this.pkg = undefined
88
+ /**
89
+ * Path of the import package
90
+ * @type {String}
91
+ */
92
+ this.path = importPath.replaceAll('\\', '/')
93
+ /**
94
+ * Language code for the langauge to import. Used for locating course content.
95
+ * @type {String}
96
+ */
97
+ this.language = language
98
+ /**
99
+ * List of asset folders to check
100
+ * @type {Array<String>}
101
+ */
102
+ this.assetFolders = assetFolders ?? ['assets']
103
+ /**
104
+ * List of asset metadata
105
+ * @type {Array}
106
+ */
107
+ this.assetData = []
108
+ /**
109
+ * List of tags to apply to the course
110
+ * @type {Array<String>}
111
+ */
112
+ this.tags = tags
113
+ /**
114
+ * Path to the import course folder
115
+ * @type {String}
116
+ */
117
+ this.coursePath = undefined
118
+ /**
119
+ * Path to the import course language folder
120
+ * @type {String}
121
+ */
122
+ this.langPath = undefined
123
+ /**
124
+ * A cache of the import's content JSON file data (note this is not the DB data used by the application)
125
+ * @type {Object}
126
+ */
127
+ this.contentJson = {
128
+ course: {},
129
+ contentObjects: []
130
+ }
131
+ /**
132
+ * Key/value store of the installed content plugins
133
+ * @type {Object}
134
+ */
135
+ this.usedContentPlugins = {}
136
+ /**
137
+ * All plugins installed during the import as a name -> metadata map
138
+ * @type {Object}
139
+ */
140
+ this.newContentPlugins = {}
141
+ /**
142
+ * Key/value store mapping old component keys to component names
143
+ * @type {Object}
144
+ */
145
+ this.componentNameMap = {}
146
+ /**
147
+ * A key/value map of asset file names to new asset ids
148
+ * @type {Object}
149
+ */
150
+ this.assetMap = {}
151
+ /**
152
+ * A key/value map of old ids to new ids
153
+ * @type {Object}
154
+ */
155
+ this.idMap = {}
156
+ /**
157
+ * The _id of the user initiating the import
158
+ * @type {String}
159
+ */
160
+ this.userId = userId
161
+ /**
162
+ * Contains non-fatal infomation messages regarding import status which can be return as response data. Fatal errors are thrown in the usual way.
163
+ * @type {Object}
164
+ */
165
+ this.statusReport = {
166
+ info: [],
167
+ warn: []
195
168
  }
169
+ /**
170
+ * User-defined settings related to what is included with the import
171
+ * @type {Object}
172
+ */
173
+ this.settings = {
174
+ isDryRun,
175
+ importContent,
176
+ importPlugins,
177
+ migrateContent,
178
+ updatePlugins,
179
+ removeSource
180
+ }
181
+ /**
182
+ * Invoked before the import process has started
183
+ */
184
+ this.preImportHook = new Hook()
185
+ /**
186
+ * Invoked once the import process has completed
187
+ */
188
+ this.postImportHook = new Hook()
189
+
190
+ /**
191
+ * plugins on import that are of a lower version than the installed version
192
+ * @type {Array}
193
+ */
194
+ this.pluginsToMigrate = ['core']
196
195
  }
197
196
 
198
197
  /**
@@ -277,21 +276,24 @@ class AdaptFrameworkImport {
277
276
  * @return {Promise}
278
277
  */
279
278
  async prepare () {
279
+ if (this.path.endsWith('.zip')) {
280
+ this.path = await unzip(this.path, `${this.path}_unzip`, { removeSource: true })
281
+ }
280
282
  try { // if it's a nested zip, move everything up a level
281
- const files = await fs.readdir(this.unzipPath)
283
+ const files = await fs.readdir(this.path)
282
284
  if (files.length === 1) {
283
- const nestDir = `${this.unzipPath}/${files[0]}`
285
+ const nestDir = `${this.path}/${files[0]}`
284
286
  await fs.stat(`${nestDir}/package.json`)
285
- const newDir = path.join(`${this.unzipPath}_2`)
287
+ const newDir = path.join(`${this.path}_2`)
286
288
  await fs.rename(nestDir, newDir)
287
- await fs.rm(this.unzipPath, { recursive: true })
288
- this.unzipPath = newDir
289
+ await fs.rm(this.path, { recursive: true })
290
+ this.path = newDir
289
291
  }
290
292
  } catch (e) {
291
293
  // nothing to do
292
294
  }
293
295
  // find and store the course data path
294
- const courseDirs = await glob(`${this.unzipPath}/*/course`)
296
+ const courseDirs = await glob(`${this.path}/*/course`)
295
297
  if (courseDirs.length > 1) {
296
298
  this.framework.log('error', 'MULTIPLE_COURSE_DIRS', courseDirs)
297
299
  throw App.instance.errors.FW_IMPORT_INVALID_COURSE
@@ -305,16 +307,16 @@ class AdaptFrameworkImport {
305
307
  this.framework.log('error', e)
306
308
  throw App.instance.errors.FW_IMPORT_INVALID_COURSE
307
309
  }
308
- FWUtils.logDir('unzipPath', this.unzipPath)
310
+ FWUtils.logDir('unzipPath', this.path)
309
311
  FWUtils.logDir('coursePath', this.coursePath)
310
312
 
311
313
  try {
312
- /** @ignore */this.pkg = await FWUtils.readJson(`${this.unzipPath}/package.json`)
314
+ /** @ignore */this.pkg = await FWUtils.readJson(`${this.path}/package.json`)
313
315
  } catch (e) {
314
316
  throw App.instance.errors.FW_IMPORT_INVALID
315
317
  }
316
318
  try {
317
- await fs.rm(`${this.unzipPath}/package-lock.json`)
319
+ await fs.rm(`${this.path}/package-lock.json`)
318
320
  } catch (e) {}
319
321
 
320
322
  if (!semver.satisfies(this.pkg.version, semver.major(this.framework.version).toString())) {
@@ -335,7 +337,7 @@ class AdaptFrameworkImport {
335
337
  */
336
338
  async convertSchemas () {
337
339
  return octopus.runRecursive({
338
- cwd: this.unzipPath,
340
+ cwd: this.path,
339
341
  logger: { log: (...args) => FWUtils.log('debug', ...args) }
340
342
  })
341
343
  }
@@ -344,7 +346,7 @@ class AdaptFrameworkImport {
344
346
  * Writes the contents of 2-customStyles.less to course.json file. Unfortunately it's necessary to do it this way to ensure it's included in migrations.
345
347
  */
346
348
  async patchCustomStyle () {
347
- const [customStylePath] = await glob('**/2-customStyles.less', { cwd: this.unzipPath, absolute: true })
349
+ const [customStylePath] = await glob('**/2-customStyles.less', { cwd: this.path, absolute: true })
348
350
  const courseJsonPath = `${this.langPath}/course.json`
349
351
  if (!customStylePath) {
350
352
  return
@@ -406,7 +408,7 @@ class AdaptFrameworkImport {
406
408
  * @return {Promise}
407
409
  */
408
410
  async loadPluginData () {
409
- const usedPluginPaths = await glob(`${this.unzipPath}/src/+(components|extensions|menu|theme)/*`, { absolute: true })
411
+ const usedPluginPaths = await glob(`${this.path}/src/+(components|extensions|menu|theme)/*`, { absolute: true })
410
412
  const getPluginType = pluginData => {
411
413
  for (const type of ['component', 'extension', 'menu', 'theme']) {
412
414
  if (pluginData[type] !== undefined) return type
@@ -802,7 +804,7 @@ class AdaptFrameworkImport {
802
804
  }
803
805
  try {
804
806
  const tasks = [
805
- fs.rm(this.unzipPath, { recursive: true })
807
+ fs.rm(this.path, { recursive: true })
806
808
  ]
807
809
  if (error) {
808
810
  tasks.push(
@@ -6,7 +6,6 @@ import fs from 'fs/promises'
6
6
  import FWUtils from './AdaptFrameworkUtils.js'
7
7
  import path from 'path'
8
8
  import semver from 'semver'
9
- import { unzip } from 'zipper'
10
9
 
11
10
  /**
12
11
  * Module to handle the interface with the Adapt framework
@@ -320,11 +319,6 @@ class AdaptFrameworkModule extends AbstractModule {
320
319
  * @return {AdaptFrameworkImportSummary}
321
320
  */
322
321
  async importCourse (options) {
323
- let unzipPath = options.importPath
324
- if (options.importPath.endsWith('.zip')) {
325
- unzipPath = `${options.importPath}_unzip`
326
- await unzip(options.importPath, unzipPath, { removeSource: true })
327
- }
328
322
  const importer = await AdaptFrameworkImport.run(options)
329
323
  importer.preImportHook.tap(() => this.preImportHook.invoke(importer))
330
324
  importer.postImportHook.tap(() => this.postImportHook.invoke(importer))
@@ -270,14 +270,14 @@ class AdaptFrameworkUtils {
270
270
  */
271
271
  static async importHandler (req, res, next) {
272
272
  try {
273
- let unzipPath = req.body.unzipPath
273
+ let importPath = req.body.importPath
274
274
  if (req.get('Content-Type').indexOf('multipart/form-data') === 0) {
275
275
  await AdaptFrameworkUtils.handleImportFile(req, res)
276
276
  const [course] = req.fileUpload.files.course
277
- unzipPath = course.filepath
277
+ importPath = course.filepath
278
278
  }
279
279
  const importer = await FrameworkImport.run({
280
- unzipPath,
280
+ importPath,
281
281
  userId: req.auth.user._id.toString(),
282
282
  isDryRun: AdaptFrameworkUtils.toBoolean(req.body.dryRun),
283
283
  assetFolders: req.body.formAssetFolders,
package/lib/apidefs.js CHANGED
@@ -54,6 +54,7 @@ export default {
54
54
  $schema: 'https://json-schema.org/draft/2020-12/schema',
55
55
  type: 'object',
56
56
  properties: {
57
+ path: { type: 'String' },
57
58
  isDryRun: { type: 'Boolean', default: false },
58
59
  importContent: { type: 'Boolean', default: true },
59
60
  importPlugins: { type: 'Boolean', default: true },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adapt-authoring-adaptframework",
3
- "version": "1.6.0",
3
+ "version": "1.7.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",