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/examples/script.js
CHANGED
|
@@ -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
|
+
}
|
package/lib/CacheManager.js
CHANGED
|
@@ -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
|
+
};
|