adapt-migrations 1.2.0 → 1.4.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/.eslintignore +1 -1
- package/.eslintrc.json +14 -14
- package/.github/CONTRIBUTING.md +8 -0
- package/.github/ISSUE_TEMPLATE/config.yml +1 -0
- package/.github/ISSUE_TEMPLATE/issue_template.md +23 -0
- package/.github/ISSUE_TEMPLATE.md +17 -0
- package/.github/pull_request_template.md +25 -0
- package/.github/workflows/addtomainproject.yml +19 -0
- package/.github/workflows/releases.yml +25 -0
- package/README.md +106 -106
- package/api/commands.js +33 -33
- package/api/data.js +26 -26
- package/api/describe.js +12 -12
- package/api/errors.js +28 -28
- package/api/helpers.js +17 -17
- package/api/plugins.js +47 -47
- package/api/tests.js +56 -57
- package/api/where.js +46 -46
- package/examples/migrations.js +159 -159
- package/examples/script.js +78 -78
- package/index.js +70 -70
- package/lib/CacheManager.js +95 -95
- package/lib/Journal.js +303 -303
- package/lib/Logger.js +91 -91
- package/lib/Task.js +402 -381
- package/lib/TaskContext.js +27 -27
- package/lib/TaskTest.js +21 -19
- package/lib/lifecycle.js +50 -50
- package/package.json +55 -18
package/.eslintignore
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
node_modules
|
|
1
|
+
node_modules
|
package/.eslintrc.json
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
{
|
|
2
|
-
"env": {
|
|
3
|
-
"browser": false,
|
|
4
|
-
"node": true,
|
|
5
|
-
"commonjs": false,
|
|
6
|
-
"es2020": true
|
|
7
|
-
},
|
|
8
|
-
"extends": [
|
|
9
|
-
"standard"
|
|
10
|
-
],
|
|
11
|
-
"parserOptions": {
|
|
12
|
-
"ecmaVersion": 2022
|
|
13
|
-
}
|
|
14
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"env": {
|
|
3
|
+
"browser": false,
|
|
4
|
+
"node": true,
|
|
5
|
+
"commonjs": false,
|
|
6
|
+
"es2020": true
|
|
7
|
+
},
|
|
8
|
+
"extends": [
|
|
9
|
+
"standard"
|
|
10
|
+
],
|
|
11
|
+
"parserOptions": {
|
|
12
|
+
"ecmaVersion": 2022
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
We heartily welcome contributions to the Adapt project source code and community.
|
|
2
|
+
Here is a list of resources you may find useful:
|
|
3
|
+
|
|
4
|
+
* [Contributing to the Adapt project documentation](https://github.com/adaptlearning/adapt_framework/wiki/Contributing-to-the-Adapt-Project)
|
|
5
|
+
* [The Adapt framework wiki](https://github.com/adaptlearning/adapt_framework/wiki)
|
|
6
|
+
* [Gitter chat room](https://gitter.im/adaptlearning/adapt_framework)
|
|
7
|
+
* [General GitHub documentation](http://help.github.com/)
|
|
8
|
+
* [GitHub pull request documentation](http://help.github.com/send-pull-requests/)
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
blank_issues_enabled: false
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: Issue template
|
|
3
|
+
about: Create an issue to help us improve
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
### Subject of the issue/enhancement/features
|
|
7
|
+
Describe your issue here.
|
|
8
|
+
|
|
9
|
+
### Your environment
|
|
10
|
+
* version (AT/Framework)
|
|
11
|
+
* which browser and its version
|
|
12
|
+
* device(s) + operating system(s)
|
|
13
|
+
|
|
14
|
+
### Steps to reproduce
|
|
15
|
+
Tell us how to reproduce this issue.
|
|
16
|
+
|
|
17
|
+
### Expected behaviour
|
|
18
|
+
Tell us what should happen
|
|
19
|
+
|
|
20
|
+
### Actual behaviour
|
|
21
|
+
Tell us what happens instead
|
|
22
|
+
|
|
23
|
+
### Screenshots (if you can)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
### Subject of the issue/enhancement/features
|
|
2
|
+
Describe your issue here.
|
|
3
|
+
|
|
4
|
+
### Your environment
|
|
5
|
+
* version of the cli
|
|
6
|
+
* operating system(s)
|
|
7
|
+
|
|
8
|
+
### Steps to reproduce
|
|
9
|
+
Tell us how to reproduce this issue.
|
|
10
|
+
|
|
11
|
+
### Expected behaviour
|
|
12
|
+
Tell us what should happen
|
|
13
|
+
|
|
14
|
+
### Actual behaviour
|
|
15
|
+
Tell us what happens instead
|
|
16
|
+
|
|
17
|
+
### Screenshots (if you can)
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
[//]: # (Please title your PR according to eslint commit conventions)
|
|
2
|
+
[//]: # (See https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-eslint#eslint-convention for details)
|
|
3
|
+
|
|
4
|
+
[//]: # (Link the PR to the original issue)
|
|
5
|
+
|
|
6
|
+
[//]: # (Delete Fix, Update, New and/or Breaking sections as appropriate)
|
|
7
|
+
### Fix
|
|
8
|
+
* A sentence describing each fix
|
|
9
|
+
|
|
10
|
+
### Update
|
|
11
|
+
* A sentence describing each update
|
|
12
|
+
|
|
13
|
+
### New
|
|
14
|
+
* A sentence describing each new feature
|
|
15
|
+
|
|
16
|
+
### Breaking
|
|
17
|
+
* A sentence describing each breaking change
|
|
18
|
+
|
|
19
|
+
[//]: # (List appropriate steps for testing if needed)
|
|
20
|
+
### Testing
|
|
21
|
+
1. Steps for testing
|
|
22
|
+
|
|
23
|
+
[//]: # (Mention any other dependencies)
|
|
24
|
+
|
|
25
|
+
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
name: Add to main project
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
issues:
|
|
5
|
+
types:
|
|
6
|
+
- opened
|
|
7
|
+
pull_request:
|
|
8
|
+
types:
|
|
9
|
+
- opened
|
|
10
|
+
|
|
11
|
+
jobs:
|
|
12
|
+
add-to-project:
|
|
13
|
+
name: Add to main project
|
|
14
|
+
runs-on: ubuntu-latest
|
|
15
|
+
steps:
|
|
16
|
+
- uses: actions/add-to-project@v0.1.0
|
|
17
|
+
with:
|
|
18
|
+
project-url: https://github.com/orgs/adaptlearning/projects/2
|
|
19
|
+
github-token: ${{ secrets.ADDTOPROJECT_TOKEN }}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
on:
|
|
3
|
+
push:
|
|
4
|
+
branches:
|
|
5
|
+
- master
|
|
6
|
+
jobs:
|
|
7
|
+
release:
|
|
8
|
+
name: Release
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
steps:
|
|
11
|
+
- name: Checkout
|
|
12
|
+
uses: actions/checkout@v2
|
|
13
|
+
with:
|
|
14
|
+
fetch-depth: 0
|
|
15
|
+
- name: Setup Node.js
|
|
16
|
+
uses: actions/setup-node@v2
|
|
17
|
+
with:
|
|
18
|
+
node-version: 'lts/*'
|
|
19
|
+
- name: Install dependencies
|
|
20
|
+
run: npm ci
|
|
21
|
+
- name: Release
|
|
22
|
+
env:
|
|
23
|
+
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
24
|
+
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
|
25
|
+
run: npx semantic-release
|
package/README.md
CHANGED
|
@@ -1,106 +1,106 @@
|
|
|
1
|
-
# adapt-migrations
|
|
2
|
-
|
|
3
|
-
### Todos
|
|
4
|
-
https://github.com/cgkineo/adapt-migrations/issues/1
|
|
5
|
-
|
|
6
|
-
### Commands API
|
|
7
|
-
https://github.com/cgkineo/adapt-migrations/blob/master/api/commands.js
|
|
8
|
-
* `load({ cwd, cachePath, scripts })` - loads all migration tasks
|
|
9
|
-
* `capture({ cwd, content, fromPlugins })` - captures current plugins and content
|
|
10
|
-
* `migrate({ cwd, toPlugins })` - migrates content from capture to new plugins
|
|
11
|
-
* `test({ cwd })` - tests the migrations with dummy content
|
|
12
|
-
|
|
13
|
-
### Migration script API
|
|
14
|
-
Functions:
|
|
15
|
-
* `describe(description, describeFunction)` Describe a migration
|
|
16
|
-
* `whereContent(description, contentFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
17
|
-
* `whereFromPlugin(description, fromPluginFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
18
|
-
* `whereToPlugin(description, toPluginFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
19
|
-
* `mutateContent(contentFunction)` Change content, return true/false/throw Error
|
|
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
|
|
24
|
-
* `throwError(description)` Throw an error
|
|
25
|
-
* `testSuccessWhere({ fromPlugins, toPlugins, content })` Supply some tests content which should end in success
|
|
26
|
-
* `testStopWhere({ fromPlugins, toPlugins, content })` Supply some tests content which should end prematurely
|
|
27
|
-
* `testErrorWhere({ fromPlugins, toPlugins, content })` Supply some tests content which will trigger an error
|
|
28
|
-
|
|
29
|
-
Arguments:
|
|
30
|
-
* `describeFunction = () => {}` Function body has a collection of migration script functions
|
|
31
|
-
* `contentFilterFunction = content => {}` Function body should return true/false/throw Error
|
|
32
|
-
* `fromPluginFilterFunction = fromPlugins => {}` Function body should return true/false/throw Error
|
|
33
|
-
* `toPluginFilterFunction = toPlugins => {}` Function body should return true/false/throw Error
|
|
34
|
-
* `contentFunction = content => { }` Function body should mutate or check the content, returning true/false/throw Error
|
|
35
|
-
* `fromPlugins = [{ name: 'quickNav , version: '1.0.0' }]` Test data describing the original plugins
|
|
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
|
|
38
|
-
* `content = [{ _id: 'c-05, ... }]` Test content for the course content
|
|
39
|
-
|
|
40
|
-
### Grunt Commands
|
|
41
|
-
```sh
|
|
42
|
-
grunt migration:capture # captures current plugins and content
|
|
43
|
-
# do plugin/fw updates
|
|
44
|
-
grunt migration:migrate # migrates content from capture to new plugins
|
|
45
|
-
grunt migration:test # tests the migrations with dummy content
|
|
46
|
-
grunt migration:test --file=adapt-contrib-text/migrations/text.js # tests the migrations with dummy content
|
|
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
|
|
1
|
+
# adapt-migrations
|
|
2
|
+
|
|
3
|
+
### Todos
|
|
4
|
+
https://github.com/cgkineo/adapt-migrations/issues/1
|
|
5
|
+
|
|
6
|
+
### Commands API
|
|
7
|
+
https://github.com/cgkineo/adapt-migrations/blob/master/api/commands.js
|
|
8
|
+
* `load({ cwd, cachePath, scripts })` - loads all migration tasks
|
|
9
|
+
* `capture({ cwd, content, fromPlugins })` - captures current plugins and content
|
|
10
|
+
* `migrate({ cwd, toPlugins })` - migrates content from capture to new plugins
|
|
11
|
+
* `test({ cwd })` - tests the migrations with dummy content
|
|
12
|
+
|
|
13
|
+
### Migration script API
|
|
14
|
+
Functions:
|
|
15
|
+
* `describe(description, describeFunction)` Describe a migration
|
|
16
|
+
* `whereContent(description, contentFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
17
|
+
* `whereFromPlugin(description, fromPluginFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
18
|
+
* `whereToPlugin(description, toPluginFilterFunction)` Limit when the migration runs, return true/false/throw Error
|
|
19
|
+
* `mutateContent(contentFunction)` Change content, return true/false/throw Error
|
|
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
|
|
24
|
+
* `throwError(description)` Throw an error
|
|
25
|
+
* `testSuccessWhere(description, { fromPlugins, toPlugins, content })` Supply some tests content which should end in success
|
|
26
|
+
* `testStopWhere(description, { fromPlugins, toPlugins, content })` Supply some tests content which should end prematurely
|
|
27
|
+
* `testErrorWhere(description, { fromPlugins, toPlugins, content })` Supply some tests content which will trigger an error
|
|
28
|
+
|
|
29
|
+
Arguments:
|
|
30
|
+
* `describeFunction = () => {}` Function body has a collection of migration script functions
|
|
31
|
+
* `contentFilterFunction = content => {}` Function body should return true/false/throw Error
|
|
32
|
+
* `fromPluginFilterFunction = fromPlugins => {}` Function body should return true/false/throw Error
|
|
33
|
+
* `toPluginFilterFunction = toPlugins => {}` Function body should return true/false/throw Error
|
|
34
|
+
* `contentFunction = content => { }` Function body should mutate or check the content, returning true/false/throw Error
|
|
35
|
+
* `fromPlugins = [{ name: 'quickNav , version: '1.0.0' }]` Test data describing the original plugins
|
|
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
|
|
38
|
+
* `content = [{ _id: 'c-05, ... }]` Test content for the course content
|
|
39
|
+
|
|
40
|
+
### Grunt Commands
|
|
41
|
+
```sh
|
|
42
|
+
grunt migration:capture # captures current plugins and content
|
|
43
|
+
# do plugin/fw updates
|
|
44
|
+
grunt migration:migrate # migrates content from capture to new plugins
|
|
45
|
+
grunt migration:test # tests the migrations with dummy content
|
|
46
|
+
grunt migration:test --file=adapt-contrib-text/migrations/text.js # tests the migrations with dummy content
|
|
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/commands.js
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
import Task from '../lib/Task.js'
|
|
2
|
-
|
|
3
|
-
export async function load ({ cwd = process.cwd(), scripts = [], cachePath, logger } = {}) {
|
|
4
|
-
return Task.load({
|
|
5
|
-
cwd,
|
|
6
|
-
scripts,
|
|
7
|
-
cachePath,
|
|
8
|
-
logger
|
|
9
|
-
})
|
|
10
|
-
}
|
|
11
|
-
|
|
12
|
-
export async function capture ({ content, fromPlugins, logger }) {
|
|
13
|
-
return {
|
|
14
|
-
content,
|
|
15
|
-
fromPlugins,
|
|
16
|
-
logger
|
|
17
|
-
}
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export async function migrate ({ cwd = process.cwd(), journal, logger }) {
|
|
21
|
-
return Task.runApplicable({
|
|
22
|
-
cwd,
|
|
23
|
-
journal,
|
|
24
|
-
logger
|
|
25
|
-
})
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
export async function test ({ cwd = process.cwd(), logger } = {}) {
|
|
29
|
-
return Task.runTests({
|
|
30
|
-
cwd,
|
|
31
|
-
logger
|
|
32
|
-
})
|
|
33
|
-
}
|
|
1
|
+
import Task from '../lib/Task.js'
|
|
2
|
+
|
|
3
|
+
export async function load ({ cwd = process.cwd(), scripts = [], cachePath, logger } = {}) {
|
|
4
|
+
return Task.load({
|
|
5
|
+
cwd,
|
|
6
|
+
scripts,
|
|
7
|
+
cachePath,
|
|
8
|
+
logger
|
|
9
|
+
})
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function capture ({ content, fromPlugins, logger }) {
|
|
13
|
+
return {
|
|
14
|
+
content,
|
|
15
|
+
fromPlugins,
|
|
16
|
+
logger
|
|
17
|
+
}
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export async function migrate ({ cwd = process.cwd(), journal, logger }) {
|
|
21
|
+
return Task.runApplicable({
|
|
22
|
+
cwd,
|
|
23
|
+
journal,
|
|
24
|
+
logger
|
|
25
|
+
})
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function test ({ cwd = process.cwd(), logger } = {}) {
|
|
29
|
+
return Task.runTests({
|
|
30
|
+
cwd,
|
|
31
|
+
logger
|
|
32
|
+
})
|
|
33
|
+
}
|
package/api/data.js
CHANGED
|
@@ -1,26 +1,26 @@
|
|
|
1
|
-
import { deferOrRunWrap, successStopOrErrorWrap } from '../lib/lifecycle.js'
|
|
2
|
-
|
|
3
|
-
export function mutateContent (description, callback) {
|
|
4
|
-
return deferOrRunWrap(function (context) {
|
|
5
|
-
return successStopOrErrorWrap('mutateContent', description, async () => {
|
|
6
|
-
return callback(context.content)
|
|
7
|
-
})
|
|
8
|
-
}, { description, type: 'action' })
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export function checkContent (description, callback) {
|
|
12
|
-
return deferOrRunWrap(function (context) {
|
|
13
|
-
return successStopOrErrorWrap('checkContent', description, async () => {
|
|
14
|
-
context.journal.freeze()
|
|
15
|
-
let result
|
|
16
|
-
try {
|
|
17
|
-
result = await callback(context.content)
|
|
18
|
-
} catch (err) {
|
|
19
|
-
context.journal.unfreeze()
|
|
20
|
-
throw err
|
|
21
|
-
}
|
|
22
|
-
context.journal.unfreeze()
|
|
23
|
-
return result
|
|
24
|
-
})
|
|
25
|
-
}, { description, type: 'action' })
|
|
26
|
-
};
|
|
1
|
+
import { deferOrRunWrap, successStopOrErrorWrap } from '../lib/lifecycle.js'
|
|
2
|
+
|
|
3
|
+
export function mutateContent (description, callback) {
|
|
4
|
+
return deferOrRunWrap(function (context) {
|
|
5
|
+
return successStopOrErrorWrap('mutateContent', description, async () => {
|
|
6
|
+
return callback(context.content)
|
|
7
|
+
})
|
|
8
|
+
}, { description, type: 'action' })
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export function checkContent (description, callback) {
|
|
12
|
+
return deferOrRunWrap(function (context) {
|
|
13
|
+
return successStopOrErrorWrap('checkContent', description, async () => {
|
|
14
|
+
context.journal.freeze()
|
|
15
|
+
let result
|
|
16
|
+
try {
|
|
17
|
+
result = await callback(context.content)
|
|
18
|
+
} catch (err) {
|
|
19
|
+
context.journal.unfreeze()
|
|
20
|
+
throw err
|
|
21
|
+
}
|
|
22
|
+
context.journal.unfreeze()
|
|
23
|
+
return result
|
|
24
|
+
})
|
|
25
|
+
}, { description, type: 'action' })
|
|
26
|
+
};
|
package/api/describe.js
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
|
-
import Task from '../lib/Task.js'
|
|
2
|
-
import Logger from './../lib/Logger.js'
|
|
3
|
-
|
|
4
|
-
export function describe (description, load) {
|
|
5
|
-
const logger = Logger.getInstance();
|
|
6
|
-
logger.info(`Describe -- ${description} -- Registered`)
|
|
7
|
-
if (Task.current) {
|
|
8
|
-
logger.error(`Describe -- Cannot nest describe statements -- ${description}`)
|
|
9
|
-
}
|
|
10
|
-
// eslint-disable-next-line no-new
|
|
11
|
-
new Task({ description, load })
|
|
12
|
-
};
|
|
1
|
+
import Task from '../lib/Task.js'
|
|
2
|
+
import Logger from './../lib/Logger.js'
|
|
3
|
+
|
|
4
|
+
export function describe (description, load) {
|
|
5
|
+
const logger = Logger.getInstance();
|
|
6
|
+
logger.info(`Describe -- ${description} -- Registered`)
|
|
7
|
+
if (Task.current) {
|
|
8
|
+
logger.error(`Describe -- Cannot nest describe statements -- ${description}`)
|
|
9
|
+
}
|
|
10
|
+
// eslint-disable-next-line no-new
|
|
11
|
+
new Task({ description, load })
|
|
12
|
+
};
|
package/api/errors.js
CHANGED
|
@@ -1,28 +1,28 @@
|
|
|
1
|
-
import { deferOrRunWrap, successStopOrErrorWrap } from '../lib/lifecycle.js'
|
|
2
|
-
import Logger from './../lib/Logger.js'
|
|
3
|
-
|
|
4
|
-
const logger = Logger.getInstance();
|
|
5
|
-
|
|
6
|
-
export function throwError (description) {
|
|
7
|
-
let error = description
|
|
8
|
-
if (!(description instanceof Error)) {
|
|
9
|
-
logger.error(`Errors -- ${description}`)
|
|
10
|
-
error = new Error(description)
|
|
11
|
-
} else {
|
|
12
|
-
description = description.message
|
|
13
|
-
}
|
|
14
|
-
return deferOrRunWrap(function (context) {
|
|
15
|
-
return successStopOrErrorWrap('throwError', description, async () => {
|
|
16
|
-
throw error
|
|
17
|
-
})
|
|
18
|
-
}, { type: 'action' })
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
export function ifErroredAsk (config) {
|
|
22
|
-
return deferOrRunWrap(function (context) {
|
|
23
|
-
return successStopOrErrorWrap('isErroredAsk', config.question, async () => {
|
|
24
|
-
if (!context.hasErrored) return true
|
|
25
|
-
// Ask a question
|
|
26
|
-
})
|
|
27
|
-
}, { type: 'error' })
|
|
28
|
-
};
|
|
1
|
+
import { deferOrRunWrap, successStopOrErrorWrap } from '../lib/lifecycle.js'
|
|
2
|
+
import Logger from './../lib/Logger.js'
|
|
3
|
+
|
|
4
|
+
const logger = Logger.getInstance();
|
|
5
|
+
|
|
6
|
+
export function throwError (description) {
|
|
7
|
+
let error = description
|
|
8
|
+
if (!(description instanceof Error)) {
|
|
9
|
+
logger.error(`Errors -- ${description}`)
|
|
10
|
+
error = new Error(description)
|
|
11
|
+
} else {
|
|
12
|
+
description = description.message
|
|
13
|
+
}
|
|
14
|
+
return deferOrRunWrap(function (context) {
|
|
15
|
+
return successStopOrErrorWrap('throwError', description, async () => {
|
|
16
|
+
throw error
|
|
17
|
+
})
|
|
18
|
+
}, { type: 'action' })
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function ifErroredAsk (config) {
|
|
22
|
+
return deferOrRunWrap(function (context) {
|
|
23
|
+
return successStopOrErrorWrap('isErroredAsk', config.question, async () => {
|
|
24
|
+
if (!context.hasErrored) return true
|
|
25
|
+
// Ask a question
|
|
26
|
+
})
|
|
27
|
+
}, { type: 'error' })
|
|
28
|
+
};
|
package/api/helpers.js
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
|
-
import Task from '../lib/Task.js'
|
|
2
|
-
|
|
3
|
-
function getContent() {
|
|
4
|
-
return Task.current.context.content;
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
export function getConfig () {
|
|
8
|
-
return getContent().find(({ _type, __path__ }) => _type === 'config' || __path__.endsWith('config.json'))
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
export function getCourse() {
|
|
12
|
-
return getContent().find(({ _type }) => _type === 'course');
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
export function getComponents(componentName) {
|
|
16
|
-
return getContent().filter(({ _component }) => _component === componentName);
|
|
17
|
-
}
|
|
1
|
+
import Task from '../lib/Task.js'
|
|
2
|
+
|
|
3
|
+
function getContent() {
|
|
4
|
+
return Task.current.context.content;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export function getConfig () {
|
|
8
|
+
return getContent().find(({ _type, __path__ }) => _type === 'config' || __path__.endsWith('config.json'))
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
export function getCourse() {
|
|
12
|
+
return getContent().find(({ _type }) => _type === 'course');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function getComponents(componentName) {
|
|
16
|
+
return getContent().filter(({ _component }) => _component === componentName);
|
|
17
|
+
}
|