adapt-authoring-core 2.5.0 → 3.0.1
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/.github/workflows/releases.yml +9 -24
- package/conf/config.schema.json +16 -0
- package/conf/deprecated-lang.json +4 -0
- package/conf/deprecated-logger.json +7 -0
- package/docs/configure-environment.md +78 -0
- package/docs/developer-workflow.md +649 -0
- package/docs/error-handling.md +49 -0
- package/docs/plugins/configuration.js +75 -0
- package/docs/plugins/configuration.md +14 -0
- package/docs/plugins/errors.js +22 -0
- package/docs/plugins/errorsref.md +9 -0
- package/errors/errors.json +87 -0
- package/errors/node-core.json +39 -0
- package/index.js +5 -0
- package/lib/AbstractModule.js +3 -13
- package/lib/AdaptError.js +57 -0
- package/lib/App.js +66 -51
- package/lib/Config.js +226 -0
- package/lib/DataCache.js +6 -0
- package/lib/DependencyLoader.js +46 -103
- package/lib/Errors.js +50 -0
- package/lib/Hook.js +2 -3
- package/lib/Lang.js +126 -0
- package/lib/Logger.js +149 -0
- package/lib/utils/getArgs.js +4 -6
- package/migrations/3.0.0-conf-migrate-lang-config.js +7 -0
- package/migrations/3.0.0-conf-migrate-logger-config.js +9 -0
- package/package.json +8 -25
- package/tests/AbstractModule.spec.js +4 -54
- package/tests/AdaptError.spec.js +62 -0
- package/tests/Config.spec.js +122 -0
- package/tests/DataCache.spec.js +84 -1
- package/tests/DependencyLoader.spec.js +61 -146
- package/tests/Errors.spec.js +91 -0
- package/tests/Lang.spec.js +116 -0
- package/tests/Logger.spec.js +187 -0
- package/tests/utils-getArgs.spec.js +2 -8
- package/tests/App.spec.js +0 -160
|
@@ -0,0 +1,187 @@
|
|
|
1
|
+
import { describe, it } from 'node:test'
|
|
2
|
+
import assert from 'node:assert/strict'
|
|
3
|
+
import Logger from '../lib/Logger.js'
|
|
4
|
+
|
|
5
|
+
describe('Logger', () => {
|
|
6
|
+
describe('constructor', () => {
|
|
7
|
+
it('should create with defaults', () => {
|
|
8
|
+
const logger = new Logger()
|
|
9
|
+
assert.ok(logger.config)
|
|
10
|
+
assert.ok(logger.logHook)
|
|
11
|
+
assert.equal(logger.config.mute, false)
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
it('should mute when levels is empty', () => {
|
|
15
|
+
const logger = new Logger({ levels: [] })
|
|
16
|
+
assert.equal(logger.config.mute, true)
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should not mute when levels are provided', () => {
|
|
20
|
+
const logger = new Logger({ levels: ['error'] })
|
|
21
|
+
assert.equal(logger.config.mute, false)
|
|
22
|
+
})
|
|
23
|
+
|
|
24
|
+
it('should configure levels from options', () => {
|
|
25
|
+
const logger = new Logger({ levels: ['error', 'warn'] })
|
|
26
|
+
assert.equal(logger.config.levels.error.enable, true)
|
|
27
|
+
assert.equal(logger.config.levels.debug.enable, false)
|
|
28
|
+
})
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
describe('.isLevelEnabled()', () => {
|
|
32
|
+
it('should return true when level is in config', () => {
|
|
33
|
+
assert.equal(Logger.isLevelEnabled(['error', 'warn'], 'error'), true)
|
|
34
|
+
})
|
|
35
|
+
|
|
36
|
+
it('should return false when level is not in config', () => {
|
|
37
|
+
assert.equal(Logger.isLevelEnabled(['error'], 'debug'), false)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
it('should return false when level is negated', () => {
|
|
41
|
+
assert.equal(Logger.isLevelEnabled(['error', '!error'], 'error'), false)
|
|
42
|
+
})
|
|
43
|
+
})
|
|
44
|
+
|
|
45
|
+
describe('.getModuleOverrides()', () => {
|
|
46
|
+
it('should return matching overrides', () => {
|
|
47
|
+
const config = ['error', 'debug.mymod', '!debug.other']
|
|
48
|
+
assert.deepEqual(Logger.getModuleOverrides(config, 'debug'), ['debug.mymod', '!debug.other'])
|
|
49
|
+
})
|
|
50
|
+
|
|
51
|
+
it('should return empty array when no overrides', () => {
|
|
52
|
+
assert.deepEqual(Logger.getModuleOverrides(['error'], 'debug'), [])
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should not include line-level (3-segment) entries', () => {
|
|
56
|
+
const config = ['debug.mymod', 'debug.mymod.LOAD', '!debug.other.SAVE']
|
|
57
|
+
assert.deepEqual(Logger.getModuleOverrides(config, 'debug'), ['debug.mymod'])
|
|
58
|
+
})
|
|
59
|
+
|
|
60
|
+
it('should not include id-wide entries', () => {
|
|
61
|
+
const config = ['debug.mymod', 'mymod', '!other']
|
|
62
|
+
assert.deepEqual(Logger.getModuleOverrides(config, 'debug'), ['debug.mymod'])
|
|
63
|
+
})
|
|
64
|
+
})
|
|
65
|
+
|
|
66
|
+
describe('.getLineOverrides()', () => {
|
|
67
|
+
it('should return only 3-segment entries for the level', () => {
|
|
68
|
+
const config = ['debug', 'debug.mymod', 'debug.mymod.LOAD', '!debug.other.SAVE', 'verbose.foo.BAR']
|
|
69
|
+
assert.deepEqual(Logger.getLineOverrides(config, 'debug'), ['debug.mymod.LOAD', '!debug.other.SAVE'])
|
|
70
|
+
})
|
|
71
|
+
|
|
72
|
+
it('should return empty array when no line overrides', () => {
|
|
73
|
+
assert.deepEqual(Logger.getLineOverrides(['debug', 'debug.mymod'], 'debug'), [])
|
|
74
|
+
})
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
describe('.getIdOverrides()', () => {
|
|
78
|
+
it('should return entries whose first segment is not a known level', () => {
|
|
79
|
+
const config = ['debug', 'debug.mymod', 'mymod', '!other', 'datacache']
|
|
80
|
+
assert.deepEqual(Logger.getIdOverrides(config), ['mymod', '!other', 'datacache'])
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
it('should not include bare level entries', () => {
|
|
84
|
+
assert.deepEqual(Logger.getIdOverrides(['debug', '!verbose']), [])
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
it('should not include level-prefixed entries', () => {
|
|
88
|
+
assert.deepEqual(Logger.getIdOverrides(['debug.mymod', '!verbose.foo.BAR']), [])
|
|
89
|
+
})
|
|
90
|
+
})
|
|
91
|
+
|
|
92
|
+
describe('.isLoggingEnabled()', () => {
|
|
93
|
+
it('should return true when level is enabled', () => {
|
|
94
|
+
const levels = { error: { enable: true, moduleOverrides: [] } }
|
|
95
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'error', 'test'), true)
|
|
96
|
+
})
|
|
97
|
+
|
|
98
|
+
it('should return false when level is disabled', () => {
|
|
99
|
+
const levels = { error: { enable: false, moduleOverrides: [] } }
|
|
100
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'error', 'test'), false)
|
|
101
|
+
})
|
|
102
|
+
|
|
103
|
+
it('should allow module-specific override', () => {
|
|
104
|
+
const levels = { debug: { enable: false, moduleOverrides: ['debug.mymod'] } }
|
|
105
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'debug', 'mymod'), true)
|
|
106
|
+
})
|
|
107
|
+
|
|
108
|
+
it('should allow module-specific disable override', () => {
|
|
109
|
+
const levels = { debug: { enable: true, moduleOverrides: ['!debug.mymod'] } }
|
|
110
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'debug', 'mymod'), false)
|
|
111
|
+
})
|
|
112
|
+
|
|
113
|
+
it('should mute a specific line via line-level override', () => {
|
|
114
|
+
const levels = { verbose: { enable: true, moduleOverrides: [], lineOverrides: ['!verbose.server.REQUEST_DURATION'] } }
|
|
115
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'server', 'REQUEST_DURATION'), false)
|
|
116
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'server', 'ADD_ROUTE'), true)
|
|
117
|
+
})
|
|
118
|
+
|
|
119
|
+
it('should enable a specific line when level is otherwise disabled', () => {
|
|
120
|
+
const levels = { verbose: { enable: false, moduleOverrides: [], lineOverrides: ['verbose.server.ADD_ROUTE'] } }
|
|
121
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'server', 'ADD_ROUTE'), true)
|
|
122
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'server', 'OTHER'), false)
|
|
123
|
+
})
|
|
124
|
+
|
|
125
|
+
it('should let line-level mute beat per-level module enable', () => {
|
|
126
|
+
const levels = { verbose: { enable: true, moduleOverrides: ['verbose.server'], lineOverrides: ['!verbose.server.REQUEST_DURATION'] } }
|
|
127
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'server', 'REQUEST_DURATION'), false)
|
|
128
|
+
})
|
|
129
|
+
|
|
130
|
+
it('should mute via id-wide override at any level', () => {
|
|
131
|
+
const levels = { verbose: { enable: true, moduleOverrides: [], lineOverrides: [] } }
|
|
132
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'datacache', undefined, ['!datacache']), false)
|
|
133
|
+
})
|
|
134
|
+
|
|
135
|
+
it('should let per-level module enable beat id-wide mute', () => {
|
|
136
|
+
const levels = { verbose: { enable: false, moduleOverrides: ['verbose.datacache'], lineOverrides: [] } }
|
|
137
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'datacache', undefined, ['!datacache']), true)
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
it('should let id-wide enable beat global level mute', () => {
|
|
141
|
+
const levels = { verbose: { enable: false, moduleOverrides: [], lineOverrides: [] } }
|
|
142
|
+
assert.equal(Logger.isLoggingEnabled(levels, 'verbose', 'datacache', undefined, ['datacache']), true)
|
|
143
|
+
})
|
|
144
|
+
})
|
|
145
|
+
|
|
146
|
+
describe('#log()', () => {
|
|
147
|
+
it('should not throw when muted via empty levels', () => {
|
|
148
|
+
const logger = new Logger({ levels: [] })
|
|
149
|
+
assert.doesNotThrow(() => logger.log('error', 'test', 'message'))
|
|
150
|
+
})
|
|
151
|
+
|
|
152
|
+
it('should mute a specific line via config', () => {
|
|
153
|
+
const calls = []
|
|
154
|
+
const origInfo = console.info
|
|
155
|
+
console.info = (...args) => calls.push(args)
|
|
156
|
+
try {
|
|
157
|
+
const logger = new Logger({ levels: ['info', '!info.server.REQUEST_DURATION'], showTimestamp: false })
|
|
158
|
+
logger.log('info', 'server', 'REQUEST_DURATION', 'GET', '/foo')
|
|
159
|
+
logger.log('info', 'server', 'ADD_ROUTE', 'GET', '/foo')
|
|
160
|
+
assert.equal(calls.length, 1)
|
|
161
|
+
assert.match(calls[0].join(' '), /ADD_ROUTE/)
|
|
162
|
+
} finally {
|
|
163
|
+
console.info = origInfo
|
|
164
|
+
}
|
|
165
|
+
})
|
|
166
|
+
|
|
167
|
+
it('should mute an id at every level via config', () => {
|
|
168
|
+
const calls = []
|
|
169
|
+
const origInfo = console.info
|
|
170
|
+
const origWarn = console.warn
|
|
171
|
+
console.info = (...args) => calls.push(['info', args])
|
|
172
|
+
console.warn = (...args) => calls.push(['warn', args])
|
|
173
|
+
try {
|
|
174
|
+
const logger = new Logger({ levels: ['info', 'warn', '!datacache'], showTimestamp: false })
|
|
175
|
+
logger.log('info', 'datacache', 'hit')
|
|
176
|
+
logger.log('warn', 'datacache', 'miss')
|
|
177
|
+
logger.log('info', 'server', 'ok')
|
|
178
|
+
assert.equal(calls.length, 1)
|
|
179
|
+
assert.equal(calls[0][0], 'info')
|
|
180
|
+
assert.match(calls[0][1].join(' '), /server/)
|
|
181
|
+
} finally {
|
|
182
|
+
console.info = origInfo
|
|
183
|
+
console.warn = origWarn
|
|
184
|
+
}
|
|
185
|
+
})
|
|
186
|
+
})
|
|
187
|
+
})
|
|
@@ -7,16 +7,10 @@ describe('getArgs()', () => {
|
|
|
7
7
|
it('should return an object with parsed arguments', () => {
|
|
8
8
|
const args = getArgs()
|
|
9
9
|
assert.equal(typeof args, 'object')
|
|
10
|
-
assert.ok(Array.isArray(args.params))
|
|
11
10
|
})
|
|
12
11
|
|
|
13
|
-
it('should include
|
|
12
|
+
it('should include params as an array', () => {
|
|
14
13
|
const args = getArgs()
|
|
15
|
-
assert.ok(Array.isArray(args.
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it('should derive params by slicing first two entries from _', () => {
|
|
19
|
-
const args = getArgs()
|
|
20
|
-
assert.deepEqual(args.params, args._.slice(2))
|
|
14
|
+
assert.ok(Array.isArray(args.params))
|
|
21
15
|
})
|
|
22
16
|
})
|
package/tests/App.spec.js
DELETED
|
@@ -1,160 +0,0 @@
|
|
|
1
|
-
import { describe, it, before, after } from 'node:test'
|
|
2
|
-
import assert from 'node:assert/strict'
|
|
3
|
-
import fs from 'fs-extra'
|
|
4
|
-
import path from 'path'
|
|
5
|
-
import { fileURLToPath } from 'url'
|
|
6
|
-
import App from '../lib/App.js'
|
|
7
|
-
|
|
8
|
-
const __dirname = path.dirname(fileURLToPath(import.meta.url))
|
|
9
|
-
|
|
10
|
-
describe('App', () => {
|
|
11
|
-
let testRootDir
|
|
12
|
-
let originalRootDir
|
|
13
|
-
|
|
14
|
-
before(async () => {
|
|
15
|
-
testRootDir = path.join(__dirname, 'data', 'app-test')
|
|
16
|
-
await fs.ensureDir(testRootDir)
|
|
17
|
-
await fs.writeJson(path.join(testRootDir, 'package.json'), {
|
|
18
|
-
name: 'test-app',
|
|
19
|
-
version: '1.0.0'
|
|
20
|
-
})
|
|
21
|
-
await fs.writeJson(path.join(testRootDir, 'adapt-authoring.json'), {
|
|
22
|
-
essentialApis: []
|
|
23
|
-
})
|
|
24
|
-
originalRootDir = process.env.ROOT_DIR
|
|
25
|
-
process.env.ROOT_DIR = testRootDir
|
|
26
|
-
})
|
|
27
|
-
|
|
28
|
-
after(async () => {
|
|
29
|
-
if (originalRootDir !== undefined) {
|
|
30
|
-
process.env.ROOT_DIR = originalRootDir
|
|
31
|
-
} else {
|
|
32
|
-
delete process.env.ROOT_DIR
|
|
33
|
-
}
|
|
34
|
-
await fs.remove(testRootDir)
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('.instance', () => {
|
|
38
|
-
it('should return an App instance', () => {
|
|
39
|
-
const app = App.instance
|
|
40
|
-
assert.ok(app instanceof App)
|
|
41
|
-
})
|
|
42
|
-
|
|
43
|
-
it('should return the same instance on subsequent calls (singleton)', () => {
|
|
44
|
-
const app1 = App.instance
|
|
45
|
-
const app2 = App.instance
|
|
46
|
-
assert.equal(app1, app2)
|
|
47
|
-
})
|
|
48
|
-
})
|
|
49
|
-
|
|
50
|
-
describe('constructor', () => {
|
|
51
|
-
it('should set name to adapt-authoring-core', () => {
|
|
52
|
-
const app = App.instance
|
|
53
|
-
assert.equal(app.name, 'adapt-authoring-core')
|
|
54
|
-
})
|
|
55
|
-
|
|
56
|
-
it('should set rootDir from ROOT_DIR env var', () => {
|
|
57
|
-
const app = App.instance
|
|
58
|
-
assert.equal(app.rootDir, testRootDir)
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('should initialize git info', () => {
|
|
62
|
-
const app = App.instance
|
|
63
|
-
assert.equal(typeof app.git, 'object')
|
|
64
|
-
})
|
|
65
|
-
})
|
|
66
|
-
|
|
67
|
-
describe('#dependencies', () => {
|
|
68
|
-
it('should return the dependency configs from dependencyloader', () => {
|
|
69
|
-
const app = App.instance
|
|
70
|
-
assert.equal(typeof app.dependencies, 'object')
|
|
71
|
-
assert.equal(app.dependencies, app.dependencyloader.configs)
|
|
72
|
-
})
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
describe('#getGitInfo()', () => {
|
|
76
|
-
it('should return an object', () => {
|
|
77
|
-
const app = App.instance
|
|
78
|
-
const info = app.getGitInfo()
|
|
79
|
-
assert.equal(typeof info, 'object')
|
|
80
|
-
})
|
|
81
|
-
|
|
82
|
-
it('should return empty object when .git directory does not exist', () => {
|
|
83
|
-
const app = App.instance
|
|
84
|
-
const origRootDir = app.rootDir
|
|
85
|
-
app.rootDir = '/nonexistent/path'
|
|
86
|
-
const info = app.getGitInfo()
|
|
87
|
-
app.rootDir = origRootDir
|
|
88
|
-
assert.deepEqual(info, {})
|
|
89
|
-
})
|
|
90
|
-
|
|
91
|
-
it('should return object with branch and commit when .git exists', async () => {
|
|
92
|
-
const gitDir = path.join(testRootDir, '.git')
|
|
93
|
-
const refsDir = path.join(gitDir, 'refs', 'heads')
|
|
94
|
-
await fs.ensureDir(refsDir)
|
|
95
|
-
await fs.writeFile(path.join(gitDir, 'HEAD'), 'ref: refs/heads/main\n')
|
|
96
|
-
await fs.writeFile(path.join(refsDir, 'main'), 'abc123def456\n')
|
|
97
|
-
|
|
98
|
-
const app = App.instance
|
|
99
|
-
const origRootDir = app.rootDir
|
|
100
|
-
app.rootDir = testRootDir
|
|
101
|
-
const info = app.getGitInfo()
|
|
102
|
-
app.rootDir = origRootDir
|
|
103
|
-
|
|
104
|
-
assert.equal(info.branch, 'main')
|
|
105
|
-
assert.equal(info.commit, 'abc123def456')
|
|
106
|
-
|
|
107
|
-
await fs.remove(gitDir)
|
|
108
|
-
})
|
|
109
|
-
})
|
|
110
|
-
|
|
111
|
-
describe('#waitForModule()', () => {
|
|
112
|
-
it('should delegate to dependencyloader.waitForModule', async () => {
|
|
113
|
-
const app = App.instance
|
|
114
|
-
let calledWith
|
|
115
|
-
const origWaitForModule = app.dependencyloader.waitForModule.bind(app.dependencyloader)
|
|
116
|
-
app.dependencyloader.waitForModule = async (name) => {
|
|
117
|
-
calledWith = name
|
|
118
|
-
return { name }
|
|
119
|
-
}
|
|
120
|
-
const result = await app.waitForModule('test-mod')
|
|
121
|
-
app.dependencyloader.waitForModule = origWaitForModule
|
|
122
|
-
|
|
123
|
-
assert.equal(calledWith, 'test-mod')
|
|
124
|
-
assert.deepEqual(result, { name: 'test-mod' })
|
|
125
|
-
})
|
|
126
|
-
|
|
127
|
-
it('should return array when multiple module names are passed', async () => {
|
|
128
|
-
const app = App.instance
|
|
129
|
-
const origWaitForModule = app.dependencyloader.waitForModule.bind(app.dependencyloader)
|
|
130
|
-
app.dependencyloader.waitForModule = async (name) => ({ name })
|
|
131
|
-
const result = await app.waitForModule('mod-a', 'mod-b')
|
|
132
|
-
app.dependencyloader.waitForModule = origWaitForModule
|
|
133
|
-
|
|
134
|
-
assert.ok(Array.isArray(result))
|
|
135
|
-
assert.equal(result.length, 2)
|
|
136
|
-
assert.deepEqual(result[0], { name: 'mod-a' })
|
|
137
|
-
assert.deepEqual(result[1], { name: 'mod-b' })
|
|
138
|
-
})
|
|
139
|
-
|
|
140
|
-
it('should return single result (not array) for single module', async () => {
|
|
141
|
-
const app = App.instance
|
|
142
|
-
const origWaitForModule = app.dependencyloader.waitForModule.bind(app.dependencyloader)
|
|
143
|
-
app.dependencyloader.waitForModule = async (name) => ({ name })
|
|
144
|
-
const result = await app.waitForModule('single-mod')
|
|
145
|
-
app.dependencyloader.waitForModule = origWaitForModule
|
|
146
|
-
|
|
147
|
-
assert.ok(!Array.isArray(result))
|
|
148
|
-
assert.deepEqual(result, { name: 'single-mod' })
|
|
149
|
-
})
|
|
150
|
-
})
|
|
151
|
-
|
|
152
|
-
describe('#setReady()', () => {
|
|
153
|
-
it('should set _isStarting to false', async () => {
|
|
154
|
-
const app = App.instance
|
|
155
|
-
app._isStarting = true
|
|
156
|
-
await app.setReady()
|
|
157
|
-
assert.equal(app._isStarting, false)
|
|
158
|
-
})
|
|
159
|
-
})
|
|
160
|
-
})
|