@bhooai/nexus-cli 0.1.4 → 2.0.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 (112) hide show
  1. package/package.json +8 -12
  2. package/src/commands/add.ts +274 -0
  3. package/src/commands/build.ts +91 -0
  4. package/src/commands/db.ts +241 -0
  5. package/src/commands/dev.ts +149 -186
  6. package/src/commands/doctor.ts +102 -184
  7. package/src/commands/init.ts +204 -958
  8. package/src/commands/maintenance-commands.ts +40 -0
  9. package/src/commands/plugin.ts +233 -0
  10. package/src/commands/queue.ts +145 -0
  11. package/src/commands/test.ts +34 -0
  12. package/src/dispatcher.ts +98 -0
  13. package/src/generators.ts +102 -0
  14. package/src/index.ts +32 -90
  15. package/src/make/generators.ts +431 -0
  16. package/src/ports.ts +85 -0
  17. package/src/prompts.ts +68 -0
  18. package/src/templating/render.ts +109 -0
  19. package/src/util.ts +74 -112
  20. package/templates/base/Dockerfile.ejs +68 -0
  21. package/templates/base/README.md.ejs +62 -0
  22. package/templates/base/apps/admin/index.html.ejs +12 -0
  23. package/templates/base/apps/admin/nginx.conf.ejs +16 -0
  24. package/templates/base/apps/admin/package.json.ejs +15 -0
  25. package/templates/base/apps/admin/src/main.tsx.ejs +7 -0
  26. package/templates/base/apps/admin/vite.config.ts.ejs +22 -0
  27. package/templates/base/apps/ai-server/main.py.ejs +30 -0
  28. package/templates/base/apps/ai-server/requirements.txt +4 -0
  29. package/templates/base/apps/backend/src/main.ts +14 -0
  30. package/templates/base/apps/backend/src/routes/index.ts.ejs +11 -0
  31. package/templates/base/apps/frontend/index.html.ejs +12 -0
  32. package/templates/base/apps/frontend/nginx.conf.ejs +33 -0
  33. package/templates/base/apps/frontend/package.json.ejs +11 -0
  34. package/templates/base/apps/frontend/src/main.tsx.ejs +16 -0
  35. package/templates/base/apps/frontend/vite.config.ts.ejs +22 -0
  36. package/templates/base/docker-compose.yml.ejs +93 -0
  37. package/templates/base/dockerignore +21 -0
  38. package/templates/base/gitignore +43 -0
  39. package/templates/base/nexus.config.ts.ejs +97 -0
  40. package/templates/base/package.json.ejs +40 -0
  41. package/templates/{tsconfig.json → base/tsconfig.json} +2 -3
  42. package/tsconfig.json +5 -4
  43. package/PLAN.md +0 -141
  44. package/README.md +0 -34
  45. package/src/commands/cluster.ts +0 -133
  46. package/src/commands/node.ts +0 -106
  47. package/src/commands/pysetup.ts +0 -142
  48. package/src/commands/sync.ts +0 -116
  49. package/src/commands/uninstall.ts +0 -287
  50. package/src/config-sync.ts +0 -384
  51. package/src/dotenv.ts +0 -39
  52. package/src/supervisor.ts +0 -394
  53. package/src/wizard.ts +0 -149
  54. package/templates/Dockerfile +0 -60
  55. package/templates/README.md +0 -69
  56. package/templates/apps/admin/index.html +0 -12
  57. package/templates/apps/admin/package.json +0 -22
  58. package/templates/apps/admin/postcss.config.js +0 -3
  59. package/templates/apps/admin/src/main.tsx +0 -10
  60. package/templates/apps/admin/src/vite-env.d.ts +0 -18
  61. package/templates/apps/admin/tailwind.config.js +0 -9
  62. package/templates/apps/admin/tsconfig.json +0 -17
  63. package/templates/apps/admin/vite.config.ts +0 -64
  64. package/templates/apps/ai-server/main.py +0 -43
  65. package/templates/apps/ai-server/providers/__init__.py +0 -3
  66. package/templates/apps/ai-server/providers/base.py +0 -111
  67. package/templates/apps/ai-server/requirements.txt +0 -3
  68. package/templates/apps/ai-server/routers/__init__.py +0 -3
  69. package/templates/apps/ai-server/routers/chat.py +0 -47
  70. package/templates/apps/ai-server/routers/embeddings.py +0 -30
  71. package/templates/apps/ai-server/routers/lint.py +0 -167
  72. package/templates/apps/ai-server/routers/models.py +0 -23
  73. package/templates/apps/ai-server/routers/preflight.py +0 -169
  74. package/templates/apps/ai-server/settings.py +0 -48
  75. package/templates/apps/backend/package.json +0 -34
  76. package/templates/apps/backend/src/main.ts +0 -375
  77. package/templates/apps/backend/src/modules/admin/adminRoutes.ts +0 -732
  78. package/templates/apps/backend/src/modules/admin/clusterRoutes.ts +0 -391
  79. package/templates/apps/backend/src/modules/admin/databaseRoutes.ts +0 -161
  80. package/templates/apps/backend/src/modules/admin/lintProxy.ts +0 -89
  81. package/templates/apps/backend/src/modules/admin/preflightProxy.ts +0 -242
  82. package/templates/apps/backend/src/modules/admin/roleCatalog.ts +0 -78
  83. package/templates/apps/backend/src/modules/admin/schemaRoutes.ts +0 -449
  84. package/templates/apps/backend/src/modules/ai/aiProxy.ts +0 -265
  85. package/templates/apps/backend/src/modules/auth/authRoutes.ts +0 -220
  86. package/templates/apps/backend/src/modules/payments/paymentRoutes.ts +0 -100
  87. package/templates/apps/backend/src/modules/payments/paymentStore.ts +0 -172
  88. package/templates/apps/backend/src/modules/requests/requestLog.ts +0 -175
  89. package/templates/apps/backend/src/modules/users/userGraph.ts +0 -83
  90. package/templates/apps/backend/src/modules/users/userModel.ts +0 -88
  91. package/templates/apps/backend/src/plugins/CronScheduler.ts +0 -69
  92. package/templates/apps/backend/src/plugins/loadPlugins.ts +0 -107
  93. package/templates/apps/backend/tsconfig.json +0 -14
  94. package/templates/apps/frontend/index.html +0 -12
  95. package/templates/apps/frontend/package.json +0 -20
  96. package/templates/apps/frontend/postcss.config.js +0 -3
  97. package/templates/apps/frontend/src/index.css +0 -44
  98. package/templates/apps/frontend/src/main.tsx +0 -65
  99. package/templates/apps/frontend/tailwind.config.js +0 -9
  100. package/templates/apps/frontend/vite.config.ts +0 -72
  101. package/templates/bin/nexus.js +0 -35
  102. package/templates/bin/serve-all.mjs +0 -45
  103. package/templates/dockerignore +0 -15
  104. package/templates/gitignore +0 -12
  105. package/templates/nexus.config.ts +0 -69
  106. package/templates/package.json +0 -47
  107. package/tests/cli.test.ts +0 -52
  108. package/tests/config-sync.test.ts +0 -201
  109. package/tests/dotenv.test.ts +0 -51
  110. package/vitest.config.ts +0 -9
  111. package/vitest.config.ts.timestamp-1786095205351-444061f6ff5c58.mjs +0 -13
  112. /package/templates/{uploads → base/apps/backend/storage}/.gitkeep +0 -0
@@ -1,201 +0,0 @@
1
- import { describe, it, expect, beforeEach, afterEach } from 'vitest';
2
- import { mkdtemp, writeFile, readFile, rm, mkdir } from 'node:fs/promises';
3
- import { existsSync } from 'node:fs';
4
- import { tmpdir } from 'node:os';
5
- import { join } from 'node:path';
6
- import { syncConfig, deriveValues, fingerprintOf, configChangedSinceSync, syncFingerprintPath } from '../src/config-sync.js';
7
- import { mergeConfig } from '../../nexus-core/src/index.js';
8
-
9
- const DOCKERFILE_FIXTURE = `# Build
10
- # docker run --rm -p 4000:4000 -p 3000:3000 -p 3001:3001 --env-file .env node-1
11
- # Access:
12
- # frontend http://localhost:3000 (SPA)
13
- # admin http://localhost:3001 (SPA)
14
- # backend http://localhost:4000 (API + /health)
15
- # node http://localhost:7575 (cluster node agent)
16
- EXPOSE 3000 3001 4000 7575
17
- HEALTHCHECK --interval=30s --timeout=5s --start-period=10s --retries=3 \\\\
18
- CMD wget -qO- http://127.0.0.1:\\\${NEXUS_SERVER_PORT:-4000}/health >/dev/null || exit 1
19
- `;
20
-
21
- const SERVE_ALL_FIXTURE = `import { spawn } from 'node:child_process';
22
- const nodePort = process.env.NEXUS_NODE_PORT ?? '7575';
23
- run('frontend', join(root, 'apps', 'frontend'), npm, ['run', 'preview', '--', '--host', '0.0.0.0', '--port', '3000']);
24
- run('admin', join(root, 'apps', 'admin'), npm, ['run', 'preview', '--', '--host', '0.0.0.0', '--port', '3001']);
25
- `;
26
-
27
- const ADMIN_PKG_FIXTURE = `{
28
- "scripts": { "dev": "vite --port 3001", "preview": "vite preview" }
29
- }
30
- `;
31
-
32
- const PS1_FIXTURE = [
33
- "$dbUri = 'mongodb://host.docker.internal:27017/nexus-docker'",
34
- "$redisUrl = 'redis://host.docker.internal:6379'",
35
- "$aiUrl = 'http://host.docker.internal:8000'",
36
- "$nodePort = '7575'",
37
- "$serverPort = '4000'",
38
- "$frontendPort = '3000'",
39
- "$adminPort = '3001'",
40
- " '-p', \"$serverPort`:$serverPort\",",
41
- " '-p', \"$frontendPort`:$frontendPort\",",
42
- " '-p', \"$adminPort`:$adminPort\",",
43
- " '-p', \"$nodePort`:$nodePort\",",
44
- " '-e', \"NEXUS_NODE_PORT=$nodePort\",",
45
- " '-e', \"NEXUS_SERVER_PORT=$serverPort\"",
46
- ].join('\n');
47
-
48
- const SH_FIXTURE = [
49
- 'DB_URI="mongodb://host.docker.internal:27017/nexus-docker"',
50
- 'REDIS_URL="redis://host.docker.internal:6379"',
51
- 'AI_URL="http://host.docker.internal:8000"',
52
- 'NODE_PORT="7575"',
53
- ' -p 4000:4000',
54
- ' -p 3000:3000',
55
- ' -p 3001:3001',
56
- ' -p 7575:7575',
57
- ' -e "NEXUS_NODE_PORT=$NODE_PORT"',
58
- ].join('\n');
59
-
60
- const BAT_FIXTURE = [
61
- '-p 4000:4000',
62
- '-p 3000:3000',
63
- '-p 3001:3001',
64
- '-p 7575:7575',
65
- '-e NEXUS_DB_URI=mongodb://host.docker.internal:27017/nexus-docker',
66
- '-e NEXUS_REDIS_URL=redis://host.docker.internal:6379',
67
- '-e NEXUS_AI_SERVER_URL=http://host.docker.internal:8000',
68
- '-e NEXUS_NODE_PORT=7575',
69
- 'echo frontend http://localhost:3000 admin http://localhost:3001 backend http://localhost:4000/health',
70
- ].join('\n');
71
-
72
- describe('nexus config-sync', () => {
73
- let dir: string;
74
-
75
- beforeEach(async () => {
76
- dir = await mkdtemp(join(tmpdir(), 'nexus-sync-'));
77
- await mkdir(join(dir, 'bin'), { recursive: true });
78
- await mkdir(join(dir, 'apps', 'admin'), { recursive: true });
79
- await writeFile(join(dir, 'package.json'), JSON.stringify({ name: 'sync-test' }), 'utf8');
80
- await writeFile(join(dir, 'Dockerfile'), DOCKERFILE_FIXTURE, 'utf8');
81
- await writeFile(join(dir, 'bin', 'serve-all.mjs'), SERVE_ALL_FIXTURE, 'utf8');
82
- await writeFile(join(dir, 'apps', 'admin', 'package.json'), ADMIN_PKG_FIXTURE, 'utf8');
83
- await writeFile(join(dir, 'docker.ps1'), PS1_FIXTURE, 'utf8');
84
- await writeFile(join(dir, 'docker.sh'), SH_FIXTURE, 'utf8');
85
- await writeFile(join(dir, 'docker.bat'), BAT_FIXTURE, 'utf8');
86
- });
87
-
88
- afterEach(async () => {
89
- await rm(dir, { recursive: true, force: true });
90
- });
91
-
92
- it('writes the configured ports + URLs into every derived file', async () => {
93
- const cfg = mergeConfig({
94
- server: { port: 4100, host: '0.0.0.0' },
95
- frontend: { port: 3100 },
96
- admin: { port: 3111 },
97
- cluster: { nodeAgentPort: 7676, lbPort: 9090 },
98
- ai: { serverUrl: 'http://localhost:8111' },
99
- db: { uri: 'mongodb://localhost:27017/sync-test' },
100
- redis: { url: 'redis://localhost:6379' },
101
- });
102
-
103
- const report = await syncConfig(dir, cfg, { db: false });
104
- expect(existsSync(join(dir, '.nexus-sync.json'))).toBe(true);
105
-
106
- expect(report.files.every((f) => f.status === 'updated' || f.status === 'in-sync')).toBe(true);
107
-
108
- const df = await readFile(join(dir, 'Dockerfile'), 'utf8');
109
- expect(df).toContain('EXPOSE 3100 3111 4100 7676');
110
- expect(df).toContain('-p 4100:4100 -p 3100:3100 -p 3111:3111');
111
- expect(df).toContain('http://localhost:3100');
112
- expect(df).toContain('http://localhost:3111');
113
- expect(df).toContain('http://localhost:4100');
114
- expect(df).toContain('http://localhost:7676');
115
- expect(df).toContain('NEXUS_SERVER_PORT:-4100');
116
-
117
- const sa = await readFile(join(dir, 'bin', 'serve-all.mjs'), 'utf8');
118
- expect(sa).toContain("NEXUS_NODE_PORT ?? '7676'");
119
- expect(sa).toContain("'--port', '3100'");
120
- expect(sa).toContain("'--port', '3111'");
121
-
122
- const pkg = await readFile(join(dir, 'apps', 'admin', 'package.json'), 'utf8');
123
- expect(pkg).toContain('"dev": "vite --port 3111"');
124
-
125
- const ps1 = await readFile(join(dir, 'docker.ps1'), 'utf8');
126
- expect(ps1).toContain("$nodePort = '7676'");
127
- expect(ps1).toContain("$serverPort = '4100'");
128
- expect(ps1).toContain("$frontendPort = '3100'");
129
- expect(ps1).toContain("$adminPort = '3111'");
130
- expect(ps1).toContain('NEXUS_NODE_PORT=$nodePort');
131
- expect(ps1).toContain('NEXUS_SERVER_PORT=$serverPort');
132
-
133
- const sh = await readFile(join(dir, 'docker.sh'), 'utf8');
134
- expect(sh).toContain('-p 4100:4100');
135
- expect(sh).toContain('-p 3100:3100');
136
- expect(sh).toContain('-p 3111:3111');
137
- expect(sh).toContain('-p 7676:7676');
138
-
139
- const bat = await readFile(join(dir, 'docker.bat'), 'utf8');
140
- expect(bat).toContain('-p 4100:4100');
141
- expect(bat).toContain('-p 3100:3100');
142
- expect(bat).toContain('NEXUS_NODE_PORT=7676');
143
- });
144
-
145
- it('second run is idempotent (in-sync everywhere)', async () => {
146
- const cfg = mergeConfig({});
147
- await syncConfig(dir, cfg, { db: false });
148
- const second = await syncConfig(dir, cfg, { db: false });
149
- expect(second.files.map((f) => f.status)).toEqual(['in-sync', 'in-sync', 'in-sync', 'in-sync', 'in-sync', 'in-sync']);
150
- });
151
-
152
- it('ported to a new value and back — round-trips (no stale digits left)', async () => {
153
- const cfgA = mergeConfig({ frontend: { port: 3120 }, admin: { port: 3121 } });
154
- await syncConfig(dir, cfgA, { db: false });
155
- const cfgB = mergeConfig({});
156
- await syncConfig(dir, cfgB, { db: false });
157
-
158
- const df = await readFile(join(dir, 'Dockerfile'), 'utf8');
159
- expect(df).toContain('EXPOSE 3000 3001 4000 7575');
160
- expect(df).not.toContain('3120');
161
-
162
- const sa = await readFile(join(dir, 'bin', 'serve-all.mjs'), 'utf8');
163
- expect(sa).toContain("'--port', '3000'");
164
- expect(sa).not.toContain('312');
165
-
166
- const sh = await readFile(join(dir, 'docker.sh'), 'utf8');
167
- expect(sh).not.toContain('312');
168
-
169
- const bat = await readFile(join(dir, 'docker.bat'), 'utf8');
170
- expect(bat).not.toContain('312');
171
-
172
- const pkg = await readFile(join(dir, 'apps', 'admin', 'package.json'), 'utf8');
173
- expect(pkg).toContain('"dev": "vite --port 3001"');
174
- });
175
-
176
- it('fingerprint flips when the config changes', async () => {
177
- const base = mergeConfig({});
178
- expect(configChangedSinceSync(dir, base)).toBe(true);
179
- const report = await syncConfig(dir, base, { db: false });
180
- expect(report.fingerprint).toBeTruthy();
181
- expect(configChangedSinceSync(dir, base)).toBe(false);
182
- const changed = mergeConfig({ server: { port: 5555 } });
183
- expect(configChangedSinceSync(dir, changed)).toBe(true);
184
- expect(syncFingerprintPath(dir).length).toBeGreaterThan(0);
185
- });
186
-
187
- it('dry-run (write:false) reports updates but leaves files untouched', async () => {
188
- const cfg = mergeConfig({ server: { port: 9999 } });
189
- const before = await readFile(join(dir, 'Dockerfile'), 'utf8');
190
- const report = await syncConfig(dir, cfg, { write: false, db: false });
191
- expect(report.files.some((f) => f.status === 'updated')).toBe(true);
192
- const after = await readFile(join(dir, 'Dockerfile'), 'utf8');
193
- expect(after).toBe(before);
194
- });
195
-
196
- it('reports missing files without crashing and without hidden dotfile', async () => {
197
- await rm(join(dir, 'Dockerfile'));
198
- const report = await syncConfig(dir, mergeConfig({}), { db: false });
199
- expect(report.files.find((f) => f.file === 'Dockerfile')?.status).toBe('missing');
200
- });
201
- });
@@ -1,51 +0,0 @@
1
- import { describe, it, expect } from 'vitest';
2
- import { mkdtemp, writeFile, rm } from 'node:fs/promises';
3
- import { tmpdir } from 'node:os';
4
- import { join } from 'node:path';
5
- import { loadDotEnv } from '../src/dotenv.js';
6
-
7
- describe('loadDotEnv', () => {
8
- it('parses KEY=VALUE lines, comments, quotes, and export prefixes into env', async () => {
9
- const dir = await mkdtemp(join(tmpdir(), 'nexus-dotenv-'));
10
- try {
11
- await writeFile(
12
- join(dir, '.env'),
13
- [
14
- '# comment',
15
- 'NEXUS_PAYMENTS_RAZORPAY_ENABLED=true',
16
- 'NEXUS_PAYMENTS_RAZORPAY_KEY_ID=rzp_test_abc',
17
- "export NEXUS_AI_SERVER_URL='http://localhost:8000'",
18
- 'QUOTED="value with spaces"',
19
- '',
20
- ].join('\n'),
21
- 'utf8',
22
- );
23
- const env: Record<string, string> = {};
24
- loadDotEnv(dir, env as NodeJS.ProcessEnv);
25
- expect(env.NEXUS_PAYMENTS_RAZORPAY_ENABLED).toBe('true');
26
- expect(env.NEXUS_PAYMENTS_RAZORPAY_KEY_ID).toBe('rzp_test_abc');
27
- expect(env.NEXUS_AI_SERVER_URL).toBe('http://localhost:8000');
28
- expect(env.QUOTED).toBe('value with spaces');
29
- } finally {
30
- await rm(dir, { recursive: true, force: true });
31
- }
32
- });
33
-
34
- it('never overrides an existing environment variable', async () => {
35
- const dir = await mkdtemp(join(tmpdir(), 'nexus-dotenv-'));
36
- try {
37
- await writeFile(join(dir, '.env'), 'NEXUS_SERVER_PORT=9999\n', 'utf8');
38
- const env: Record<string, string> = { NEXUS_SERVER_PORT: '4000' };
39
- loadDotEnv(dir, env as NodeJS.ProcessEnv);
40
- expect(env.NEXUS_SERVER_PORT).toBe('4000');
41
- } finally {
42
- await rm(dir, { recursive: true, force: true });
43
- }
44
- });
45
-
46
- it('is a no-op when there is no .env file', () => {
47
- const env: Record<string, string> = {};
48
- loadDotEnv(join(tmpdir(), 'definitely-missing-' + Date.now()), env as NodeJS.ProcessEnv);
49
- expect(Object.keys(env)).toHaveLength(0);
50
- });
51
- });
package/vitest.config.ts DELETED
@@ -1,9 +0,0 @@
1
- import { defineProject } from 'vitest/config';
2
-
3
- export default defineProject({
4
- test: {
5
- environment: 'node',
6
- include: ['tests/**/*.test.ts'],
7
- globals: false,
8
- },
9
- });
@@ -1,13 +0,0 @@
1
- // packages/nexus-cli/vitest.config.ts
2
- import { defineProject } from "file:///C:/server/BhooAI/BhooAI-Nexus/BhooAI-Nexus/bhooai-nexus/node_modules/vitest/dist/config.js";
3
- var vitest_config_default = defineProject({
4
- test: {
5
- environment: "node",
6
- include: ["tests/**/*.test.ts"],
7
- globals: false
8
- }
9
- });
10
- export {
11
- vitest_config_default as default
12
- };
13
- //# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsicGFja2FnZXMvbmV4dXMtY2xpL3ZpdGVzdC5jb25maWcudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJDOlxcXFxzZXJ2ZXJcXFxcQmhvb0FJXFxcXEJob29BSS1OZXh1c1xcXFxCaG9vQUktTmV4dXNcXFxcYmhvb2FpLW5leHVzXFxcXHBhY2thZ2VzXFxcXG5leHVzLWNsaVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiQzpcXFxcc2VydmVyXFxcXEJob29BSVxcXFxCaG9vQUktTmV4dXNcXFxcQmhvb0FJLU5leHVzXFxcXGJob29haS1uZXh1c1xcXFxwYWNrYWdlc1xcXFxuZXh1cy1jbGlcXFxcdml0ZXN0LmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vQzovc2VydmVyL0Job29BSS9CaG9vQUktTmV4dXMvQmhvb0FJLU5leHVzL2Job29haS1uZXh1cy9wYWNrYWdlcy9uZXh1cy1jbGkvdml0ZXN0LmNvbmZpZy50c1wiO2ltcG9ydCB7IGRlZmluZVByb2plY3QgfSBmcm9tICd2aXRlc3QvY29uZmlnJztcblxuZXhwb3J0IGRlZmF1bHQgZGVmaW5lUHJvamVjdCh7XG4gIHRlc3Q6IHtcbiAgICBlbnZpcm9ubWVudDogJ25vZGUnLFxuICAgIGluY2x1ZGU6IFsndGVzdHMvKiovKi50ZXN0LnRzJ10sXG4gICAgZ2xvYmFsczogZmFsc2UsXG4gIH0sXG59KTsiXSwKICAibWFwcGluZ3MiOiAiO0FBQW9hLFNBQVMscUJBQXFCO0FBRWxjLElBQU8sd0JBQVEsY0FBYztBQUFBLEVBQzNCLE1BQU07QUFBQSxJQUNKLGFBQWE7QUFBQSxJQUNiLFNBQVMsQ0FBQyxvQkFBb0I7QUFBQSxJQUM5QixTQUFTO0FBQUEsRUFDWDtBQUNGLENBQUM7IiwKICAibmFtZXMiOiBbXQp9Cg==