@bhooai/nexus-cli 0.1.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/PLAN.md +141 -0
- package/README.md +34 -0
- package/package.json +25 -0
- package/src/commands/cluster.ts +133 -0
- package/src/commands/dev.ts +133 -0
- package/src/commands/doctor.ts +199 -0
- package/src/commands/init.ts +960 -0
- package/src/commands/node.ts +101 -0
- package/src/commands/pysetup.ts +136 -0
- package/src/commands/sync.ts +116 -0
- package/src/commands/uninstall.ts +287 -0
- package/src/config-sync.ts +384 -0
- package/src/dotenv.ts +39 -0
- package/src/index.ts +94 -0
- package/src/supervisor.ts +384 -0
- package/src/util.ts +123 -0
- package/src/wizard.ts +149 -0
- package/templates/Dockerfile +60 -0
- package/templates/README.md +69 -0
- package/templates/apps/admin/index.html +12 -0
- package/templates/apps/admin/package.json +24 -0
- package/templates/apps/admin/postcss.config.js +6 -0
- package/templates/apps/admin/src/main.tsx +10 -0
- package/templates/apps/admin/src/vite-env.d.ts +18 -0
- package/templates/apps/admin/tailwind.config.js +9 -0
- package/templates/apps/admin/tsconfig.json +17 -0
- package/templates/apps/admin/vite.config.ts +64 -0
- package/templates/apps/ai-server/main.py +43 -0
- package/templates/apps/ai-server/providers/__init__.py +3 -0
- package/templates/apps/ai-server/providers/base.py +111 -0
- package/templates/apps/ai-server/requirements.txt +3 -0
- package/templates/apps/ai-server/routers/__init__.py +3 -0
- package/templates/apps/ai-server/routers/chat.py +47 -0
- package/templates/apps/ai-server/routers/embeddings.py +30 -0
- package/templates/apps/ai-server/routers/lint.py +167 -0
- package/templates/apps/ai-server/routers/models.py +23 -0
- package/templates/apps/ai-server/routers/preflight.py +169 -0
- package/templates/apps/ai-server/settings.py +48 -0
- package/templates/apps/backend/package.json +33 -0
- package/templates/apps/backend/src/main.ts +375 -0
- package/templates/apps/backend/src/modules/admin/adminRoutes.ts +732 -0
- package/templates/apps/backend/src/modules/admin/clusterRoutes.ts +391 -0
- package/templates/apps/backend/src/modules/admin/databaseRoutes.ts +161 -0
- package/templates/apps/backend/src/modules/admin/lintProxy.ts +89 -0
- package/templates/apps/backend/src/modules/admin/preflightProxy.ts +242 -0
- package/templates/apps/backend/src/modules/admin/roleCatalog.ts +78 -0
- package/templates/apps/backend/src/modules/admin/schemaRoutes.ts +449 -0
- package/templates/apps/backend/src/modules/ai/aiProxy.ts +265 -0
- package/templates/apps/backend/src/modules/auth/authRoutes.ts +220 -0
- package/templates/apps/backend/src/modules/payments/paymentRoutes.ts +100 -0
- package/templates/apps/backend/src/modules/payments/paymentStore.ts +172 -0
- package/templates/apps/backend/src/modules/requests/requestLog.ts +175 -0
- package/templates/apps/backend/src/modules/users/userGraph.ts +83 -0
- package/templates/apps/backend/src/modules/users/userModel.ts +88 -0
- package/templates/apps/backend/src/plugins/CronScheduler.ts +69 -0
- package/templates/apps/backend/src/plugins/loadPlugins.ts +107 -0
- package/templates/apps/backend/tsconfig.json +14 -0
- package/templates/apps/frontend/index.html +12 -0
- package/templates/apps/frontend/package.json +19 -0
- package/templates/apps/frontend/src/main.tsx +64 -0
- package/templates/apps/frontend/vite.config.ts +63 -0
- package/templates/bin/nexus.js +35 -0
- package/templates/bin/serve-all.mjs +45 -0
- package/templates/dockerignore +15 -0
- package/templates/gitignore +12 -0
- package/templates/nexus.config.ts +69 -0
- package/templates/package.json +47 -0
- package/templates/tsconfig.json +17 -0
- package/templates/uploads/.gitkeep +0 -0
- package/tests/cli.test.ts +45 -0
- package/tests/config-sync.test.ts +201 -0
- package/tests/dotenv.test.ts +51 -0
- package/tsconfig.json +9 -0
- package/vitest.config.ts +9 -0
- package/vitest.config.ts.timestamp-1786095205351-444061f6ff5c58.mjs +13 -0
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
{
|
|
2
|
+
"extends": "../../tsconfig.json",
|
|
3
|
+
"compilerOptions": {
|
|
4
|
+
"rootDir": "src",
|
|
5
|
+
"outDir": "dist",
|
|
6
|
+
"module": "ESNext",
|
|
7
|
+
"moduleResolution": "Bundler",
|
|
8
|
+
"target": "ES2022",
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"types": ["node"]
|
|
12
|
+
},
|
|
13
|
+
"include": ["src"]
|
|
14
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<!doctype html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
6
|
+
<title>My Nexus App</title>
|
|
7
|
+
</head>
|
|
8
|
+
<body>
|
|
9
|
+
<div id="root"></div>
|
|
10
|
+
<script type="module" src="/src/main.tsx"></script>
|
|
11
|
+
</body>
|
|
12
|
+
</html>
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-nexus-frontend",
|
|
3
|
+
"private": true,
|
|
4
|
+
"type": "module",
|
|
5
|
+
"scripts": {
|
|
6
|
+
"dev": "vite",
|
|
7
|
+
"build": "vite build",
|
|
8
|
+
"preview": "vite preview"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"react": "^18.3.0",
|
|
12
|
+
"react-dom": "^18.3.0"
|
|
13
|
+
},
|
|
14
|
+
"devDependencies": {
|
|
15
|
+
"@vitejs/plugin-react": "^4.3.0",
|
|
16
|
+
"typescript": "^5.6.0",
|
|
17
|
+
"vite": "^5.4.0"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { createRoot } from 'react-dom/client';
|
|
3
|
+
|
|
4
|
+
// Generated placeholder frontend. The framework's own apps/frontend has the full
|
|
5
|
+
// auth / GraphQL / streaming / checkout UI — copy what you need from there.
|
|
6
|
+
async function pingHealth() {
|
|
7
|
+
try {
|
|
8
|
+
const res = await fetch('/health');
|
|
9
|
+
const body = await res.json();
|
|
10
|
+
document.getElementById('status')!.textContent = `${body.status} @ ${new Date(body.time).toLocaleTimeString()}`;
|
|
11
|
+
} catch {
|
|
12
|
+
document.getElementById('status')!.textContent = 'backend not reachable';
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
function App() {
|
|
17
|
+
const [file, setFile] = React.useState<File | null>(null);
|
|
18
|
+
const [uploadStatus, setUploadStatus] = React.useState('');
|
|
19
|
+
|
|
20
|
+
React.useEffect(() => {
|
|
21
|
+
pingHealth();
|
|
22
|
+
const id = setInterval(pingHealth, 5000);
|
|
23
|
+
return () => clearInterval(id);
|
|
24
|
+
}, []);
|
|
25
|
+
|
|
26
|
+
async function uploadFile() {
|
|
27
|
+
if (!file) return;
|
|
28
|
+
setUploadStatus('uploading...');
|
|
29
|
+
try {
|
|
30
|
+
const csrfResponse = await fetch('/csrf-token', { credentials: 'include' });
|
|
31
|
+
const { token } = await csrfResponse.json() as { token: string };
|
|
32
|
+
const form = new FormData();
|
|
33
|
+
form.append('file', file);
|
|
34
|
+
const response = await fetch('/uploads', {
|
|
35
|
+
method: 'POST',
|
|
36
|
+
body: form,
|
|
37
|
+
credentials: 'include',
|
|
38
|
+
headers: { 'x-csrf-token': token },
|
|
39
|
+
});
|
|
40
|
+
const body = await response.json() as { files?: Array<{ url: string }>; error?: { message?: string } };
|
|
41
|
+
if (!response.ok) throw new Error(body.error?.message ?? 'upload failed');
|
|
42
|
+
setUploadStatus(`uploaded: ${body.files?.[0]?.url ?? 'ok'}`);
|
|
43
|
+
} catch (error) {
|
|
44
|
+
setUploadStatus(error instanceof Error ? error.message : 'upload failed');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
return (
|
|
49
|
+
<main style={{ fontFamily: 'system-ui, sans-serif', padding: '2rem' }}>
|
|
50
|
+
<h1>My Nexus App</h1>
|
|
51
|
+
<p>Backend health: <span id="status">checking…</span></p>
|
|
52
|
+
<h2>Upload a file</h2>
|
|
53
|
+
<input type="file" onChange={(event) => setFile(event.target.files?.[0] ?? null)} />
|
|
54
|
+
<button type="button" onClick={uploadFile} disabled={!file}>Upload</button>
|
|
55
|
+
<p>{uploadStatus}</p>
|
|
56
|
+
</main>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
createRoot(document.getElementById('root')!).render(
|
|
61
|
+
<React.StrictMode>
|
|
62
|
+
<App />
|
|
63
|
+
</React.StrictMode>,
|
|
64
|
+
);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { defineConfig } from 'vite';
|
|
2
|
+
import react from '@vitejs/plugin-react';
|
|
3
|
+
import { existsSync } from 'node:fs';
|
|
4
|
+
import { dirname, join, resolve } from 'node:path';
|
|
5
|
+
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
|
+
|
|
7
|
+
const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..', '..');
|
|
8
|
+
|
|
9
|
+
// Read the merged host/port values from the project's single nexus config file
|
|
10
|
+
// (ts/js/mjs/cjs, discovery order matches the backend loader) plus NEXUS_*
|
|
11
|
+
// env overrides, so this Vite dev server and its proxy stay in one source of
|
|
12
|
+
// truth: nexus.config.js.
|
|
13
|
+
async function loadConfig() {
|
|
14
|
+
const exts = ['ts', 'js', 'mjs', 'cjs'];
|
|
15
|
+
let user: Record<string, any> = {};
|
|
16
|
+
for (const ext of exts) {
|
|
17
|
+
const file = join(PROJECT_ROOT, `nexus.config.${ext}`);
|
|
18
|
+
if (existsSync(file)) {
|
|
19
|
+
const mod = (await import(pathToFileURL(file).href + `?v=${Date.now()}`)) as Record<string, any>;
|
|
20
|
+
user = mod.default ?? mod.config ?? {};
|
|
21
|
+
break;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
const num = (v: string | undefined, fallback: number) => (v !== undefined && Number.isFinite(Number(v)) ? Number(v) : fallback);
|
|
25
|
+
const str = (v: string | undefined, fallback: string) => (v !== undefined && v !== '' ? v : fallback);
|
|
26
|
+
|
|
27
|
+
return {
|
|
28
|
+
server: {
|
|
29
|
+
host: str(process.env.NEXUS_SERVER_HOST, user.server?.host ?? '0.0.0.0'),
|
|
30
|
+
port: num(process.env.NEXUS_SERVER_PORT, user.server?.port ?? 4000),
|
|
31
|
+
},
|
|
32
|
+
frontend: {
|
|
33
|
+
host: str(process.env.NEXUS_FRONTEND_HOST, user.frontend?.host ?? 'localhost'),
|
|
34
|
+
port: num(process.env.NEXUS_FRONTEND_PORT, user.frontend?.port ?? 3000),
|
|
35
|
+
enabled: user.frontend?.enabled ?? true,
|
|
36
|
+
},
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export default defineConfig(async () => {
|
|
41
|
+
const cfg = await loadConfig();
|
|
42
|
+
const backendHost = cfg.server.host === '0.0.0.0' || cfg.server.host === '::' ? '127.0.0.1' : cfg.server.host;
|
|
43
|
+
const backend = `http://${backendHost}:${cfg.server.port}`;
|
|
44
|
+
const backendWs = `ws://${backendHost}:${cfg.server.port}`;
|
|
45
|
+
|
|
46
|
+
return {
|
|
47
|
+
plugins: [react()],
|
|
48
|
+
server: {
|
|
49
|
+
port: cfg.frontend.port,
|
|
50
|
+
host: cfg.frontend.host,
|
|
51
|
+
proxy: {
|
|
52
|
+
'/health': { target: backend, changeOrigin: true },
|
|
53
|
+
'/auth': { target: backend, changeOrigin: true },
|
|
54
|
+
'/csrf-token': { target: backend, changeOrigin: true },
|
|
55
|
+
'/graphql': { target: backend, changeOrigin: true, ws: true },
|
|
56
|
+
'/ai': { target: backend, changeOrigin: true },
|
|
57
|
+
'/payments': { target: backend, changeOrigin: true },
|
|
58
|
+
'/uploads': { target: backend, changeOrigin: true },
|
|
59
|
+
'/ws': { target: backendWs, ws: true, changeOrigin: true },
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
});
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* Generated Nexus CLI entry. Resolves the `@bhooai/nexus-cli` package and
|
|
4
|
+
* dispatches the command. The CLI package's main points at TypeScript source
|
|
5
|
+
* (run via tsx) so there is no build step; if a compiled dist is present it is
|
|
6
|
+
* used directly.
|
|
7
|
+
*/
|
|
8
|
+
import { existsSync } from 'node:fs';
|
|
9
|
+
import { extname } from 'node:path';
|
|
10
|
+
import { pathToFileURL } from 'node:url';
|
|
11
|
+
import { createRequire } from 'node:module';
|
|
12
|
+
|
|
13
|
+
const require = createRequire(import.meta.url);
|
|
14
|
+
|
|
15
|
+
async function main() {
|
|
16
|
+
// Resolve the CLI through the installed package exports. In a freshly init'd
|
|
17
|
+
// project the CLI is vendored under packages/nexus-cli, so this resolves to
|
|
18
|
+
// the vendored TypeScript source (no junction into the framework required).
|
|
19
|
+
const cliEntry = require.resolve('@bhooai/nexus-cli');
|
|
20
|
+
let mod;
|
|
21
|
+
if (existsSync(cliEntry) && extname(cliEntry) === '.js') {
|
|
22
|
+
mod = await import(pathToFileURL(cliEntry).href);
|
|
23
|
+
} else {
|
|
24
|
+
const { tsImport } = await import('tsx/esm/api');
|
|
25
|
+
mod = await tsImport(pathToFileURL(cliEntry).href, import.meta.url);
|
|
26
|
+
}
|
|
27
|
+
const cmd = process.argv[2] ?? 'help';
|
|
28
|
+
const rest = process.argv.slice(3);
|
|
29
|
+
await mod.run(cmd, rest);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
main().catch((err) => {
|
|
33
|
+
console.error(err?.stack ?? err);
|
|
34
|
+
process.exit(1);
|
|
35
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// Runs the whole Nexus stack in one process tree (used as the Docker CMD):
|
|
2
|
+
// backend -> nexus node serve --role=backend --port=<NEXUS_NODE_PORT|7575>
|
|
3
|
+
// (cluster node agent on :7575 + backend role service)
|
|
4
|
+
// frontend -> vite preview on :3000 (built SPA, proxies API)
|
|
5
|
+
// admin -> vite preview on :3001 (built admin SPA, proxies API)
|
|
6
|
+
// Forwards signals / reaps children so `docker stop` shuts down cleanly.
|
|
7
|
+
import { spawn } from 'node:child_process';
|
|
8
|
+
import { join, dirname } from 'node:path';
|
|
9
|
+
import { fileURLToPath } from 'node:url';
|
|
10
|
+
|
|
11
|
+
const root = join(dirname(fileURLToPath(import.meta.url)), '..');
|
|
12
|
+
const npm = process.platform === 'win32' ? 'npm.cmd' : 'npm';
|
|
13
|
+
const node = process.execPath;
|
|
14
|
+
const nodePort = process.env.NEXUS_NODE_PORT ?? '7575';
|
|
15
|
+
const children = [];
|
|
16
|
+
let shuttingDown = false;
|
|
17
|
+
|
|
18
|
+
function run(name, cwd, cmd, args) {
|
|
19
|
+
const p = spawn(cmd, args, { cwd, stdio: ['ignore', 'inherit', 'inherit'], env: process.env });
|
|
20
|
+
children.push(p);
|
|
21
|
+
p.on('exit', (code) => {
|
|
22
|
+
if (shuttingDown) return;
|
|
23
|
+
console.error(`[serve-all] ${name} exited with code ${code ?? 1}`);
|
|
24
|
+
shutdown(code ?? 1);
|
|
25
|
+
});
|
|
26
|
+
p.on('error', (err) => {
|
|
27
|
+
if (shuttingDown) return;
|
|
28
|
+
console.error(`[serve-all] ${name} failed to start: ${err.message}`);
|
|
29
|
+
shutdown(1);
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function shutdown(code) {
|
|
34
|
+
if (shuttingDown) return;
|
|
35
|
+
shuttingDown = true;
|
|
36
|
+
for (const c of children) c.kill('SIGTERM');
|
|
37
|
+
setTimeout(() => process.exit(code), 3000);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
process.on('SIGINT', () => shutdown(0));
|
|
41
|
+
process.on('SIGTERM', () => shutdown(0));
|
|
42
|
+
|
|
43
|
+
run('backend', root, node, ['bin/nexus.js', 'node', 'serve', '--role=backend', `--port=${nodePort}`]);
|
|
44
|
+
run('frontend', join(root, 'apps', 'frontend'), npm, ['run', 'preview', '--', '--host', '0.0.0.0', '--port', '3000']);
|
|
45
|
+
run('admin', join(root, 'apps', 'admin'), npm, ['run', 'preview', '--', '--host', '0.0.0.0', '--port', '3001']);
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import type { NexusConfig } from '@bhooai/nexus-core';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* THE single, human-edited config file for every server host/port.
|
|
5
|
+
*
|
|
6
|
+
* Precedence (low → high): code defaults < this file < nexus.runtime.json
|
|
7
|
+
* (admin, gitignored) < NEXUS_* env vars < CLI flags. Admin writes only
|
|
8
|
+
* `nexus.runtime.json`, so this file stays clean.
|
|
9
|
+
*
|
|
10
|
+
* The backend, MongoDB, Redis, the AI server, the frontend and admin Vite
|
|
11
|
+
* dev servers, and the node mesh all read their host/port from here. The
|
|
12
|
+
* Vite dev servers (apps/admin/vite.config.ts, apps/frontend/vite.config.ts)
|
|
13
|
+
* open the SAME file, so change host/port in ONE place.
|
|
14
|
+
*/
|
|
15
|
+
const config: Partial<NexusConfig> = {
|
|
16
|
+
env: 'development',
|
|
17
|
+
|
|
18
|
+
// The HTTP API server the browser/cluster proxy to.
|
|
19
|
+
server: { port: 4000, host: '0.0.0.0', https: false, trustProxy: false, bodyLimit: 12 * 1024 * 1024 },
|
|
20
|
+
// Vite dev server for the frontend — hosts guests visit.
|
|
21
|
+
frontend: { port: 3000, host: 'localhost', enabled: true },
|
|
22
|
+
// Python AI server (first /ai/* proxy hops to this).
|
|
23
|
+
ai: { serverUrl: 'http://localhost:8000', timeoutMs: 60_000, defaultProvider: 'auto', schemaModel: 'llama3:latest' },
|
|
24
|
+
// Vite dev server for the admin app.
|
|
25
|
+
admin: { port: 3001, host: 'localhost', enabled: true },
|
|
26
|
+
// Node mesh: set kind via —as=root|node at init (this file stays minimal).
|
|
27
|
+
cluster: { enabled: false, failOpenSingleNode: true, lbHost: '0.0.0.0', lbPort: 8080, nodeAgentHost: '0.0.0.0', nodeAgentPort: 7575, registryFile: 'cluster.runtime.json', token: '' },
|
|
28
|
+
|
|
29
|
+
// File uploads.
|
|
30
|
+
uploads: { dir: 'uploads', path: '/uploads', maxFileSize: 10 * 1024 * 1024, maxFiles: 20, allowedTypes: [] },
|
|
31
|
+
// MongoDB host/port/database.
|
|
32
|
+
db: { uri: 'mongodb://localhost:27017/acme-app', maxPoolSize: 10, autoIndex: true },
|
|
33
|
+
// Redis host/port.
|
|
34
|
+
redis: { url: 'redis://localhost:6379', keyPrefix: 'acme:' },
|
|
35
|
+
// GraphQL API server (first /graphql proxy hops to this).
|
|
36
|
+
graphql: { path: '/graphql', federation: 'in-process', subscriptions: true, introspection: true },
|
|
37
|
+
//Web Socket server for the browser/cluster proxy to.
|
|
38
|
+
ws: { path: '/ws', heartbeatMs: 30_000, requireCsrf: true },
|
|
39
|
+
|
|
40
|
+
auth: {
|
|
41
|
+
// JWT secret comes ONLY from NEXUS_AUTH_JWT_SECRET in .env — never here.
|
|
42
|
+
jwt: { accessTtl: 60 * 15, refreshTtl: 60 * 60 * 24 * 30 },
|
|
43
|
+
cookieName: 'nexus_sid',
|
|
44
|
+
refreshCookieName: 'nexus_rid',
|
|
45
|
+
requireEmailVerification: false,
|
|
46
|
+
},
|
|
47
|
+
|
|
48
|
+
payments: { webhookPath: '/payments/webhook/:provider', currency: 'INR',
|
|
49
|
+
razorpay: { enabled: false, sandbox: true },
|
|
50
|
+
paypal: { enabled: false, sandbox: true },
|
|
51
|
+
payu: { enabled: false, sandbox: true },
|
|
52
|
+
skrill: { enabled: false, sandbox: true },
|
|
53
|
+
payoneer: { enabled: false, sandbox: true },
|
|
54
|
+
},
|
|
55
|
+
email: { provider: 'log', from: 'no-reply@acme.local' },
|
|
56
|
+
// Certificates for HTTPS, WebRTC, etc.
|
|
57
|
+
certs: { dir: 'certs', keyType: 'rsa', rsaModulus: 2048, ecCurve: 'prime256v1', validityDays: 365 },
|
|
58
|
+
// Google Ads API (first /ads proxy hops to this).
|
|
59
|
+
ads: { enabled: false, developerToken: '', clientId: '', clientSecret: '', refreshToken: '', customerId: '' },
|
|
60
|
+
// WebRTC server (first /webrtc proxy hops to this).
|
|
61
|
+
webrtc: { rtcMinPort: 40000, rtcMaxPort: 40100, announceIp: '127.0.0.1' },
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
// Logging and plugins.
|
|
65
|
+
logging: { level: 'info', format: 'pretty', console: true, dir: 'logs', maxFileSize: 10 * 1024 * 1024, maxFiles: 7 },
|
|
66
|
+
plugins: { dir: 'plugins', entries: [] },
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export default config;
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "my-nexus-app",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"type": "module",
|
|
6
|
+
"workspaces": [
|
|
7
|
+
"../bhooai-nexus/packages/*",
|
|
8
|
+
"apps/backend",
|
|
9
|
+
"apps/admin",
|
|
10
|
+
"apps/frontend"
|
|
11
|
+
],
|
|
12
|
+
"scripts": {
|
|
13
|
+
"init": "nexus init .",
|
|
14
|
+
"dev": "nexus dev",
|
|
15
|
+
"build": "nexus build",
|
|
16
|
+
"test": "nexus test",
|
|
17
|
+
"doctor": "nexus doctor",
|
|
18
|
+
"pysetup": "nexus pysetup",
|
|
19
|
+
"uninstall": "nexus uninstall",
|
|
20
|
+
"start": "tsx apps/backend/src/main.ts"
|
|
21
|
+
},
|
|
22
|
+
"dependencies": {
|
|
23
|
+
"bhooai-nexus": "file:../bhooai-nexus",
|
|
24
|
+
"@bhooai/admin": "*",
|
|
25
|
+
"@bhooai/nexus-cli": "*",
|
|
26
|
+
"@bhooai/nexus-core": "*",
|
|
27
|
+
"@bhooai/nexus-auth": "*",
|
|
28
|
+
"@bhooai/nexus-data": "*",
|
|
29
|
+
"@bhooai/nexus-telemetry": "*",
|
|
30
|
+
"@bhooai/nexus-realtime": "*",
|
|
31
|
+
"@bhooai/nexus-graphql": "*",
|
|
32
|
+
"@bhooai/nexus-cache": "*",
|
|
33
|
+
"@bhooai/nexus-email": "*",
|
|
34
|
+
"@bhooai/nexus-payments": "*",
|
|
35
|
+
"@bhooai/nexus-crypto": "*",
|
|
36
|
+
"@bhooai/nexus-cluster": "*",
|
|
37
|
+
"@bhooai/nexus-ads": "*",
|
|
38
|
+
"@bhooai/nexus-plugins": "*",
|
|
39
|
+
"@bhooai/nexus-ai-client": "*"
|
|
40
|
+
},
|
|
41
|
+
"devDependencies": {
|
|
42
|
+
"tsx": "^4.19.0",
|
|
43
|
+
"typescript": "^5.6.0",
|
|
44
|
+
"@types/node": "^22.0.0",
|
|
45
|
+
"vitest": "^2.1.0"
|
|
46
|
+
}
|
|
47
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "ESNext",
|
|
5
|
+
"moduleResolution": "Bundler",
|
|
6
|
+
"lib": ["ES2022", "DOM"],
|
|
7
|
+
"jsx": "react-jsx",
|
|
8
|
+
"strict": true,
|
|
9
|
+
"esModuleInterop": true,
|
|
10
|
+
"skipLibCheck": true,
|
|
11
|
+
"resolveJsonModule": true,
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"noEmit": true,
|
|
14
|
+
"types": ["node"]
|
|
15
|
+
},
|
|
16
|
+
"include": ["apps/**/*", "nexus.config.ts"]
|
|
17
|
+
}
|
|
File without changes
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
|
|
2
|
+
import { run } from '../src/index.js';
|
|
3
|
+
import { mkdtempSync, rmSync, existsSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { tmpdir } from 'node:os';
|
|
5
|
+
import { join } from 'node:path';
|
|
6
|
+
|
|
7
|
+
describe('CLI dispatcher', () => {
|
|
8
|
+
it('prints help and exits 0', async () => {
|
|
9
|
+
const code = await run('help', []);
|
|
10
|
+
expect(code).toBe(0);
|
|
11
|
+
});
|
|
12
|
+
|
|
13
|
+
it('returns 1 for an unknown command', async () => {
|
|
14
|
+
const code = await run('nope', []);
|
|
15
|
+
expect(code).toBe(1);
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
describe('CLI init', () => {
|
|
20
|
+
let dir: string;
|
|
21
|
+
beforeEach(() => {
|
|
22
|
+
dir = mkdtempSync(join(tmpdir(), 'nexus-cli-'));
|
|
23
|
+
});
|
|
24
|
+
afterEach(() => {
|
|
25
|
+
rmSync(dir, { recursive: true, force: true });
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
it('scaffolds the four-terminal tree', async () => {
|
|
29
|
+
const code = await run('init', [dir, '--skip-install']);
|
|
30
|
+
expect(code).toBe(0);
|
|
31
|
+
expect(existsSync(join(dir, 'nexus.config.ts'))).toBe(true);
|
|
32
|
+
expect(existsSync(join(dir, 'apps/backend/src/main.ts'))).toBe(true);
|
|
33
|
+
expect(existsSync(join(dir, 'apps/frontend/src/main.tsx'))).toBe(true);
|
|
34
|
+
expect(existsSync(join(dir, 'apps/ai-server/main.py'))).toBe(true);
|
|
35
|
+
expect(existsSync(join(dir, 'apps/admin/src/main.tsx'))).toBe(true);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
it('is idempotent (skips existing files without --force)', async () => {
|
|
39
|
+
await run('init', [dir, '--skip-install']);
|
|
40
|
+
const code = await run('init', [dir, '--skip-install']);
|
|
41
|
+
expect(code).toBe(0);
|
|
42
|
+
const cfg = readFileSync(join(dir, 'nexus.config.ts'), 'utf8');
|
|
43
|
+
expect(cfg).toContain('NexusConfig');
|
|
44
|
+
});
|
|
45
|
+
});
|