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.
@@ -1,78 +1,78 @@
1
- // Example migration script
2
-
3
- import { describe, whereContent, whereFromPlugin, whereToPlugin, mutateContent, checkContent, throwError, ifErroredAsk, testSuccessWhere, testErrorWhere, testStopWhere } from 'adapt-migrations';
4
-
5
- describe('update plugin from v6.1.4 to v6.1.5 and add "Ollie" to display title', async () => {
6
- whereFromPlugin('text v6.1.4', { name: 'adapt-contrib-text', version: '<=6.1.4' });
7
- whereContent('has configured displayTitles', async content =>
8
- content.some(({ displayTitle }) => displayTitle)
9
- );
10
- mutateContent('change displayTitle', async content => {
11
- const quicknavs = content.filter(({ displayTitle }) => displayTitle);
12
- quicknavs.forEach(item => (item.displayTitle += ' ollie'));
13
- return true;
14
- });
15
- checkContent('check everything is ok', async content => {
16
- const isInvalid = content.some(({ displayTitle }) => displayTitle && !String(displayTitle).endsWith(' ollie'));
17
- if (isInvalid) throw new Error('found displayTitle without ollie at the end');
18
- return true;
19
- });
20
- updatePlugin('update text plugin', {name: 'adapt-contrib-text', version: '6.1.5', framework: '>=5.19.4'})
21
- });
22
-
23
- describe('quicknav to pagenav', async () => {
24
- addPlugin('add pagenav plugin', { name: 'pagenav', version: '1.0.0'});
25
- whereFromPlugin('quicknav v1.0.0', { name: 'quicknav', version: '1.0.0' });
26
- whereToPlugin('pagenav v1.0.0', { name: 'pagenav', version: '1.0.0' });
27
- removePlugin('remove quicknav plugin', {name: 'quicknav'})
28
- whereContent('has configured quicknavs', async content =>
29
- content.some(({ _component }) => _component === 'quicknav')
30
- );
31
- mutateContent('change _component name', async content => {
32
- const quicknavs = content.filter(({ _component }) => _component === 'quicknav');
33
- quicknavs.forEach(item => (item._component = 'pagenav'));
34
- return true;
35
- });
36
- checkContent('check everything is ok', async content => {
37
- const isInvalid = content.some(({ isInvalid }) => isInvalid);
38
- if (isInvalid) throw new Error('found invalid content attribute');
39
- return true;
40
- });
41
- // TODO: handle errors with question, allow to run without ui
42
- // ifErroredAsk({ question: 'Skip error', yes: 'Yes', no: 'No', defaultSkipError: true });
43
- // TODO: modify stack traces one errors to refer to the original migration script rather than the cached one,keep map of cached files to original files
44
- testSuccessWhere('Valid plugins and content', {
45
- fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
46
- toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
47
- content: [{ _component: 'quicknav' }]
48
- });
49
- testStopWhere('Invalid content', {
50
- fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
51
- toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
52
- content: [{ _component: 'quicknav1' }]
53
- });
54
- testStopWhere('Invalid origin plugins', {
55
- fromPlugins: [{ name: 'quicknav', version: '0.1.0' }],
56
- toPlugins: [{ name: 'pagenav', version: '0.1.0' }],
57
- content: [{ _component: 'quicknav' }]
58
- });
59
- testStopWhere('Invalid destination plugins', {
60
- content: [{ _component: 'quicknav' }],
61
- fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
62
- toPlugins: [{ name: 'pagenav', version: '0.1.0' }]
63
- });
64
- testErrorWhere('Has invalid configuration', {
65
- fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
66
- toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
67
- content: [{ _component: 'quicknav', isInvalid: true }]
68
- });
69
- });
70
-
71
- describe('where quicknav is weirdly configured', async () => {
72
- checkContent('check everything is ok', async content => {
73
- const isInvalid = content.some(({ isInvalid }) => isInvalid);
74
- if (isInvalid) throw new Error('Something went wrong');
75
- return true;
76
- });
77
- throwError('this is an error');
78
- });
1
+ // Example migration script
2
+
3
+ import { describe, whereContent, whereFromPlugin, whereToPlugin, mutateContent, checkContent, throwError, ifErroredAsk, testSuccessWhere, testErrorWhere, testStopWhere } from 'adapt-migrations';
4
+
5
+ describe('update plugin from v6.1.4 to v6.1.5 and add "Ollie" to display title', async () => {
6
+ whereFromPlugin('text v6.1.4', { name: 'adapt-contrib-text', version: '<=6.1.4' });
7
+ whereContent('has configured displayTitles', async content =>
8
+ content.some(({ displayTitle }) => displayTitle)
9
+ );
10
+ mutateContent('change displayTitle', async content => {
11
+ const quicknavs = content.filter(({ displayTitle }) => displayTitle);
12
+ quicknavs.forEach(item => (item.displayTitle += ' ollie'));
13
+ return true;
14
+ });
15
+ checkContent('check everything is ok', async content => {
16
+ const isInvalid = content.some(({ displayTitle }) => displayTitle && !String(displayTitle).endsWith(' ollie'));
17
+ if (isInvalid) throw new Error('found displayTitle without ollie at the end');
18
+ return true;
19
+ });
20
+ updatePlugin('update text plugin', {name: 'adapt-contrib-text', version: '6.1.5', framework: '>=5.19.4'})
21
+ });
22
+
23
+ describe('quicknav to pagenav', async () => {
24
+ addPlugin('add pagenav plugin', { name: 'pagenav', version: '1.0.0'});
25
+ whereFromPlugin('quicknav v1.0.0', { name: 'quicknav', version: '1.0.0' });
26
+ whereToPlugin('pagenav v1.0.0', { name: 'pagenav', version: '1.0.0' });
27
+ removePlugin('remove quicknav plugin', {name: 'quicknav'})
28
+ whereContent('has configured quicknavs', async content =>
29
+ content.some(({ _component }) => _component === 'quicknav')
30
+ );
31
+ mutateContent('change _component name', async content => {
32
+ const quicknavs = content.filter(({ _component }) => _component === 'quicknav');
33
+ quicknavs.forEach(item => (item._component = 'pagenav'));
34
+ return true;
35
+ });
36
+ checkContent('check everything is ok', async content => {
37
+ const isInvalid = content.some(({ isInvalid }) => isInvalid);
38
+ if (isInvalid) throw new Error('found invalid content attribute');
39
+ return true;
40
+ });
41
+ // TODO: handle errors with question, allow to run without ui
42
+ // ifErroredAsk({ question: 'Skip error', yes: 'Yes', no: 'No', defaultSkipError: true });
43
+ // TODO: modify stack traces one errors to refer to the original migration script rather than the cached one,keep map of cached files to original files
44
+ testSuccessWhere('Valid plugins and content', {
45
+ fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
46
+ toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
47
+ content: [{ _component: 'quicknav' }]
48
+ });
49
+ testStopWhere('Invalid content', {
50
+ fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
51
+ toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
52
+ content: [{ _component: 'quicknav1' }]
53
+ });
54
+ testStopWhere('Invalid origin plugins', {
55
+ fromPlugins: [{ name: 'quicknav', version: '0.1.0' }],
56
+ toPlugins: [{ name: 'pagenav', version: '0.1.0' }],
57
+ content: [{ _component: 'quicknav' }]
58
+ });
59
+ testStopWhere('Invalid destination plugins', {
60
+ content: [{ _component: 'quicknav' }],
61
+ fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
62
+ toPlugins: [{ name: 'pagenav', version: '0.1.0' }]
63
+ });
64
+ testErrorWhere('Has invalid configuration', {
65
+ fromPlugins: [{ name: 'quicknav', version: '1.0.0' }],
66
+ toPlugins: [{ name: 'pagenav', version: '1.0.0' }],
67
+ content: [{ _component: 'quicknav', isInvalid: true }]
68
+ });
69
+ });
70
+
71
+ describe('where quicknav is weirdly configured', async () => {
72
+ checkContent('check everything is ok', async content => {
73
+ const isInvalid = content.some(({ isInvalid }) => isInvalid);
74
+ if (isInvalid) throw new Error('Something went wrong');
75
+ return true;
76
+ });
77
+ throwError('this is an error');
78
+ });
package/index.js CHANGED
@@ -1,70 +1,70 @@
1
- import {
2
- load,
3
- capture,
4
- migrate,
5
- test
6
- } from './api/commands.js'
7
- import {
8
- describe
9
- } from './api/describe.js'
10
- import {
11
- whereContent,
12
- whereFromPlugin,
13
- whereToPlugin
14
- } from './api/where.js'
15
- import {
16
- checkContent,
17
- mutateContent
18
- } from './api/data.js'
19
- import {
20
- ifErroredAsk,
21
- throwError
22
- } from './api/errors.js'
23
- import {
24
- testErrorWhere,
25
- testStopWhere,
26
- testSuccessWhere
27
- } from './api/tests.js'
28
- import {
29
- updatePlugin,
30
- removePlugin,
31
- addPlugin
32
- } from './api/plugins.js'
33
- import {
34
- getConfig,
35
- getCourse,
36
- getComponents
37
- } from './api/helpers.js'
38
- import Journal from './lib/Journal.js'
39
- import CacheManager from './lib/CacheManager.js'
40
- import Logger from './lib/Logger.js'
41
-
42
- export {
43
- // commands
44
- load,
45
- capture,
46
- migrate,
47
- test,
48
- // migration script api
49
- describe,
50
- whereContent,
51
- whereFromPlugin,
52
- whereToPlugin,
53
- checkContent,
54
- mutateContent,
55
- ifErroredAsk,
56
- throwError,
57
- testErrorWhere,
58
- testStopWhere,
59
- testSuccessWhere,
60
- updatePlugin,
61
- removePlugin,
62
- addPlugin,
63
- getConfig,
64
- getCourse,
65
- getComponents,
66
- // environment objects
67
- Journal,
68
- CacheManager,
69
- Logger
70
- }
1
+ import {
2
+ load,
3
+ capture,
4
+ migrate,
5
+ test
6
+ } from './api/commands.js'
7
+ import {
8
+ describe
9
+ } from './api/describe.js'
10
+ import {
11
+ whereContent,
12
+ whereFromPlugin,
13
+ whereToPlugin
14
+ } from './api/where.js'
15
+ import {
16
+ checkContent,
17
+ mutateContent
18
+ } from './api/data.js'
19
+ import {
20
+ ifErroredAsk,
21
+ throwError
22
+ } from './api/errors.js'
23
+ import {
24
+ testErrorWhere,
25
+ testStopWhere,
26
+ testSuccessWhere
27
+ } from './api/tests.js'
28
+ import {
29
+ updatePlugin,
30
+ removePlugin,
31
+ addPlugin
32
+ } from './api/plugins.js'
33
+ import {
34
+ getConfig,
35
+ getCourse,
36
+ getComponents
37
+ } from './api/helpers.js'
38
+ import Journal from './lib/Journal.js'
39
+ import CacheManager from './lib/CacheManager.js'
40
+ import Logger from './lib/Logger.js'
41
+
42
+ export {
43
+ // commands
44
+ load,
45
+ capture,
46
+ migrate,
47
+ test,
48
+ // migration script api
49
+ describe,
50
+ whereContent,
51
+ whereFromPlugin,
52
+ whereToPlugin,
53
+ checkContent,
54
+ mutateContent,
55
+ ifErroredAsk,
56
+ throwError,
57
+ testErrorWhere,
58
+ testStopWhere,
59
+ testSuccessWhere,
60
+ updatePlugin,
61
+ removePlugin,
62
+ addPlugin,
63
+ getConfig,
64
+ getCourse,
65
+ getComponents,
66
+ // environment objects
67
+ Journal,
68
+ CacheManager,
69
+ Logger
70
+ }
@@ -1,95 +1,95 @@
1
- import crypto from 'crypto'
2
- import path from 'path'
3
- import globs from 'globs'
4
- import fs from 'fs-extra'
5
- import os from 'os'
6
- import Logger from './Logger.js'
7
-
8
- export const ONE_MINUTE = 60 * 1000
9
- export const ONE_HOUR = 60 * ONE_MINUTE
10
- export const ONE_WEEK = 7 * 24 * ONE_HOUR
11
-
12
- const logger = Logger.getInstance();
13
-
14
- export default class CacheManager {
15
- constructor (maxAge = ONE_WEEK) {
16
- this.maxAge = maxAge
17
- }
18
-
19
- static hash (path) {
20
- return crypto
21
- .createHash('sha1')
22
- .update(path, 'utf8')
23
- .digest('hex')
24
- }
25
-
26
- async getTempPath () {
27
- const osTempPath = await fs.promises.realpath(path.resolve(os.tmpdir()))
28
- const tempPath = path.posix.join(osTempPath, 'migrations')
29
- await fs.ensureDir(tempPath)
30
- return tempPath
31
- }
32
-
33
- async getCachePath ({ basePath = process.cwd(), outputPath, tempPath }) {
34
- const projectHash = CacheManager.hash(path.join(basePath, outputPath))
35
- if (tempPath) await fs.ensureDir(tempPath)
36
- const cachePath = path.join(tempPath ?? await this.getTempPath(), `${projectHash}.cache`)
37
- return cachePath
38
- }
39
-
40
- async getCheckFilePath () {
41
- const checkFilePath = path.join(await this.getTempPath(), 'last.touch')
42
- return checkFilePath
43
- }
44
-
45
- async isCleaningTime () {
46
- // By default, clean once a day, or with a floor of one hourly intervals
47
- const checkInterval = Math.max(this.maxAge / 7, ONE_HOUR)
48
- const checkFilePath = await this.getCheckFilePath()
49
- // Check if checkFile is older than the cleaning interval
50
- return (!fs.existsSync(checkFilePath) || Date.now() - (await fs.stat(checkFilePath)).mtime >= checkInterval)
51
- }
52
-
53
- async clean () {
54
- if (!await this.isCleaningTime()) return
55
- logger.debug('CacheManager -- Clean running')
56
- const checkFilePath = await this.getCheckFilePath()
57
- // Touch checkFile
58
- await fs.writeFile(checkFilePath, String(Date.now()))
59
- logger.debug('CacheManager -- Clearing compilation caches')
60
- const tempPath = await this.getTempPath()
61
- // Fetch all cache files except checkFile
62
- const files = await new Promise((resolve, reject) => globs([
63
- `${tempPath}/**`,
64
- `!${checkFilePath}`
65
- ], {
66
- nodir: true
67
- }, (err, files) => err ? reject(err) : resolve(files)))
68
- // Fetch file ages
69
- const fileAges = []
70
- const now = Date.now()
71
- for (const index in files) {
72
- const file = files[index]
73
- let age = this.maxAge
74
- try {
75
- const stat = await fs.stat(file)
76
- age = (now - stat.mtime)
77
- } catch (err) {
78
- logger.error(`CacheManager -- ${err}`)
79
- }
80
- fileAges[index] = { file, age }
81
- }
82
- // Sort by oldest
83
- fileAges.sort((a, b) => b.age - a.age)
84
- // Filter by expired
85
- const toRemove = fileAges.filter(fileAge => fileAge.age >= this.maxAge)
86
- // Delete expired cache files
87
- for (const fileAge of toRemove) {
88
- try {
89
- await fs.unlink(fileAge.file)
90
- } catch (err) {
91
- logger.error(`CacheManager -- Could not clear cache file ${fileAge.file}`)
92
- }
93
- }
94
- }
95
- };
1
+ import crypto from 'crypto'
2
+ import path from 'path'
3
+ import globs from 'globs'
4
+ import fs from 'fs-extra'
5
+ import os from 'os'
6
+ import Logger from './Logger.js'
7
+
8
+ export const ONE_MINUTE = 60 * 1000
9
+ export const ONE_HOUR = 60 * ONE_MINUTE
10
+ export const ONE_WEEK = 7 * 24 * ONE_HOUR
11
+
12
+ const logger = Logger.getInstance();
13
+
14
+ export default class CacheManager {
15
+ constructor (maxAge = ONE_WEEK) {
16
+ this.maxAge = maxAge
17
+ }
18
+
19
+ static hash (path) {
20
+ return crypto
21
+ .createHash('sha1')
22
+ .update(path, 'utf8')
23
+ .digest('hex')
24
+ }
25
+
26
+ async getTempPath () {
27
+ const osTempPath = await fs.promises.realpath(path.resolve(os.tmpdir()))
28
+ const tempPath = path.posix.join(osTempPath, 'migrations')
29
+ await fs.ensureDir(tempPath)
30
+ return tempPath
31
+ }
32
+
33
+ async getCachePath ({ basePath = process.cwd(), outputPath, tempPath }) {
34
+ const projectHash = CacheManager.hash(path.join(basePath, outputPath))
35
+ if (tempPath) await fs.ensureDir(tempPath)
36
+ const cachePath = path.join(tempPath ?? await this.getTempPath(), `${projectHash}.cache`)
37
+ return cachePath
38
+ }
39
+
40
+ async getCheckFilePath () {
41
+ const checkFilePath = path.join(await this.getTempPath(), 'last.touch')
42
+ return checkFilePath
43
+ }
44
+
45
+ async isCleaningTime () {
46
+ // By default, clean once a day, or with a floor of one hourly intervals
47
+ const checkInterval = Math.max(this.maxAge / 7, ONE_HOUR)
48
+ const checkFilePath = await this.getCheckFilePath()
49
+ // Check if checkFile is older than the cleaning interval
50
+ return (!fs.existsSync(checkFilePath) || Date.now() - (await fs.stat(checkFilePath)).mtime >= checkInterval)
51
+ }
52
+
53
+ async clean () {
54
+ if (!await this.isCleaningTime()) return
55
+ logger.debug('CacheManager -- Clean running')
56
+ const checkFilePath = await this.getCheckFilePath()
57
+ // Touch checkFile
58
+ await fs.writeFile(checkFilePath, String(Date.now()))
59
+ logger.debug('CacheManager -- Clearing compilation caches')
60
+ const tempPath = await this.getTempPath()
61
+ // Fetch all cache files except checkFile
62
+ const files = await new Promise((resolve, reject) => globs([
63
+ `${tempPath}/**`,
64
+ `!${checkFilePath}`
65
+ ], {
66
+ nodir: true
67
+ }, (err, files) => err ? reject(err) : resolve(files)))
68
+ // Fetch file ages
69
+ const fileAges = []
70
+ const now = Date.now()
71
+ for (const index in files) {
72
+ const file = files[index]
73
+ let age = this.maxAge
74
+ try {
75
+ const stat = await fs.stat(file)
76
+ age = (now - stat.mtime)
77
+ } catch (err) {
78
+ logger.error(`CacheManager -- ${err}`)
79
+ }
80
+ fileAges[index] = { file, age }
81
+ }
82
+ // Sort by oldest
83
+ fileAges.sort((a, b) => b.age - a.age)
84
+ // Filter by expired
85
+ const toRemove = fileAges.filter(fileAge => fileAge.age >= this.maxAge)
86
+ // Delete expired cache files
87
+ for (const fileAge of toRemove) {
88
+ try {
89
+ await fs.unlink(fileAge.file)
90
+ } catch (err) {
91
+ logger.error(`CacheManager -- Could not clear cache file ${fileAge.file}`)
92
+ }
93
+ }
94
+ }
95
+ };