adapt-migrations 1.1.0 → 1.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/README.md CHANGED
@@ -18,6 +18,9 @@ Functions:
18
18
  * `whereToPlugin(description, toPluginFilterFunction)` Limit when the migration runs, return true/false/throw Error
19
19
  * `mutateContent(contentFunction)` Change content, return true/false/throw Error
20
20
  * `checkContent(contentFunction)` Check content, return true/false/throw Error
21
+ * `addPlugin(description, pluginConfig)` Add a plugin
22
+ * `updatePlugin(description, pluginConfig)` Update a plugin
23
+ * `removePlugin(description, pluginConfig)` Remove a plugin
21
24
  * `throwError(description)` Throw an error
22
25
  * `testSuccessWhere({ fromPlugins, toPlugins, content })` Supply some tests content which should end in success
23
26
  * `testStopWhere({ fromPlugins, toPlugins, content })` Supply some tests content which should end prematurely
@@ -31,6 +34,7 @@ Arguments:
31
34
  * `contentFunction = content => { }` Function body should mutate or check the content, returning true/false/throw Error
32
35
  * `fromPlugins = [{ name: 'quickNav , version: '1.0.0' }]` Test data describing the original plugins
33
36
  * `toPlugins = [{ name: 'pageNav , version: '1.0.0' }]` Test data describing the destination plugins
37
+ * `pluginConfig = { name: 'pageNav , version: '1.0.0' }` Describes a plugin
34
38
  * `content = [{ _id: 'c-05, ... }]` Test content for the course content
35
39
 
36
40
  ### Grunt Commands
@@ -41,3 +45,62 @@ grunt migration:migrate # migrates content from capture to new plugins
41
45
  grunt migration:test # tests the migrations with dummy content
42
46
  grunt migration:test --file=adapt-contrib-text/migrations/text.js # tests the migrations with dummy content
43
47
  ```
48
+
49
+ ### Description of how
50
+ The whole `describe` function block is executed as a normal function, from top to bottom, always. It does not return early.
51
+
52
+ When the `describe` function block is executed, we're effectively using javascript function calls (the step functions) to define a single migration script (task) and its steps and then that migration script (task, and its steps) is run in part, to ascertain if it is applicable (using the where section), or in full when it is applicable by running through every step.
53
+
54
+ The step functions (whereFromPlugins, mutateContent, etc) have two phases:
55
+
56
+ #### Step function phases
57
+ 1. Task definition phase: Adding themselves as steps inside a task for later execution
58
+ ```js
59
+ describe(description, async () => { // Make a task
60
+ // where/selection/applicability section
61
+ whereFromPlugin(description, version) // Define as step 1 in the task
62
+ whereContent(description, () => {}) // Define as step 2 in the task
63
+ // mutation section to make changes
64
+ mutateContent(async content => {}) // Define as step 3 in the task
65
+ // checking section to ensure changes, content is immutable
66
+ checkContent(async content => {}) // Define as step 4 in the task
67
+ // plugin progression section
68
+ addPlugin(description, { name, version }) // Define as step 5 in the task
69
+ updatePlugin(description, { name, version }) // Define as step 6 in the task
70
+ removePlugin(description, { name, version }) // Define as step 7 in the task
71
+ // testing data for grunt migration:test
72
+ testSuccessWhere({ fromPlugins, toPlugins, content })
73
+ testStopWhere({ fromPlugins, toPlugins, content })
74
+ testErrorWhere({ fromPlugins, toPlugins, content })
75
+ })
76
+ ```
77
+ 2. Execution phase: When used inside any other executing utility function block
78
+ ```js
79
+ describe(name, async () => {
80
+ mutateContent(name, async content => { // Execute the task step 1
81
+    if (whereFromPlugin(name, version)) { // Execute the function immediately
82
+      // the plugin version is matched
83
+    }
84
+  })
85
+ })
86
+ ```
87
+
88
+ It's these two phases which decouple the definition of and execution of migration scripts, whilst using the same block of javascript. This was the simplest form I could think of, without having too many rules or nesting but whilst also providing flexibility, imply an order and convey concise meaning in as few words as possible.
89
+
90
+ We define variables and steps in the definition phase of the migration script (task) for later execution. The describe function doesn't return early at `whereFromPlugins`, both because functions can't implicitly return early, and because the function is executed in a definition phase, where it's just adding a description of itself to a task for later use.
91
+
92
+ `whereFromPlugins` is a single step in a task, it will be executed multiple times as the migrations progress, this is to find out whether the task is applicable and whether the task should proceed through all of the steps until conclusion.
93
+
94
+ Tasks and steps have three results: success (true), stop (false) or error (throw Error). Using those return values and having some of the step functions marked up as "where" functions, we can selectively define and execute a variety of migrations scripts, for a variety of courses, with a fun array of predictable outcomes.
95
+
96
+ References:
97
+ Migration files are loaded: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/Task.js#L293-L296
98
+ Capturing the description and callback of each describe function call to make a new task: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/api/describe.js#L11
99
+ Each of the describe blocks in the file are executed one by one: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/Task.js#L297-L302
100
+ The step functions are executed and deferred: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/api/where.js#L4-L10
101
+ The step functions add themselves as tests or steps to the currently loading task: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/lifecycle.js#L23
102
+ After the loading phase, on each run of the migrations scripts, applicable tasks are selected for execution until no more tasks can be executed: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/Task.js#L320-L323
103
+ Tasks are applicable if all of their where steps come back success: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/Task.js#L184-L197
104
+ The applicable tasks are run: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/Task.js#L330-L336
105
+ The last success, stop or error step determines if the task failed or completed successfully or stopped because it wasn't applicable: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/lib/lifecycle.js#L35-L41
106
+ Here is checkContent, freezing the data and dealing with its result: https://github.com/adaptlearning/adapt-migrations/blob/1b156d8dad82f370c974f630adb3d58eaa8517b8/api/data.js#L13-L24
package/api/plugins.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { deferOrRunWrap, successStopOrErrorWrap } from '../lib/lifecycle.js'
2
- const VERSION_CHECK = /^\d+\.\d+\.\d+$/
2
+ import { valid } from 'semver'
3
3
 
4
4
  export function removePlugin (description, config) {
5
5
  return deferOrRunWrap(function (context) {
@@ -17,7 +17,7 @@ export function addPlugin (description, config) {
17
17
  return deferOrRunWrap(function (context) {
18
18
  return successStopOrErrorWrap('addPlugin', description, async () => {
19
19
  if (!description || !config) throw new Error('addPlugin - incorrectly configured')
20
- if (!VERSION_CHECK.test(config.version)) throw new Error(`addPlugin - invalid version number ${config.version}`)
20
+ if (!valid(config.version)) throw new Error(`addPlugin - invalid version number ${config.version}`)
21
21
 
22
22
  const newPlugin = context.toPlugins.find(plugin => (plugin.name === config.name))
23
23
  if (!newPlugin) throw new Error(`addPlugin - ${config.name} not found`)
@@ -32,7 +32,7 @@ export function updatePlugin (description, config) {
32
32
  return deferOrRunWrap(function (context) {
33
33
  return successStopOrErrorWrap('updatePlugin', description, async () => {
34
34
  if (!description || !config) throw new Error('updatePlugin - incorrectly configured')
35
- if (!VERSION_CHECK.test(config.version)) throw new Error(`updatePlugin - invalid version number ${config.version}`)
35
+ if (!valid(config.version)) throw new Error(`updatePlugin - invalid version number ${config.version}`)
36
36
 
37
37
  context.fromPlugins.forEach(plugin => {
38
38
  if (plugin.name !== config.name) return
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "adapt-migrations",
3
3
  "type": "module",
4
4
  "main": "index.js",
5
- "version": "1.1.0",
5
+ "version": "1.2.0",
6
6
  "devDependencies": {
7
7
  "eslint": "^8.42.0",
8
8
  "eslint-config-standard": "^17.1.0",