bajo 2.23.0 → 2.24.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 +246 -174
  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 +6 -5
  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 +8 -7
  30. package/docs/data/search.json +1 -1
  31. package/docs/extend_bajo_hook.js.html +289 -0
  32. package/docs/global.html +22 -1
  33. package/docs/index.html +1 -1
  34. package/docs/index.js.html +53 -16
  35. package/docs/lib_applet-hook.js.html +328 -0
  36. package/docs/lib_config.js.html +167 -0
  37. package/docs/lib_helper.js.html +186 -192
  38. package/docs/module-Applet.html +3 -0
  39. package/docs/module-Helper-Main.html +3 -0
  40. package/docs/module-Helper.html +1 -8
  41. package/docs/module-Hook.html +1 -1
  42. package/docs/tutorial-01_WELCOME.html +3 -0
  43. package/docs/tutorial-02_GETTING_STARTED.html +125 -0
  44. package/docs/tutorial-03_USER_GUIDE.html +54 -0
  45. package/docs/tutorial-04_DEV_GUIDE.html +178 -0
  46. package/docs/tutorial-05_ECOSYSTEM.html +3 -0
  47. package/extend/bajo/intl/en-US.json +2 -1
  48. package/extend/bajo/intl/id.json +2 -1
  49. package/index.js +51 -14
  50. package/lib/applet-hook.js +325 -0
  51. package/lib/config.js +164 -0
  52. package/lib/helper.js +184 -190
  53. package/package.json +6 -2
  54. package/test/00-global-hooks.test.js +44 -0
  55. package/test/e2e/_run.js +19 -0
  56. package/test/e2e/e2e-applet-process.test.js +66 -0
  57. package/test/e2e/e2e-boot-process.test.js +62 -0
  58. package/test/e2e/e2e-hooks-process.test.js +68 -0
  59. package/test/integration/_setup.js +55 -0
  60. package/test/integration/aspect-01-boot-instance.test.js +24 -0
  61. package/test/integration/aspect-02-plugin-loading.test.js +24 -0
  62. package/test/integration/aspect-03-config-merge.test.js +27 -0
  63. package/test/integration/aspect-04-intl-loading.test.js +29 -0
  64. package/test/integration/aspect-05-cache-roundtrip.test.js +26 -0
  65. package/test/integration/aspect-06-log-file-save.test.js +28 -0
  66. package/test/integration/aspect-07-plugin-data-dir.test.js +25 -0
  67. package/test/integration/aspect-08-plugin-lookup.test.js +25 -0
  68. package/test/integration/aspect-09-config-formats.test.js +26 -0
  69. package/test/integration/aspect-10-hooks.test.js +38 -0
  70. package/test/unit/app.test.js +115 -0
  71. package/test/unit/applet-hook.test.js +10 -0
  72. package/test/unit/bajo.test.js +151 -0
  73. package/test/unit/base.test.js +90 -0
  74. package/test/unit/cache.test.js +89 -0
  75. package/test/unit/config.test.js +14 -0
  76. package/test/unit/err.test.js +57 -0
  77. package/test/unit/helper.test.js +116 -0
  78. package/test/unit/index.test.js +40 -0
  79. package/test/unit/log.test.js +84 -0
  80. package/test/unit/plugin.test.js +95 -0
  81. package/test/unit/print.test.js +72 -0
  82. package/test/unit/tools.test.js +29 -0
  83. package/wiki/CHANGES.md +4 -0
  84. package/wiki/tutorials/01_WELCOME.md +18 -0
  85. package/wiki/tutorials/02_GETTING_STARTED.md +366 -0
  86. package/wiki/tutorials/03_USER_GUIDE.md +262 -0
  87. package/wiki/tutorials/04_DEV_GUIDE.md +386 -0
  88. package/wiki/tutorials/05_ECOSYSTEM.md +95 -0
  89. package/wiki/tutorials/tutorials.json +17 -0
  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
@@ -0,0 +1,44 @@
1
+ /* global beforeEach, afterEach */
2
+
3
+ const state = {
4
+ originalSetInterval: null,
5
+ createdIntervals: [],
6
+ listenerCounts: null
7
+ }
8
+
9
+ beforeEach(() => {
10
+ state.createdIntervals = []
11
+ state.listenerCounts = {
12
+ SIGINT: process.listeners('SIGINT').length,
13
+ SIGTERM: process.listeners('SIGTERM').length,
14
+ beforeExit: process.listeners('beforeExit').length,
15
+ uncaughtException: process.listeners('uncaughtException').length,
16
+ unhandledRejection: process.listeners('unhandledRejection').length,
17
+ warning: process.listeners('warning').length
18
+ }
19
+
20
+ if (!state.originalSetInterval) state.originalSetInterval = global.setInterval
21
+ global.setInterval = (fn, delay, ...args) => {
22
+ const id = state.originalSetInterval(fn, delay, ...args)
23
+ state.createdIntervals.push(id)
24
+ return id
25
+ }
26
+ })
27
+
28
+ afterEach(() => {
29
+ if (state.originalSetInterval) {
30
+ global.setInterval = state.originalSetInterval
31
+ }
32
+ for (const id of state.createdIntervals) {
33
+ clearInterval(id)
34
+ }
35
+
36
+ for (const event of Object.keys(state.listenerCounts)) {
37
+ const listeners = process.listeners(event)
38
+ const keep = state.listenerCounts[event]
39
+ if (listeners.length <= keep) continue
40
+ for (const listener of listeners.slice(keep)) {
41
+ process.removeListener(event, listener)
42
+ }
43
+ }
44
+ })
@@ -0,0 +1,19 @@
1
+ import { spawn } from 'node:child_process'
2
+
3
+ export const runNode = (cwd, file, timeoutMs = 15000) => {
4
+ return new Promise((resolve) => {
5
+ const child = spawn(process.execPath, [file], { cwd, stdio: ['ignore', 'pipe', 'pipe'] })
6
+ let stdout = ''
7
+ let stderr = ''
8
+ child.stdout.on('data', c => { stdout += c.toString() })
9
+ child.stderr.on('data', c => { stderr += c.toString() })
10
+ const timer = setTimeout(() => {
11
+ child.kill('SIGTERM')
12
+ resolve({ code: null, timedOut: true, stdout, stderr })
13
+ }, timeoutMs)
14
+ child.on('close', code => {
15
+ clearTimeout(timer)
16
+ resolve({ code, timedOut: false, stdout, stderr })
17
+ })
18
+ })
19
+ }
@@ -0,0 +1,66 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import fs from 'fs-extra'
6
+ import { expect } from 'chai'
7
+ import { runNode } from './_run.js'
8
+
9
+ describe('e2e applet process', () => {
10
+ let root
11
+
12
+ beforeEach(() => {
13
+ root = fs.mkdtempSync(path.join('/tmp', 'bajo-e2e-applet-'))
14
+ })
15
+
16
+ afterEach(() => {
17
+ if (root) fs.rmSync(root, { recursive: true, force: true })
18
+ })
19
+
20
+ it('runs applet path in separate process with real plugin', async function () {
21
+ this.timeout(25000)
22
+
23
+ const entry = pathToFileURL(path.join('/mnt/d/Projects/Bajo/bajo', 'index.js')).href
24
+ const pluginName = 'bajo-cli'
25
+ const pdir = path.join(root, 'node_modules', pluginName)
26
+
27
+ await fs.ensureDir(pdir)
28
+ await fs.writeJson(path.join(root, 'package.json'), { name: 'e2e-app', type: 'module', bajo: { plugins: [pluginName] } })
29
+ await fs.writeJson(path.join(pdir, 'package.json'), { name: pluginName, version: '1.0.0', type: 'module', main: 'index.js' })
30
+ await fs.writeFile(path.join(pdir, 'index.js'), `
31
+ async function factory (pkgName) {
32
+ const me = this
33
+ return class BajoCli extends this.app.baseClass.Base {
34
+ constructor () {
35
+ super(pkgName, me.app)
36
+ this.config = {}
37
+ }
38
+ runApplet = async () => {
39
+ await this.app.lib.fs.writeFile(this.app.dir + '/e2e-applet.txt', 'ran', 'utf8')
40
+ }
41
+ }
42
+ }
43
+ export default factory
44
+ `, 'utf8')
45
+
46
+ await fs.ensureDir(path.join(pdir, 'extend', 'bajoCli'))
47
+ await fs.writeFile(path.join(pdir, 'extend', 'bajoCli', 'applet.js'), 'export default function () { return { name: "noop" } }', 'utf8')
48
+
49
+ await fs.ensureDir(path.join(root, 'data', 'config'))
50
+ await fs.writeJson(path.join(root, 'data', 'config', 'bajo.json'), { log: { level: 'silent', save: false }, exitHandler: false, cache: { purgeIntvDur: '1h' } })
51
+
52
+ await fs.writeFile(path.join(root, 'run-app.mjs'), `
53
+ process.argv = ['node', 'index.js', '--applet=bajoCli:noop']
54
+ import boot from ${JSON.stringify(entry)}
55
+ await boot({ cwd: process.cwd() })
56
+ console.log('APPLET_OK')
57
+ process.exit(0)
58
+ `, 'utf8')
59
+
60
+ const res = await runNode(root, 'run-app.mjs')
61
+ expect(res.timedOut).to.equal(false)
62
+ expect(res.code).to.equal(0)
63
+ expect(res.stdout).to.include('APPLET_OK')
64
+ expect(fs.existsSync(path.join(root, 'e2e-applet.txt'))).to.equal(true)
65
+ })
66
+ })
@@ -0,0 +1,62 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import fs from 'fs-extra'
6
+ import { expect } from 'chai'
7
+ import { runNode } from './_run.js'
8
+
9
+ describe('e2e boot process', () => {
10
+ let root
11
+
12
+ beforeEach(() => {
13
+ root = fs.mkdtempSync(path.join('/tmp', 'bajo-e2e-boot-'))
14
+ })
15
+
16
+ afterEach(() => {
17
+ if (root) fs.rmSync(root, { recursive: true, force: true })
18
+ })
19
+
20
+ it('boots real app in separate process and runs plugin start', async function () {
21
+ this.timeout(20000)
22
+
23
+ const entry = pathToFileURL(path.join('/mnt/d/Projects/Bajo/bajo', 'index.js')).href
24
+ const pluginName = 'e2e-plugin-boot'
25
+ const pdir = path.join(root, 'node_modules', pluginName)
26
+
27
+ await fs.ensureDir(pdir)
28
+ await fs.writeJson(path.join(root, 'package.json'), { name: 'e2e-app', type: 'module', bajo: { plugins: [pluginName] } })
29
+ await fs.writeJson(path.join(pdir, 'package.json'), { name: pluginName, version: '1.0.0', type: 'module', main: 'index.js' })
30
+ await fs.writeFile(path.join(pdir, 'index.js'), `
31
+ async function factory (pkgName) {
32
+ const me = this
33
+ return class E2EPluginBoot extends this.app.baseClass.Base {
34
+ constructor () {
35
+ super(pkgName, me.app)
36
+ this.config = {}
37
+ this.start = async () => {
38
+ await this.app.lib.fs.writeFile(this.app.dir + '/e2e-boot.txt', 'started', 'utf8')
39
+ }
40
+ }
41
+ }
42
+ }
43
+ export default factory
44
+ `, 'utf8')
45
+
46
+ await fs.ensureDir(path.join(root, 'data', 'config'))
47
+ await fs.writeJson(path.join(root, 'data', 'config', 'bajo.json'), { log: { level: 'silent', save: false }, exitHandler: false, cache: { purgeIntvDur: '1h' } })
48
+
49
+ await fs.writeFile(path.join(root, 'run-app.mjs'), `
50
+ import boot from ${JSON.stringify(entry)}
51
+ const app = await boot({ cwd: process.cwd() })
52
+ console.log('BOOT_OK:' + !!app.getPlugin(${JSON.stringify(pluginName)}, true))
53
+ process.exit(0)
54
+ `, 'utf8')
55
+
56
+ const res = await runNode(root, 'run-app.mjs')
57
+ expect(res.timedOut).to.equal(false)
58
+ expect(res.code).to.equal(0)
59
+ expect(res.stdout).to.include('BOOT_OK:true')
60
+ expect(fs.existsSync(path.join(root, 'e2e-boot.txt'))).to.equal(true)
61
+ })
62
+ })
@@ -0,0 +1,68 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import { pathToFileURL } from 'node:url'
5
+ import fs from 'fs-extra'
6
+ import { expect } from 'chai'
7
+ import { runNode } from './_run.js'
8
+
9
+ describe('e2e hooks process', () => {
10
+ let root
11
+
12
+ beforeEach(() => {
13
+ root = fs.mkdtempSync(path.join('/tmp', 'bajo-e2e-hooks-'))
14
+ })
15
+
16
+ afterEach(() => {
17
+ if (root) fs.rmSync(root, { recursive: true, force: true })
18
+ })
19
+
20
+ it('executes real plugin hooks in separate process', async function () {
21
+ this.timeout(20000)
22
+
23
+ const entry = pathToFileURL(path.join('/mnt/d/Projects/Bajo/bajo', 'index.js')).href
24
+ const pluginName = 'e2e-plugin-hooks'
25
+ const pdir = path.join(root, 'node_modules', pluginName)
26
+
27
+ await fs.ensureDir(path.join(pdir, 'extend', 'bajo', 'hook'))
28
+ await fs.writeJson(path.join(root, 'package.json'), { name: 'e2e-app', type: 'module', bajo: { plugins: [pluginName] } })
29
+ await fs.writeJson(path.join(pdir, 'package.json'), { name: pluginName, version: '1.0.0', type: 'module', main: 'index.js' })
30
+ await fs.writeFile(path.join(pdir, 'index.js'), `
31
+ async function factory (pkgName) {
32
+ const me = this
33
+ return class E2EPluginHooks extends this.app.baseClass.Base {
34
+ constructor () {
35
+ super(pkgName, me.app)
36
+ this.config = {}
37
+ }
38
+ }
39
+ }
40
+ export default factory
41
+ `, 'utf8')
42
+
43
+ await fs.writeFile(path.join(pdir, 'extend', 'bajo', 'hook', 'bajo@after-boot.js'), `
44
+ export default {
45
+ level: 1,
46
+ handler: async function () {
47
+ await this.app.lib.fs.writeFile(this.app.dir + '/e2e-hook.txt', 'done', 'utf8')
48
+ }
49
+ }
50
+ `, 'utf8')
51
+
52
+ await fs.ensureDir(path.join(root, 'data', 'config'))
53
+ await fs.writeJson(path.join(root, 'data', 'config', 'bajo.json'), { log: { level: 'silent', save: false }, exitHandler: false, cache: { purgeIntvDur: '1h' } })
54
+
55
+ await fs.writeFile(path.join(root, 'run-app.mjs'), `
56
+ import boot from ${JSON.stringify(entry)}
57
+ await boot({ cwd: process.cwd() })
58
+ console.log('HOOK_OK')
59
+ process.exit(0)
60
+ `, 'utf8')
61
+
62
+ const res = await runNode(root, 'run-app.mjs')
63
+ expect(res.timedOut).to.equal(false)
64
+ expect(res.code).to.equal(0)
65
+ expect(res.stdout).to.include('HOOK_OK')
66
+ expect(fs.existsSync(path.join(root, 'e2e-hook.txt'))).to.equal(true)
67
+ })
68
+ })
@@ -0,0 +1,55 @@
1
+ import path from 'node:path'
2
+ import fs from 'fs-extra'
3
+
4
+ export const makeRoot = (prefix = 'bajo-int-') => fs.mkdtempSync(path.join('/tmp', prefix))
5
+
6
+ export const cleanupRoot = (root) => {
7
+ if (root) fs.rmSync(root, { recursive: true, force: true })
8
+ }
9
+
10
+ export const writeBaseApp = async (root, pluginName, extraBajoConfig = {}) => {
11
+ await fs.writeJson(path.join(root, 'package.json'), {
12
+ name: 'bajo-int-app',
13
+ type: 'module',
14
+ bajo: { plugins: [pluginName] }
15
+ })
16
+
17
+ await fs.ensureDir(path.join(root, 'data', 'config'))
18
+ await fs.writeJson(path.join(root, 'data', 'config', 'bajo.json'), {
19
+ env: 'dev',
20
+ lang: 'en-US',
21
+ exitHandler: false,
22
+ log: { level: 'silent', save: false },
23
+ cache: { purgeIntvDur: '1h' },
24
+ ...extraBajoConfig
25
+ })
26
+ }
27
+
28
+ export const writePlugin = async (root, pluginName, className = 'IntegrationPlugin', startBody = '') => {
29
+ const pluginDir = path.join(root, 'node_modules', pluginName)
30
+ await fs.ensureDir(pluginDir)
31
+ await fs.writeJson(path.join(pluginDir, 'package.json'), {
32
+ name: pluginName,
33
+ version: '1.0.0',
34
+ type: 'module',
35
+ main: 'index.js'
36
+ })
37
+
38
+ const code = `
39
+ async function factory (pkgName) {
40
+ const me = this
41
+ return class ${className} extends this.app.baseClass.Base {
42
+ constructor () {
43
+ super(pkgName, me.app)
44
+ this.config = { marker: 'ok', feature: true }
45
+ this.start = async () => {
46
+ ${startBody}
47
+ }
48
+ }
49
+ }
50
+ }
51
+ export default factory
52
+ `
53
+ await fs.writeFile(path.join(pluginDir, 'index.js'), code, 'utf8')
54
+ return pluginDir
55
+ }
@@ -0,0 +1,24 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import { expect } from 'chai'
4
+ import boot from '../../index.js'
5
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
6
+
7
+ describe('integration aspect 01 - boot instance', () => {
8
+ let root
9
+
10
+ beforeEach(async () => {
11
+ root = makeRoot('bajo-int-a01-')
12
+ await writeBaseApp(root, 'int-a01-plugin')
13
+ await writePlugin(root, 'int-a01-plugin', 'IntA01', "await this.app.lib.fs.writeFile(this.app.dir + '/a01.txt', 'ok', 'utf8')")
14
+ })
15
+
16
+ afterEach(() => cleanupRoot(root))
17
+
18
+ it('boots app instance successfully', async function () {
19
+ this.timeout(12000)
20
+ const app = await boot({ cwd: root })
21
+ expect(app).to.be.an('object')
22
+ expect(app.bajo).to.be.an('object')
23
+ })
24
+ })
@@ -0,0 +1,24 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import { expect } from 'chai'
4
+ import boot from '../../index.js'
5
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
6
+
7
+ describe('integration aspect 02 - plugin loading', () => {
8
+ let root
9
+
10
+ beforeEach(async () => {
11
+ root = makeRoot('bajo-int-a02-')
12
+ await writeBaseApp(root, 'int-a02-plugin')
13
+ await writePlugin(root, 'int-a02-plugin', 'IntA02', "this.state.started = true")
14
+ })
15
+
16
+ afterEach(() => cleanupRoot(root))
17
+
18
+ it('loads plugin and keeps namespace mapping', async function () {
19
+ this.timeout(12000)
20
+ const app = await boot({ cwd: root })
21
+ expect(app.intA02Plugin).to.be.an('object')
22
+ expect(app.getAllNs()).to.include('intA02Plugin')
23
+ })
24
+ })
@@ -0,0 +1,27 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import fs from 'fs-extra'
5
+ import { expect } from 'chai'
6
+ import boot from '../../index.js'
7
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
8
+
9
+ describe('integration aspect 03 - config merge', () => {
10
+ let root
11
+
12
+ beforeEach(async () => {
13
+ root = makeRoot('bajo-int-a03-')
14
+ await writeBaseApp(root, 'int-a03-plugin')
15
+ await writePlugin(root, 'int-a03-plugin', 'IntA03', '')
16
+ await fs.writeJson(path.join(root, 'data', 'config', 'intA03Plugin.json'), { marker: 'override', feature: false })
17
+ })
18
+
19
+ afterEach(() => cleanupRoot(root))
20
+
21
+ it('merges plugin runtime config from data/config', async function () {
22
+ this.timeout(12000)
23
+ const app = await boot({ cwd: root })
24
+ expect(app.intA03Plugin.getConfig('marker')).to.equal('override')
25
+ expect(app.intA03Plugin.getConfig('feature')).to.equal(false)
26
+ })
27
+ })
@@ -0,0 +1,29 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import fs from 'fs-extra'
5
+ import { expect } from 'chai'
6
+ import boot from '../../index.js'
7
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
8
+
9
+ describe('integration aspect 04 - intl loading', () => {
10
+ let root
11
+ let pluginDir
12
+
13
+ beforeEach(async () => {
14
+ root = makeRoot('bajo-int-a04-')
15
+ await writeBaseApp(root, 'int-a04-plugin')
16
+ pluginDir = await writePlugin(root, 'int-a04-plugin', 'IntA04', '')
17
+ await fs.ensureDir(path.join(pluginDir, 'extend', 'bajo', 'intl'))
18
+ await fs.writeJson(path.join(pluginDir, 'extend', 'bajo', 'intl', 'en-US.json'), { hello: 'Hello %s' })
19
+ })
20
+
21
+ afterEach(() => cleanupRoot(root))
22
+
23
+ it('loads plugin translation file and translates text', async function () {
24
+ this.timeout(12000)
25
+ const app = await boot({ cwd: root })
26
+ expect(app.intA04Plugin.intl['en-US'].hello).to.equal('Hello %s')
27
+ expect(app.t('intA04Plugin', 'hello', 'Joe')).to.equal('Hello Joe')
28
+ })
29
+ })
@@ -0,0 +1,26 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import { expect } from 'chai'
4
+ import boot from '../../index.js'
5
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
6
+
7
+ describe('integration aspect 05 - cache roundtrip', () => {
8
+ let root
9
+
10
+ beforeEach(async () => {
11
+ root = makeRoot('bajo-int-a05-')
12
+ await writeBaseApp(root, 'int-a05-plugin')
13
+ await writePlugin(root, 'int-a05-plugin', 'IntA05', '')
14
+ })
15
+
16
+ afterEach(() => cleanupRoot(root))
17
+
18
+ it('persists and reads cache values through app.cache', async function () {
19
+ this.timeout(12000)
20
+ const app = await boot({ cwd: root })
21
+ const payload = { ok: true, ids: [1, 2, 3] }
22
+ await app.cache.save('bajo.demo:path/item', payload, 60000)
23
+ const loaded = await app.cache.load('bajo.demo:path/item', 60000)
24
+ expect(loaded).to.deep.equal(payload)
25
+ })
26
+ })
@@ -0,0 +1,28 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import fs from 'fs-extra'
5
+ import { expect } from 'chai'
6
+ import boot from '../../index.js'
7
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
8
+
9
+ describe('integration aspect 06 - log save', () => {
10
+ let root
11
+
12
+ beforeEach(async () => {
13
+ root = makeRoot('bajo-int-a06-')
14
+ await writeBaseApp(root, 'int-a06-plugin', { log: { level: 'trace', save: true, pretty: false, useUtc: false, timeTaken: false, dateFormat: 'YYYY-MM-DD', rotation: { cycle: 'none', byPlugin: false } } })
15
+ await writePlugin(root, 'int-a06-plugin', 'IntA06', '')
16
+ })
17
+
18
+ afterEach(() => cleanupRoot(root))
19
+
20
+ it('writes log file when save is enabled', async function () {
21
+ this.timeout(12000)
22
+ const app = await boot({ cwd: root })
23
+ app.log.info('demo', 'saved%s', 'ok')
24
+ const logFile = path.join(root, 'data', 'log', 'bajo.log')
25
+ expect(fs.existsSync(logFile)).to.equal(true)
26
+ expect(fs.readFileSync(logFile, 'utf8')).to.include('saved')
27
+ })
28
+ })
@@ -0,0 +1,25 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import fs from 'fs-extra'
4
+ import { expect } from 'chai'
5
+ import boot from '../../index.js'
6
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
7
+
8
+ describe('integration aspect 07 - plugin data dir', () => {
9
+ let root
10
+
11
+ beforeEach(async () => {
12
+ root = makeRoot('bajo-int-a07-')
13
+ await writeBaseApp(root, 'int-a07-plugin')
14
+ await writePlugin(root, 'int-a07-plugin', 'IntA07', "const d = this.app.getPluginDataDir(this.ns); this.app.lib.fs.writeFileSync(d + '/ready.txt', '1', 'utf8')")
15
+ })
16
+
17
+ afterEach(() => cleanupRoot(root))
18
+
19
+ it('creates plugin data dir and allows writes during plugin start', async function () {
20
+ this.timeout(12000)
21
+ const app = await boot({ cwd: root })
22
+ const d = app.getPluginDataDir('intA07Plugin')
23
+ expect(fs.existsSync(d + '/ready.txt')).to.equal(true)
24
+ })
25
+ })
@@ -0,0 +1,25 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import { expect } from 'chai'
4
+ import boot from '../../index.js'
5
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
6
+
7
+ describe('integration aspect 08 - plugin lookup', () => {
8
+ let root
9
+
10
+ beforeEach(async () => {
11
+ root = makeRoot('bajo-int-a08-')
12
+ await writeBaseApp(root, 'int-a08-plugin')
13
+ await writePlugin(root, 'int-a08-plugin', 'IntA08', '')
14
+ })
15
+
16
+ afterEach(() => cleanupRoot(root))
17
+
18
+ it('resolves plugin by namespace and package name', async function () {
19
+ this.timeout(12000)
20
+ const app = await boot({ cwd: root })
21
+ const byNs = app.getPlugin('intA08Plugin')
22
+ const byPkg = app.getPlugin('int-a08-plugin')
23
+ expect(byNs).to.equal(byPkg)
24
+ })
25
+ })
@@ -0,0 +1,26 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import { expect } from 'chai'
4
+ import boot from '../../index.js'
5
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
6
+
7
+ describe('integration aspect 09 - config formats', () => {
8
+ let root
9
+
10
+ beforeEach(async () => {
11
+ root = makeRoot('bajo-int-a09-')
12
+ await writeBaseApp(root, 'int-a09-plugin')
13
+ await writePlugin(root, 'int-a09-plugin', 'IntA09', '')
14
+ })
15
+
16
+ afterEach(() => cleanupRoot(root))
17
+
18
+ it('provides config format registry from runtime handlers', async function () {
19
+ this.timeout(12000)
20
+ const app = await boot({ cwd: root })
21
+ const exts = app.getConfigFormats()
22
+ expect(exts).to.include('.json')
23
+ expect(exts).to.include('.yml')
24
+ expect(app.getConfigFormats(true)).to.include('json')
25
+ })
26
+ })
@@ -0,0 +1,38 @@
1
+ /* global describe, it, beforeEach, afterEach */
2
+
3
+ import path from 'node:path'
4
+ import fs from 'fs-extra'
5
+ import { expect } from 'chai'
6
+ import boot from '../../index.js'
7
+ import { makeRoot, cleanupRoot, writeBaseApp, writePlugin } from './_setup.js'
8
+
9
+ describe('integration aspect 10 - hook execution', () => {
10
+ let root
11
+ let pluginDir
12
+
13
+ beforeEach(async () => {
14
+ root = makeRoot('bajo-int-a10-')
15
+ await writeBaseApp(root, 'int-a10-plugin')
16
+ pluginDir = await writePlugin(root, 'int-a10-plugin', 'IntA10', "await this.app.lib.fs.writeFile(this.app.dir + '/plugin-start.txt', 'started', 'utf8')")
17
+
18
+ const hookDir = path.join(pluginDir, 'extend', 'bajo', 'hook')
19
+ await fs.ensureDir(hookDir)
20
+ await fs.writeFile(path.join(hookDir, 'bajo@after-boot.js'), `
21
+ export default {
22
+ level: 100,
23
+ handler: async function () {
24
+ await this.app.lib.fs.writeFile(this.app.dir + '/after-boot.txt', 'ok', 'utf8')
25
+ }
26
+ }
27
+ `, 'utf8')
28
+ })
29
+
30
+ afterEach(() => cleanupRoot(root))
31
+
32
+ it('collects and runs plugin hook during boot lifecycle', async function () {
33
+ this.timeout(12000)
34
+ await boot({ cwd: root })
35
+ expect(fs.existsSync(path.join(root, 'plugin-start.txt'))).to.equal(true)
36
+ expect(fs.existsSync(path.join(root, 'after-boot.txt'))).to.equal(true)
37
+ })
38
+ })