bajo 2.23.0 → 2.25.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.
Files changed (108) hide show
  1. package/.jsdoc.conf.json +2 -1
  2. package/class/app.js +220 -107
  3. package/class/bajo.js +257 -177
  4. package/class/base.js +85 -24
  5. package/class/cache.js +8 -16
  6. package/class/err.js +31 -10
  7. package/class/log.js +46 -38
  8. package/class/plugin.js +53 -30
  9. package/class/print.js +18 -15
  10. package/class/tools.js +17 -7
  11. package/docs/App.html +51 -1
  12. package/docs/Bajo.html +1 -1
  13. package/docs/Base.html +34 -1
  14. package/docs/Cache.html +1 -1
  15. package/docs/Err.html +2 -2
  16. package/docs/Log.html +2 -2
  17. package/docs/Main.html +3 -0
  18. package/docs/Plugin.html +1 -1
  19. package/docs/Print.html +1 -1
  20. package/docs/Tools.html +1 -1
  21. package/docs/class_app.js.html +222 -109
  22. package/docs/class_bajo.js.html +260 -179
  23. package/docs/class_base.js.html +87 -26
  24. package/docs/class_cache.js.html +10 -18
  25. package/docs/class_err.js.html +33 -12
  26. package/docs/class_log.js.html +48 -40
  27. package/docs/class_plugin.js.html +55 -32
  28. package/docs/class_print.js.html +20 -17
  29. package/docs/class_tools.js.html +19 -9
  30. package/docs/data/search.json +1 -1
  31. package/docs/global.html +22 -1
  32. package/docs/index.html +1 -1
  33. package/docs/index.js.html +53 -16
  34. package/docs/lib_applet-hook.js.html +328 -0
  35. package/docs/lib_config.js.html +167 -0
  36. package/docs/lib_helper.js.html +186 -192
  37. package/docs/module-Applet.html +3 -0
  38. package/docs/module-Helper.html +1 -8
  39. package/docs/module-Hook.html +1 -1
  40. package/docs/tutorial-01_WELCOME.html +3 -0
  41. package/docs/tutorial-02_GETTING_STARTED.html +125 -0
  42. package/docs/tutorial-03_USER_GUIDE.html +54 -0
  43. package/docs/tutorial-04_DEV_GUIDE.html +178 -0
  44. package/docs/tutorial-05_ECOSYSTEM.html +3 -0
  45. package/extend/bajo/intl/en-US.json +2 -1
  46. package/extend/bajo/intl/id.json +2 -1
  47. package/index.js +51 -14
  48. package/lib/applet-hook.js +325 -0
  49. package/lib/config.js +164 -0
  50. package/lib/helper.js +185 -191
  51. package/package.json +6 -2
  52. package/test/00-global-hooks.test.js +44 -0
  53. package/test/e2e/_run.js +19 -0
  54. package/test/e2e/e2e-applet-process.test.js +66 -0
  55. package/test/e2e/e2e-boot-process.test.js +62 -0
  56. package/test/e2e/e2e-hooks-process.test.js +68 -0
  57. package/test/integration/_setup.js +55 -0
  58. package/test/integration/aspect-01-boot-instance.test.js +24 -0
  59. package/test/integration/aspect-02-plugin-loading.test.js +24 -0
  60. package/test/integration/aspect-03-config-merge.test.js +27 -0
  61. package/test/integration/aspect-04-intl-loading.test.js +29 -0
  62. package/test/integration/aspect-05-cache-roundtrip.test.js +26 -0
  63. package/test/integration/aspect-06-log-file-save.test.js +28 -0
  64. package/test/integration/aspect-07-plugin-data-dir.test.js +25 -0
  65. package/test/integration/aspect-08-plugin-lookup.test.js +25 -0
  66. package/test/integration/aspect-09-config-formats.test.js +26 -0
  67. package/test/integration/aspect-10-hooks.test.js +38 -0
  68. package/test/unit/app.test.js +115 -0
  69. package/test/unit/applet-hook.test.js +10 -0
  70. package/test/unit/bajo.test.js +151 -0
  71. package/test/unit/base.test.js +90 -0
  72. package/test/unit/cache.test.js +89 -0
  73. package/test/unit/config.test.js +14 -0
  74. package/test/unit/err.test.js +57 -0
  75. package/test/unit/helper.test.js +116 -0
  76. package/test/unit/index.test.js +40 -0
  77. package/test/unit/log.test.js +84 -0
  78. package/test/unit/plugin.test.js +95 -0
  79. package/test/unit/print.test.js +72 -0
  80. package/test/unit/tools.test.js +29 -0
  81. package/wiki/CHANGES.md +10 -0
  82. package/wiki/tutorials/01_WELCOME.md +18 -0
  83. package/wiki/tutorials/02_GETTING_STARTED.md +366 -0
  84. package/wiki/tutorials/03_USER_GUIDE.md +262 -0
  85. package/wiki/tutorials/04_DEV_GUIDE.md +386 -0
  86. package/wiki/tutorials/05_ECOSYSTEM.md +95 -0
  87. package/wiki/tutorials/tutorials.json +17 -0
  88. package/docs/lib_hook.js.html +0 -231
  89. package/docs/module-Lib.html +0 -3
  90. package/lib/hook.js +0 -228
  91. package/test/app.test.js +0 -183
  92. package/test/bajo.test.js +0 -124
  93. package/test/base.test.js +0 -75
  94. package/test/cache.test.js +0 -94
  95. package/test/e2e.test.js +0 -137
  96. package/test/err.test.js +0 -73
  97. package/test/helper.test.js +0 -39
  98. package/test/import-module.test.js +0 -138
  99. package/test/integration.test.js +0 -218
  100. package/test/log.test.js +0 -119
  101. package/test/plugin.test.js +0 -116
  102. package/test/print.test.js +0 -100
  103. package/test/tools.test.js +0 -38
  104. package/wiki/CONFIG.md +0 -36
  105. package/wiki/DEV_GUIDE.md +0 -3
  106. package/wiki/ECOSYSTEM.md +0 -93
  107. package/wiki/GETTING-STARTED.md +0 -356
  108. package/wiki/USER-GUIDE.md +0 -256
@@ -1,39 +0,0 @@
1
- /* global describe, it */
2
-
3
- import { expect } from 'chai'
4
-
5
- import { outmatchNs, parseObject, lib } from '../class/_helper.js'
6
-
7
- describe('_helper', () => {
8
- it('matches namespace/path patterns', () => {
9
- const ctx = {
10
- bajo: {
11
- breakNsPath: (pattern) => {
12
- const [fullNs, p] = pattern.split(':')
13
- return { fullNs, path: p }
14
- }
15
- }
16
- }
17
-
18
- expect(outmatchNs.call(ctx, 'demo.api:users/12', 'demo.api:users/*')).to.equal(true)
19
- expect(outmatchNs.call(ctx, 'demo.api', 'demo.api')).to.equal(true)
20
- expect(outmatchNs.call(ctx, 'demo.api:users/12', 'x.api:users/*')).to.equal(false)
21
- })
22
-
23
- it('parses object and applies translator from namespace', () => {
24
- const ctx = {
25
- bajo: {
26
- t: (text, arg) => `T:${text}:${arg}`
27
- }
28
- }
29
- const parsed = parseObject.call(ctx, { msg: 't:hello|world' }, { ns: 'bajo', lang: 'en-US', parseValue: true })
30
-
31
- expect(parsed).to.deep.equal({ msg: 'T:hello:world' })
32
- })
33
-
34
- it('exports library helpers', () => {
35
- expect(lib).to.have.property('_')
36
- expect(lib).to.have.property('fs')
37
- expect(lib).to.have.property('dayjs')
38
- })
39
- })
@@ -1,138 +0,0 @@
1
- /* global describe, it, beforeEach, afterEach */
2
-
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { expect } from 'chai'
6
- import fs from 'fs-extra'
7
-
8
- import importModule from '../lib/import-module.js'
9
-
10
- const createTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'bajo-import-module-test-'))
11
-
12
- const writeModule = async (filePath, content) => {
13
- await fs.ensureDir(path.dirname(filePath))
14
- await fs.writeFile(filePath, content, 'utf8')
15
- }
16
-
17
- describe('importModule', () => {
18
- let rootDir
19
-
20
- beforeEach(() => {
21
- rootDir = createTempRoot()
22
- })
23
-
24
- afterEach(() => {
25
- delete globalThis.__importModuleCounter
26
- if (rootDir) fs.rmSync(rootDir, { recursive: true, force: true })
27
- })
28
-
29
- it('imports default export by default', async () => {
30
- const file = path.join(rootDir, 'default.js')
31
- await writeModule(file, 'export default "hello"')
32
-
33
- const mod = await importModule(file)
34
-
35
- expect(mod).to.equal('hello')
36
- })
37
-
38
- it('returns module namespace when asDefaultImport is false', async () => {
39
- const file = path.join(rootDir, 'named.js')
40
- await writeModule(file, 'export const answer = 42; export default "x"')
41
-
42
- const mod = await importModule(file, { asDefaultImport: false })
43
-
44
- expect(mod).to.have.property('default', 'x')
45
- expect(mod).to.have.property('answer', 42)
46
- })
47
-
48
- it('returns undefined when target file does not exist', async () => {
49
- const mod = await importModule(path.join(rootDir, 'missing.js'))
50
-
51
- expect(mod).to.equal(undefined)
52
- })
53
-
54
- it('imports a fresh copy when noCache is true', async () => {
55
- const file = path.join(rootDir, 'counter.js')
56
- await writeModule(file, 'globalThis.__importModuleCounter = (globalThis.__importModuleCounter ?? 0) + 1; export default globalThis.__importModuleCounter')
57
-
58
- const first = await importModule(file, { noCache: true })
59
- const second = await importModule(file, { noCache: true })
60
-
61
- expect(first).to.equal(1)
62
- expect(second).to.equal(2)
63
- })
64
-
65
- it('resolves plugin file through context when called with this', async () => {
66
- const file = path.join(rootDir, 'ctx.js')
67
- await writeModule(file, 'export default "from-context"')
68
- const ctx = {
69
- app: {
70
- getPluginFile: (input) => {
71
- expect(input).to.equal('plugin:file')
72
- return file
73
- }
74
- }
75
- }
76
-
77
- const mod = await importModule.call(ctx, 'plugin:file')
78
-
79
- expect(mod).to.equal('from-context')
80
- })
81
-
82
- it('wraps function modules into handler object when asHandler is true', async () => {
83
- const file = path.join(rootDir, 'handler-fn.js')
84
- await writeModule(file, 'export default function sampleHandler () { return "ok" }')
85
-
86
- const mod = await importModule(file, { asHandler: true })
87
-
88
- expect(mod).to.have.property('level', 999)
89
- expect(mod).to.have.property('handler').that.is.a('function')
90
- expect(mod.handler()).to.equal('ok')
91
- })
92
-
93
- it('returns plain object modules as-is in handler mode', async () => {
94
- const file = path.join(rootDir, 'handler-obj.js')
95
- await writeModule(file, 'export default { level: 100, handler: () => "ok" }')
96
-
97
- const mod = await importModule(file, { asHandler: true })
98
-
99
- expect(mod).to.have.property('level', 100)
100
- expect(mod).to.have.property('handler').that.is.a('function')
101
- })
102
-
103
- it('throws a generic error for non-handler modules without context', async () => {
104
- const file = path.join(rootDir, 'bad-handler.js')
105
- await writeModule(file, 'export default 123')
106
-
107
- let err
108
- try {
109
- await importModule(file, { asHandler: true })
110
- } catch (error) {
111
- err = error
112
- }
113
-
114
- expect(err).to.be.instanceOf(Error)
115
- expect(err.message).to.equal(`File '${file}' is NOT a handler module`)
116
- })
117
-
118
- it('throws contextual error for non-handler modules with context', async () => {
119
- const file = path.join(rootDir, 'bad-handler-ctx.js')
120
- await writeModule(file, 'export default 123')
121
- const ctx = {
122
- app: {
123
- getPluginFile: () => file
124
- },
125
- error: (code, target) => new Error(`${code}:${target}`)
126
- }
127
-
128
- let err
129
- try {
130
- await importModule.call(ctx, 'any:file', { asHandler: true })
131
- } catch (error) {
132
- err = error
133
- }
134
-
135
- expect(err).to.be.instanceOf(Error)
136
- expect(err.message).to.equal(`fileNotModuleHandler%s:${file}`)
137
- })
138
- })
@@ -1,218 +0,0 @@
1
- /* global describe, it, beforeEach, afterEach */
2
-
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { expect } from 'chai'
6
- import fs from 'fs-extra'
7
-
8
- import App from '../class/app.js'
9
- import Bajo from '../class/bajo.js'
10
- import Base from '../class/base.js'
11
- import Plugin from '../class/plugin.js'
12
- import Log from '../class/log.js'
13
- import boot from '../index.js'
14
-
15
- const createTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'bajo-integration-test-'))
16
-
17
- const cleanupNewProcessListeners = (beforeCounts) => {
18
- const events = Object.keys(beforeCounts)
19
- for (const event of events) {
20
- const listeners = process.listeners(event)
21
- const keep = beforeCounts[event]
22
- if (listeners.length <= keep) continue
23
- for (const listener of listeners.slice(keep)) {
24
- process.removeListener(event, listener)
25
- }
26
- }
27
- }
28
-
29
- describe('Integration', () => {
30
- let rootDir
31
-
32
- beforeEach(async () => {
33
- rootDir = createTempRoot()
34
- await fs.writeJson(path.join(rootDir, 'package.json'), {
35
- name: 'bajo-integration-app',
36
- type: 'module'
37
- })
38
- })
39
-
40
- afterEach(() => {
41
- if (rootDir) fs.rmSync(rootDir, { recursive: true, force: true })
42
- })
43
-
44
- it('integrates App + Bajo + Plugin with real package info and ns-path file resolution', async () => {
45
- const app = new App({ cwd: rootDir })
46
- const bajo = new Bajo(app)
47
- app.bajo = bajo
48
- bajo.dir = {
49
- base: rootDir,
50
- data: path.join(rootDir, 'data')
51
- }
52
- await fs.ensureDir(bajo.dir.data)
53
-
54
- const pluginDir = path.join(rootDir, 'plugins', 'demo-plugin')
55
- await fs.ensureDir(pluginDir)
56
- await fs.writeJson(path.join(pluginDir, 'package.json'), {
57
- name: 'demo-plugin',
58
- version: '1.0.0',
59
- description: 'integration plugin'
60
- })
61
-
62
- const plugin = new Plugin('demo-plugin', app)
63
- plugin.dir = { pkg: pluginDir }
64
- app.addPlugin(plugin)
65
- app.pluginPkgs = ['demo-plugin']
66
-
67
- expect(plugin.getPkgInfo()).to.deep.equal({
68
- name: 'demo-plugin',
69
- version: '1.0.0',
70
- description: 'integration plugin'
71
- })
72
-
73
- const dataDir = app.getPluginDataDir('demoPlugin')
74
- expect(fs.existsSync(dataDir)).to.equal(true)
75
-
76
- const resolved = app.getPluginFile('demoPlugin:/src/index.js')
77
- expect(resolved).to.equal(path.join(pluginDir, 'src/index.js'))
78
- })
79
-
80
- it('integrates Base.loadConfig with real config files', async () => {
81
- const app = new App({ cwd: rootDir })
82
- const bajo = new Bajo(app)
83
- app.bajo = bajo
84
- app.pluginPkgs = ['main']
85
-
86
- bajo.dir = {
87
- base: rootDir,
88
- data: path.join(rootDir, 'data')
89
- }
90
- bajo.config = { env: 'dev' }
91
- await fs.ensureDir(path.join(bajo.dir.data, 'config'))
92
- bajo.config.log = {
93
- level: 'silent',
94
- save: false,
95
- pretty: false,
96
- useUtc: false,
97
- timeTaken: false,
98
- dateFormat: 'YYYY-MM-DD',
99
- rotation: { cycle: 'none', byPlugin: false }
100
- }
101
- app.log = new Log(app)
102
- await fs.writeJson(path.join(bajo.dir.data, 'config', 'main.json'), {
103
- title: 'Integration Main',
104
- main: {
105
- feature: true,
106
- port: 7000
107
- }
108
- })
109
-
110
- const main = new Base('main', app)
111
- main.config = {
112
- main: {
113
- feature: false,
114
- port: 3000
115
- }
116
- }
117
- app.addPlugin(main)
118
-
119
- await main.loadConfig()
120
-
121
- expect(main.getConfig('main.feature')).to.equal(true)
122
- expect(main.getConfig('main.port')).to.equal(7000)
123
- expect(main.getConfig('title')).to.equal('Integration Main')
124
- expect(main.dir.pkg).to.equal(path.join(rootDir, 'main'))
125
- expect(main.dir.data).to.equal(path.join(bajo.dir.data, 'plugins', 'main'))
126
- })
127
-
128
- it('integrates App cache save/load using Bajo ns parser and filesystem cache', async () => {
129
- const app = new App({ cwd: rootDir })
130
- const bajo = new Bajo(app)
131
- app.bajo = bajo
132
- bajo.dir = {
133
- base: rootDir,
134
- data: path.join(rootDir, 'data')
135
- }
136
- await fs.ensureDir(bajo.dir.data)
137
-
138
- const content = { ok: true, items: [1, 2, 3] }
139
- await app.cache.save('bajo.api:items/list', content, 60000)
140
- const loaded = await app.cache.load('bajo.api:items/list', 60000)
141
-
142
- expect(loaded).to.deep.equal(content)
143
- })
144
-
145
- it('integrates real full boot flow through index entry with disk plugin', async () => {
146
- const pluginName = 'integration-plugin'
147
- const pluginDir = path.join(rootDir, 'node_modules', pluginName)
148
- await fs.ensureDir(pluginDir)
149
- await fs.writeJson(path.join(rootDir, 'package.json'), {
150
- name: 'bajo-integration-app',
151
- type: 'module',
152
- bajo: {
153
- plugins: [pluginName]
154
- }
155
- })
156
- await fs.writeJson(path.join(pluginDir, 'package.json'), {
157
- name: pluginName,
158
- version: '1.0.0',
159
- type: 'module',
160
- main: 'index.js'
161
- })
162
- await fs.writeFile(path.join(pluginDir, 'index.js'), `
163
- async function factory (pkgName) {
164
- const me = this
165
-
166
- return class IntegrationPlugin extends this.app.baseClass.Base {
167
- constructor () {
168
- super(pkgName, me.app)
169
- this.config = { started: false }
170
- this.start = async () => {
171
- this.state.started = true
172
- const dir = this.app.getPluginDataDir(this.ns)
173
- this.app.lib.fs.writeFileSync(dir + '/started.txt', 'ok', 'utf8')
174
- }
175
- }
176
- }
177
- }
178
-
179
- export default factory
180
- `, 'utf8')
181
-
182
- const dataDir = path.join(rootDir, 'data', 'config')
183
- await fs.ensureDir(dataDir)
184
- await fs.writeJson(path.join(dataDir, 'bajo.json'), {
185
- log: { level: 'silent', save: false },
186
- exitHandler: false,
187
- cache: { purgeIntvDur: '1h' }
188
- })
189
-
190
- const events = ['SIGINT', 'SIGTERM', 'beforeExit', 'uncaughtException', 'unhandledRejection', 'warning']
191
- const beforeCounts = events.reduce((acc, event) => {
192
- acc[event] = process.listeners(event).length
193
- return acc
194
- }, {})
195
-
196
- const originalSetInterval = global.setInterval
197
- const createdIntervals = []
198
- global.setInterval = (fn, delay, ...args) => {
199
- const id = originalSetInterval(fn, delay, ...args)
200
- createdIntervals.push(id)
201
- return id
202
- }
203
-
204
- const app = await boot({ cwd: rootDir })
205
-
206
- try {
207
- expect(app).to.be.an('object')
208
- expect(app.integrationPlugin).to.be.an('object')
209
- const startedFile = path.join(rootDir, 'data', 'plugins', 'integrationPlugin', 'started.txt')
210
- expect(fs.existsSync(startedFile)).to.equal(true)
211
- expect(fs.readFileSync(startedFile, 'utf8')).to.equal('ok')
212
- } finally {
213
- global.setInterval = originalSetInterval
214
- for (const id of createdIntervals) clearInterval(id)
215
- cleanupNewProcessListeners(beforeCounts)
216
- }
217
- })
218
- })
package/test/log.test.js DELETED
@@ -1,119 +0,0 @@
1
- /* global describe, it, beforeEach, afterEach */
2
-
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { expect } from 'chai'
6
- import fs from 'fs-extra'
7
- import dayjs from 'dayjs'
8
-
9
- import Log from '../class/log.js'
10
-
11
- const createTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'bajo-log-test-'))
12
-
13
- describe('Log', () => {
14
- let root
15
- let app
16
- let log
17
-
18
- beforeEach(() => {
19
- root = createTempRoot()
20
- app = {
21
- runAt: new Date(),
22
- t: (prefix, text, ...args) => `${prefix}:${text}:${args.join('|')}`,
23
- lib: {
24
- fs,
25
- dayjs,
26
- _: {
27
- isEmpty: (v) => v == null || v === '' || (typeof v === 'object' && Object.keys(v).length === 0),
28
- merge: (a, b) => Object.assign(a, b),
29
- without: (arr, item) => arr.filter(i => i !== item)
30
- }
31
- },
32
- bajo: {
33
- dir: { data: root },
34
- config: {
35
- env: 'dev',
36
- log: {
37
- level: 'trace',
38
- save: false,
39
- pretty: false,
40
- useUtc: false,
41
- timeTaken: false,
42
- dateFormat: 'YYYY-MM-DD',
43
- rotation: { cycle: 'none', byPlugin: false }
44
- }
45
- },
46
- isLogInRange: () => true
47
- }
48
- }
49
- })
50
-
51
- afterEach(() => {
52
- if (root) fs.rmSync(root, { recursive: true, force: true })
53
- })
54
-
55
- it('formats and prints messages in dev mode', () => {
56
- const originalLog = console.log
57
- const lines = []
58
- console.log = (line) => lines.push(line)
59
- log = new Log(app)
60
-
61
- try {
62
- log.info('main', 'hello %s', 'x')
63
- } finally {
64
- console.log = originalLog
65
- }
66
-
67
- expect(lines.length).to.equal(1)
68
- expect(lines[0]).to.include('[INFO]')
69
- expect(lines[0]).to.include('[main]')
70
- expect(lines[0]).to.include('main:hello %s:x')
71
- })
72
-
73
- it('returns error message fallback by code/statusCode', () => {
74
- log = new Log(app)
75
-
76
- expect(log.getErrorMessage(new Error('x'))).to.equal('x')
77
- expect(log.getErrorMessage({ message: '', code: 'E1' })).to.equal('E1')
78
- expect(log.getErrorMessage({ message: '', statusCode: 404 })).to.equal(404)
79
- })
80
-
81
- it('builds rotation patterns and saves file', () => {
82
- app.bajo.config.log.save = true
83
- log = new Log(app)
84
- app.bajo.config.log.rotation.cycle = 'daily'
85
-
86
- const pattern = log.getRotationPattern()
87
- log.save('\u001b[31mhello\u001b[0m', 'x')
88
-
89
- expect(pattern).to.be.a('string')
90
- expect(fs.existsSync(path.join(root, 'log', `bajo.${pattern}.log`))).to.equal(true)
91
- })
92
-
93
- it('supports prod json output', () => {
94
- app.bajo.config.env = 'prod'
95
- log = new Log(app)
96
- const originalLog = console.log
97
- const lines = []
98
- console.log = (line) => lines.push(line)
99
-
100
- try {
101
- log.warn('core', { id: 1 }, 'hi')
102
- } finally {
103
- console.log = originalLog
104
- }
105
-
106
- const payload = JSON.parse(lines[0])
107
- expect(payload.prefix).to.equal('core')
108
- expect(payload.level).to.be.a('number')
109
- expect(payload).to.have.property('data')
110
- })
111
-
112
- it('disposes app reference', async () => {
113
- log = new Log(app)
114
-
115
- await log.dispose()
116
-
117
- expect(log.app).to.equal(null)
118
- })
119
- })
@@ -1,116 +0,0 @@
1
- /* global describe, it, beforeEach */
2
-
3
- import os from 'node:os'
4
- import path from 'node:path'
5
- import { expect } from 'chai'
6
- import fs from 'fs-extra'
7
- import lodash from 'lodash'
8
-
9
- import Plugin from '../class/plugin.js'
10
-
11
- const createTempRoot = () => fs.mkdtempSync(path.join(os.tmpdir(), 'bajo-plugin-test-'))
12
-
13
- describe('Plugin', () => {
14
- let app
15
-
16
- beforeEach(() => {
17
- const calls = []
18
- app = {
19
- lib: {
20
- _: lodash,
21
- fs
22
- },
23
- log: {
24
- trace: (...args) => calls.push(['trace', ...args]),
25
- debug: (...args) => calls.push(['debug', ...args]),
26
- info: (...args) => calls.push(['info', ...args]),
27
- warn: (...args) => calls.push(['warn', ...args]),
28
- error: (...args) => calls.push(['error', ...args]),
29
- fatal: (...args) => calls.push(['fatal', ...args]),
30
- silent: (...args) => calls.push(['silent', ...args])
31
- },
32
- t: (ns, text, ...params) => `${ns}:${text}:${params.join('|')}`,
33
- te: (ns, text) => `${ns}:${text}`,
34
- dump: (...args) => calls.push(['dump', ...args]),
35
- _calls: calls
36
- }
37
- })
38
-
39
- it('initializes namespace and prefixed log shortcuts', () => {
40
- const plugin = new Plugin('my-plugin', app)
41
-
42
- plugin.log.info('hello')
43
-
44
- expect(plugin.ns).to.equal('myPlugin')
45
- expect(app._calls[0]).to.deep.equal(['info', 'myPlugin', 'hello'])
46
- })
47
-
48
- it('reads package info and selected keys', async () => {
49
- const root = createTempRoot()
50
- const pkgDir = path.join(root, 'plugin')
51
- await fs.ensureDir(pkgDir)
52
- await fs.writeJson(path.join(pkgDir, 'package.json'), {
53
- name: 'my-plugin',
54
- version: '1.2.3',
55
- private: true
56
- })
57
- const plugin = new Plugin('my-plugin', app)
58
- plugin.dir = { pkg: pkgDir }
59
-
60
- expect(plugin.getPkgInfo()).to.deep.equal({
61
- name: 'my-plugin',
62
- version: '1.2.3'
63
- })
64
- expect(plugin.getPkgInfo(pkgDir, [])).to.include({ private: true })
65
- })
66
-
67
- it('returns config value with clone and omit support', () => {
68
- const plugin = new Plugin('my-plugin', app)
69
- plugin.config = { a: { b: 1, c: 2 } }
70
-
71
- const val = plugin.getConfig('a', { omit: ['c'] })
72
- val.b = 99
73
-
74
- expect(val).to.deep.equal({ b: 99 })
75
- expect(plugin.config.a.b).to.equal(1)
76
- expect(plugin.getConfig('missing', { defValue: { x: 1 } })).to.deep.equal({ x: 1 })
77
- expect(plugin.getConfig('a', { noClone: true })).to.equal(plugin.config.a)
78
- })
79
-
80
- it('returns native Error when print is unavailable', () => {
81
- const plugin = new Plugin('my-plugin', app)
82
-
83
- const err = plugin.error('plain error')
84
- const fatalErr = plugin.fatal('fatal error')
85
-
86
- expect(err).to.be.instanceOf(Error)
87
- expect(err.message).to.equal('plain error')
88
- expect(fatalErr).to.be.instanceOf(Error)
89
- })
90
-
91
- it('delegates translation and dump to app', () => {
92
- const plugin = new Plugin('my-plugin', app)
93
-
94
- expect(plugin.t('hello', 'x')).to.equal('myPlugin:hello:x')
95
- expect(plugin.te('hello')).to.equal('myPlugin:hello')
96
- plugin.dump(1, 2)
97
-
98
- expect(app._calls[0]).to.deep.equal(['dump', 1, 2])
99
- })
100
-
101
- it('binds methods and disposes references', async () => {
102
- const plugin = new Plugin('my-plugin', app)
103
- plugin.value = 42
104
- plugin.read = function () {
105
- return this.value
106
- }
107
-
108
- plugin.selfBind('read')
109
- const reader = plugin.read
110
- await plugin.dispose()
111
-
112
- expect(reader()).to.equal(42)
113
- expect(plugin.app).to.equal(null)
114
- expect(plugin.config).to.equal(null)
115
- })
116
- })