wolverine-ai 1.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.
- package/PLATFORM.md +442 -0
- package/README.md +475 -0
- package/SERVER_BEST_PRACTICES.md +62 -0
- package/TELEMETRY.md +108 -0
- package/bin/wolverine.js +95 -0
- package/examples/01-basic-typo.js +31 -0
- package/examples/02-multi-file/routes/users.js +15 -0
- package/examples/02-multi-file/server.js +25 -0
- package/examples/03-syntax-error.js +23 -0
- package/examples/04-secret-leak.js +14 -0
- package/examples/05-expired-key.js +27 -0
- package/examples/06-json-config/config.json +13 -0
- package/examples/06-json-config/server.js +28 -0
- package/examples/07-rate-limit-loop.js +11 -0
- package/examples/08-sandbox-escape.js +20 -0
- package/examples/buggy-server.js +39 -0
- package/examples/demos/01-basic-typo/index.js +20 -0
- package/examples/demos/01-basic-typo/routes/api.js +13 -0
- package/examples/demos/01-basic-typo/routes/health.js +4 -0
- package/examples/demos/02-multi-file/index.js +24 -0
- package/examples/demos/02-multi-file/routes/api.js +13 -0
- package/examples/demos/02-multi-file/routes/health.js +4 -0
- package/examples/demos/03-syntax-error/index.js +18 -0
- package/examples/demos/04-secret-leak/index.js +16 -0
- package/examples/demos/05-expired-key/index.js +21 -0
- package/examples/demos/06-json-config/config.json +9 -0
- package/examples/demos/06-json-config/index.js +20 -0
- package/examples/demos/07-null-crash/index.js +16 -0
- package/examples/run-demo.js +110 -0
- package/package.json +67 -0
- package/server/config/settings.json +62 -0
- package/server/index.js +33 -0
- package/server/routes/api.js +12 -0
- package/server/routes/health.js +16 -0
- package/server/routes/time.js +12 -0
- package/src/agent/agent-engine.js +727 -0
- package/src/agent/goal-loop.js +140 -0
- package/src/agent/research-agent.js +120 -0
- package/src/agent/sub-agents.js +176 -0
- package/src/backup/backup-manager.js +321 -0
- package/src/brain/brain.js +315 -0
- package/src/brain/embedder.js +131 -0
- package/src/brain/function-map.js +263 -0
- package/src/brain/vector-store.js +267 -0
- package/src/core/ai-client.js +387 -0
- package/src/core/cluster-manager.js +144 -0
- package/src/core/config.js +89 -0
- package/src/core/error-parser.js +87 -0
- package/src/core/health-monitor.js +129 -0
- package/src/core/models.js +132 -0
- package/src/core/patcher.js +55 -0
- package/src/core/runner.js +464 -0
- package/src/core/system-info.js +141 -0
- package/src/core/verifier.js +146 -0
- package/src/core/wolverine.js +290 -0
- package/src/dashboard/server.js +1332 -0
- package/src/index.js +94 -0
- package/src/logger/event-logger.js +237 -0
- package/src/logger/pricing.js +96 -0
- package/src/logger/repair-history.js +109 -0
- package/src/logger/token-tracker.js +277 -0
- package/src/mcp/mcp-client.js +224 -0
- package/src/mcp/mcp-registry.js +228 -0
- package/src/mcp/mcp-security.js +152 -0
- package/src/monitor/perf-monitor.js +300 -0
- package/src/monitor/process-monitor.js +231 -0
- package/src/monitor/route-prober.js +191 -0
- package/src/notifications/notifier.js +227 -0
- package/src/platform/heartbeat.js +93 -0
- package/src/platform/queue.js +53 -0
- package/src/platform/register.js +64 -0
- package/src/platform/telemetry.js +76 -0
- package/src/security/admin-auth.js +150 -0
- package/src/security/injection-detector.js +174 -0
- package/src/security/rate-limiter.js +152 -0
- package/src/security/sandbox.js +128 -0
- package/src/security/secret-redactor.js +217 -0
- package/src/skills/skill-registry.js +129 -0
- package/src/skills/sql.js +375 -0
package/bin/wolverine.js
ADDED
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const dotenv = require("dotenv");
|
|
5
|
+
const chalk = require("chalk");
|
|
6
|
+
|
|
7
|
+
// Load secrets
|
|
8
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env.local") });
|
|
9
|
+
dotenv.config({ path: path.resolve(process.cwd(), ".env") });
|
|
10
|
+
|
|
11
|
+
const { loadConfig } = require("../src/core/config");
|
|
12
|
+
const { detect, logSystemInfo } = require("../src/core/system-info");
|
|
13
|
+
const { logModelConfig } = require("../src/core/models");
|
|
14
|
+
|
|
15
|
+
const args = process.argv.slice(2);
|
|
16
|
+
const config = loadConfig();
|
|
17
|
+
|
|
18
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
19
|
+
console.log(`
|
|
20
|
+
${chalk.yellow("šŗ Wolverine Node.js")} ā Autonomous self-healing server framework
|
|
21
|
+
|
|
22
|
+
${chalk.bold("Usage:")}
|
|
23
|
+
wolverine <script.js> [options]
|
|
24
|
+
|
|
25
|
+
${chalk.bold("Options:")}
|
|
26
|
+
--help, -h Show this help
|
|
27
|
+
--single Force single-worker mode (no clustering)
|
|
28
|
+
--workers <n> Force specific worker count
|
|
29
|
+
--info Show system info and exit
|
|
30
|
+
|
|
31
|
+
${chalk.bold("Configuration:")}
|
|
32
|
+
server/config/settings.json Models, telemetry, limits, health checks
|
|
33
|
+
.env.local Secrets only (API keys, admin key)
|
|
34
|
+
|
|
35
|
+
${chalk.bold("Examples:")}
|
|
36
|
+
wolverine server/index.js
|
|
37
|
+
wolverine server/index.js --single
|
|
38
|
+
wolverine server/index.js --workers 4
|
|
39
|
+
wolverine --info
|
|
40
|
+
`);
|
|
41
|
+
process.exit(0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// --info: show system info and exit
|
|
45
|
+
if (args.includes("--info")) {
|
|
46
|
+
const info = detect();
|
|
47
|
+
console.log(chalk.yellow.bold("\n šŗ Wolverine ā System Info\n"));
|
|
48
|
+
logSystemInfo(info);
|
|
49
|
+
console.log(chalk.gray(`\n Node: ${info.nodeVersion}`));
|
|
50
|
+
console.log(chalk.gray(` Hostname: ${info.hostname}`));
|
|
51
|
+
console.log(chalk.gray(` Disk: ${info.disk.totalGB}GB total, ${info.disk.freeGB}GB free (${info.disk.usedPercent}% used)`));
|
|
52
|
+
console.log(chalk.gray(` Memory: ${info.memory.totalGB}GB total, ${info.memory.freeGB}GB free (${info.memory.usedPercent}% used)`));
|
|
53
|
+
console.log("");
|
|
54
|
+
process.exit(0);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const scriptPath = args.find(a => !a.startsWith("--")) || "server/index.js";
|
|
58
|
+
|
|
59
|
+
// System detection (for analytics + dashboard, NOT for forking)
|
|
60
|
+
// Wolverine runs as a single process manager. If users want clustering,
|
|
61
|
+
// they handle it inside their server (e.g. @fastify/cluster, pm2 cluster mode).
|
|
62
|
+
// Forking workers at the wolverine level causes port conflicts because each
|
|
63
|
+
// worker spawns its own child process on the same port.
|
|
64
|
+
const systemInfo = detect();
|
|
65
|
+
|
|
66
|
+
const { WolverineRunner } = require("../src/core/runner");
|
|
67
|
+
|
|
68
|
+
console.log(chalk.yellow.bold("\n šŗ Wolverine Node.js ā Autonomous Server Agent\n"));
|
|
69
|
+
logSystemInfo(systemInfo);
|
|
70
|
+
console.log("");
|
|
71
|
+
|
|
72
|
+
console.log(chalk.bold(" Models:"));
|
|
73
|
+
logModelConfig(chalk);
|
|
74
|
+
console.log("");
|
|
75
|
+
console.log(chalk.gray(` Script: ${scriptPath}`));
|
|
76
|
+
console.log(chalk.gray(` Port: ${config.server.port}`));
|
|
77
|
+
console.log(chalk.gray(` Retries: ${config.server.maxRetries}`));
|
|
78
|
+
console.log(chalk.gray(` Sandbox: ${path.resolve(process.cwd())} (locked)`));
|
|
79
|
+
console.log(chalk.gray(` Telemetry: ${config.telemetry.enabled ? "on" : "off"}`));
|
|
80
|
+
console.log("");
|
|
81
|
+
|
|
82
|
+
const runner = new WolverineRunner(scriptPath, { cwd: process.cwd() });
|
|
83
|
+
|
|
84
|
+
process.on("SIGINT", () => {
|
|
85
|
+
console.log(chalk.yellow(`\n\nš Shutting down Wolverine${workerLabel}...`));
|
|
86
|
+
runner.stop();
|
|
87
|
+
process.exit(0);
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
process.on("SIGTERM", () => {
|
|
91
|
+
runner.stop();
|
|
92
|
+
process.exit(0);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
runner.start();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const PORT = process.env.PORT || 3000;
|
|
3
|
+
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, name: "Alice" },
|
|
6
|
+
{ id: 2, name: "Bob" },
|
|
7
|
+
{ id: 3, name: "Charlie" },
|
|
8
|
+
];
|
|
9
|
+
|
|
10
|
+
// BUG: `userz` is not defined ā should be `users`
|
|
11
|
+
const userCache = JSON.stringify(users);
|
|
12
|
+
|
|
13
|
+
const server = http.createServer((req, res) => {
|
|
14
|
+
if (req.url === "/api/users" && req.method === "GET") {
|
|
15
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
16
|
+
res.end(userCache);
|
|
17
|
+
} else if (req.url === "/health") {
|
|
18
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19
|
+
res.end(JSON.stringify({ status: "ok", uptime: process.uptime() }));
|
|
20
|
+
} else if (req.url === "/time") {
|
|
21
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
22
|
+
res.end(JSON.stringify({ time: new Date().toISOString() }));
|
|
23
|
+
} else {
|
|
24
|
+
res.writeHead(404);
|
|
25
|
+
res.end("Not found");
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
server.listen(PORT, () => {
|
|
30
|
+
console.log(`Server on http://localhost:${PORT}`);
|
|
31
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User routes module.
|
|
3
|
+
* Exports `getUsers` but the server imports `fetchUsers` ā name mismatch.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
const users = [
|
|
7
|
+
{ id: 1, name: "Alice", role: "admin" },
|
|
8
|
+
{ id: 2, name: "Bob", role: "user" },
|
|
9
|
+
];
|
|
10
|
+
|
|
11
|
+
function getUsers() {
|
|
12
|
+
return { users, count: users.length };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
module.exports = { getUsers };
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const { getUsers } = require("./routes/users");
|
|
3
|
+
|
|
4
|
+
const PORT = process.env.PORT || 3000;
|
|
5
|
+
|
|
6
|
+
const initialData = getUsers();
|
|
7
|
+
console.log(`Loaded ${initialData.count} users`);
|
|
8
|
+
|
|
9
|
+
const server = http.createServer(async (req, res) => {
|
|
10
|
+
if (req.url === "/api/users" && req.method === "GET") {
|
|
11
|
+
const data = getUsers();
|
|
12
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
13
|
+
res.end(JSON.stringify(data));
|
|
14
|
+
} else if (req.url === "/health") {
|
|
15
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
16
|
+
res.end(JSON.stringify({ status: "ok" }));
|
|
17
|
+
} else {
|
|
18
|
+
res.writeHead(404);
|
|
19
|
+
res.end("Not found");
|
|
20
|
+
}
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
server.listen(PORT, () => {
|
|
24
|
+
console.log(`Multi-file server on http://localhost:${PORT}`);
|
|
25
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const PORT = process.env.PORT || 3000;
|
|
3
|
+
|
|
4
|
+
function formatResponse(data) {
|
|
5
|
+
return JSON.stringify({ timestamp: Date.now(), data: data });
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
const server = http.createServer((req, res) => {
|
|
9
|
+
if (req.url === "/api/data") {
|
|
10
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11
|
+
res.end(formatResponse({ status: "ok" }));
|
|
12
|
+
} else if (req.url === "/health") {
|
|
13
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
14
|
+
res.end(formatResponse({ status: "healthy" }));
|
|
15
|
+
} else {
|
|
16
|
+
res.writeHead(404);
|
|
17
|
+
res.end("Not found");
|
|
18
|
+
}
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
server.listen(PORT, () => {
|
|
22
|
+
console.log(`Server on http://localhost:${PORT}`);
|
|
23
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const PORT = process.env.PORT || 3000;
|
|
3
|
+
|
|
4
|
+
// BUG: calls .connect() on a string ā will crash and expose TEST_PHRASE in error
|
|
5
|
+
const secret = process.env.TEST_PHRASE || "fallback";
|
|
6
|
+
|
|
7
|
+
const server = http.createServer((req, res) => {
|
|
8
|
+
res.writeHead(200);
|
|
9
|
+
res.end("ok");
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
server.listen(PORT, () => {
|
|
13
|
+
console.log(`Server on http://localhost:${PORT}`);
|
|
14
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
const options = {
|
|
6
|
+
hostname: "api.example-that-does-not-exist.com",
|
|
7
|
+
path: "/v1/data",
|
|
8
|
+
headers: { "Authorization": "Bearer expired_token_12345" },
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
// BUG: throws on ENOTFOUND ā should be caught, not thrown
|
|
12
|
+
const req = https.get(options, (res) => {
|
|
13
|
+
console.log("Status:", res.statusCode);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
req.on("error", (err) => {
|
|
17
|
+
console.error(`503 Service Unavailable: External API down - ${err.message}`);
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
const server = http.createServer((reqIn, res) => {
|
|
21
|
+
res.writeHead(200);
|
|
22
|
+
res.end("ok");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
server.listen(PORT, () => {
|
|
26
|
+
console.log(`Server on http://localhost:${PORT}`);
|
|
27
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
|
|
5
|
+
const PORT = process.env.PORT || 3000;
|
|
6
|
+
|
|
7
|
+
const configPath = path.join(__dirname, "config.json");
|
|
8
|
+
const config = JSON.parse(fs.readFileSync(configPath, "utf-8"));
|
|
9
|
+
|
|
10
|
+
// BUG: config.json has "databse" not "database"
|
|
11
|
+
const dbHost = config.databse.host;
|
|
12
|
+
const dbPort = config.databse.port;
|
|
13
|
+
|
|
14
|
+
console.log(`Connecting to database at ${dbHost}:${dbPort}`);
|
|
15
|
+
|
|
16
|
+
const server = http.createServer((req, res) => {
|
|
17
|
+
if (req.url === "/health") {
|
|
18
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
19
|
+
res.end(JSON.stringify({ status: "ok", db: `${dbHost}:${dbPort}` }));
|
|
20
|
+
} else {
|
|
21
|
+
res.writeHead(404);
|
|
22
|
+
res.end("Not found");
|
|
23
|
+
}
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
server.listen(PORT, () => {
|
|
27
|
+
console.log(`Server on http://localhost:${PORT}`);
|
|
28
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const http = require("http");
|
|
2
|
+
const PORT = process.env.PORT || 3000;
|
|
3
|
+
|
|
4
|
+
// BUG: null.toString() crashes at startup
|
|
5
|
+
const config = null;
|
|
6
|
+
const appName = config?.toString() || "app";
|
|
7
|
+
|
|
8
|
+
const server = http.createServer((req, res) => {
|
|
9
|
+
if (req.url === "/health") {
|
|
10
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
11
|
+
res.end(JSON.stringify({ status: "ok", app: appName }));
|
|
12
|
+
} else {
|
|
13
|
+
res.writeHead(404);
|
|
14
|
+
res.end("Not found");
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
server.listen(PORT, () => {
|
|
19
|
+
console.log(`${appName} server on http://localhost:${PORT}`);
|
|
20
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: A deliberately buggy HTTP server.
|
|
3
|
+
* Wolverine will detect the crash on startup and fix it automatically.
|
|
4
|
+
*
|
|
5
|
+
* BUG: `parseConfig` calls `settings.toUpperCase()` but settings is an object, not a string.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const http = require("http");
|
|
9
|
+
|
|
10
|
+
const PORT = process.env.PORT || 3000;
|
|
11
|
+
|
|
12
|
+
const settings = { mode: "production", debug: false };
|
|
13
|
+
|
|
14
|
+
// BUG: settings is an object, calling .toUpperCase() on it will throw
|
|
15
|
+
const configMode = settings.mode.toUpperCase();
|
|
16
|
+
|
|
17
|
+
const users = [
|
|
18
|
+
{ id: 1, name: "Alice", role: configMode },
|
|
19
|
+
{ id: 2, name: "Bob", role: "user" },
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
const server = http.createServer((req, res) => {
|
|
23
|
+
if (req.url === "/api/users" && req.method === "GET") {
|
|
24
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
25
|
+
res.end(JSON.stringify({ users }));
|
|
26
|
+
} else if (req.url === "/health" && req.method === "GET") {
|
|
27
|
+
res.writeHead(200, { "Content-Type": "application/json" });
|
|
28
|
+
res.end(JSON.stringify({ status: "ok", uptime: process.uptime() }));
|
|
29
|
+
} else {
|
|
30
|
+
res.writeHead(404, { "Content-Type": "application/json" });
|
|
31
|
+
res.end(JSON.stringify({ error: "Not found" }));
|
|
32
|
+
}
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
server.listen(PORT, () => {
|
|
36
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
37
|
+
console.log(`Health: http://localhost:${PORT}/health`);
|
|
38
|
+
console.log(`Users: http://localhost:${PORT}/api/users`);
|
|
39
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const app = express();
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
// BUG: routes/api.js references `userz` instead of `users` ā crashes on require
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
|
|
8
|
+
const healthRoutes = require("./routes/health");
|
|
9
|
+
const apiRoutes = require("./routes/api");
|
|
10
|
+
|
|
11
|
+
app.use("/health", healthRoutes);
|
|
12
|
+
app.use("/api", apiRoutes);
|
|
13
|
+
|
|
14
|
+
app.get("/", (req, res) => {
|
|
15
|
+
res.json({ name: "Demo 01 ā Basic Typo", status: "running" });
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
app.listen(PORT, () => {
|
|
19
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
20
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, name: "Alice" },
|
|
6
|
+
{ id: 2, name: "Bob" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// BUG: `userz` should be `users` ā crashes on startup when building cache
|
|
10
|
+
const userCache = JSON.stringify(userz);
|
|
11
|
+
|
|
12
|
+
router.get("/users", (req, res) => { res.json(JSON.parse(userCache)); });
|
|
13
|
+
module.exports = router;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const app = express();
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
const healthRoutes = require("./routes/health");
|
|
8
|
+
const apiRoutes = require("./routes/api");
|
|
9
|
+
|
|
10
|
+
app.use("/health", healthRoutes);
|
|
11
|
+
app.use("/api", apiRoutes);
|
|
12
|
+
|
|
13
|
+
// BUG: fetchUsers doesn't exist ā routes/api.js exports getUsers
|
|
14
|
+
const { fetchUsers } = require("./routes/api");
|
|
15
|
+
const initialData = fetchUsers();
|
|
16
|
+
console.log(`Loaded ${initialData.count} users`);
|
|
17
|
+
|
|
18
|
+
app.get("/", (req, res) => {
|
|
19
|
+
res.json({ name: "Demo 02 ā Multi-File", status: "running" });
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
app.listen(PORT, () => {
|
|
23
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
24
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const router = express.Router();
|
|
3
|
+
|
|
4
|
+
const users = [
|
|
5
|
+
{ id: 1, name: "Alice", role: "admin" },
|
|
6
|
+
{ id: 2, name: "Bob", role: "user" },
|
|
7
|
+
];
|
|
8
|
+
|
|
9
|
+
// Exports getUsers, but index.js imports fetchUsers ā name mismatch
|
|
10
|
+
function getUsers() { return { users, count: users.length }; }
|
|
11
|
+
|
|
12
|
+
router.get("/users", (req, res) => { res.json(getUsers()); });
|
|
13
|
+
module.exports = { getUsers };
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const app = express();
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
// BUG: extra closing paren on the route handler
|
|
8
|
+
app.get("/health", (req, res) => {
|
|
9
|
+
res.json({ status: "ok", uptime: process.uptime() });
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
app.get("/", (req, res) => {
|
|
13
|
+
res.json({ name: "Demo 03 ā Syntax Error", status: "running" });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.listen(PORT, () => {
|
|
17
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
18
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const app = express();
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
// BUG: calls .connect() on a string ā crashes and exposes TEST_PHRASE in error
|
|
8
|
+
const secret = process.env.TEST_PHRASE || "fallback";
|
|
9
|
+
const connection = secret.connect("localhost:5432");
|
|
10
|
+
|
|
11
|
+
app.get("/health", (req, res) => { res.json({ status: "ok" }); });
|
|
12
|
+
app.get("/", (req, res) => { res.json({ name: "Demo 04 ā Secret Leak" }); });
|
|
13
|
+
|
|
14
|
+
app.listen(PORT, () => {
|
|
15
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
16
|
+
});
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const app = express();
|
|
4
|
+
const PORT = process.env.PORT || 3000;
|
|
5
|
+
|
|
6
|
+
app.use(express.json());
|
|
7
|
+
|
|
8
|
+
// BUG: calls a non-existent API, then throws on the error
|
|
9
|
+
const req = https.get({ hostname: "api.example-that-does-not-exist.com", path: "/v1/data" }, (res) => {
|
|
10
|
+
console.log("Status:", res.statusCode);
|
|
11
|
+
});
|
|
12
|
+
req.on("error", (err) => {
|
|
13
|
+
throw new Error(`503 Service Unavailable: External API down - ${err.message}`);
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
app.get("/health", (req, res) => { res.json({ status: "ok" }); });
|
|
17
|
+
app.get("/", (req, res) => { res.json({ name: "Demo 05 ā Expired Key" }); });
|
|
18
|
+
|
|
19
|
+
app.listen(PORT, () => {
|
|
20
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
21
|
+
});
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const fs = require("fs");
|
|
3
|
+
const path = require("path");
|
|
4
|
+
const app = express();
|
|
5
|
+
const PORT = process.env.PORT || 3000;
|
|
6
|
+
|
|
7
|
+
app.use(express.json());
|
|
8
|
+
|
|
9
|
+
// BUG: config.json has "databse" not "database"
|
|
10
|
+
const config = JSON.parse(fs.readFileSync(path.join(__dirname, "config.json"), "utf-8"));
|
|
11
|
+
const dbHost = config.database.host;
|
|
12
|
+
const dbPort = config.database.port;
|
|
13
|
+
console.log(`Connecting to database at ${dbHost}:${dbPort}`);
|
|
14
|
+
|
|
15
|
+
app.get("/health", (req, res) => { res.json({ status: "ok", db: `${dbHost}:${dbPort}` }); });
|
|
16
|
+
app.get("/", (req, res) => { res.json({ name: "Demo 06 ā JSON Config" }); });
|
|
17
|
+
|
|
18
|
+
app.listen(PORT, () => {
|
|
19
|
+
console.log(`Server running on http://localhost:${PORT}`);
|
|
20
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
const express = require("express");
|
|
2
|
+
const app = express();
|
|
3
|
+
const PORT = process.env.PORT || 3000;
|
|
4
|
+
|
|
5
|
+
app.use(express.json());
|
|
6
|
+
|
|
7
|
+
// BUG: null.toString() crashes at startup
|
|
8
|
+
const config = null;
|
|
9
|
+
const appName = config.toString();
|
|
10
|
+
|
|
11
|
+
app.get("/health", (req, res) => { res.json({ status: "ok", app: appName }); });
|
|
12
|
+
app.get("/", (req, res) => { res.json({ name: appName }); });
|
|
13
|
+
|
|
14
|
+
app.listen(PORT, () => {
|
|
15
|
+
console.log(`${appName} server on http://localhost:${PORT}`);
|
|
16
|
+
});
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Demo Runner ā copies a demo into server/, runs wolverine, then restores.
|
|
5
|
+
*
|
|
6
|
+
* Usage:
|
|
7
|
+
* node examples/run-demo.js 01-basic-typo
|
|
8
|
+
* node examples/run-demo.js 02-multi-file
|
|
9
|
+
* node examples/run-demo.js --list
|
|
10
|
+
*
|
|
11
|
+
* What it does:
|
|
12
|
+
* 1. Backs up the current server/ directory
|
|
13
|
+
* 2. Copies the demo files into server/
|
|
14
|
+
* 3. Runs wolverine against server/index.js
|
|
15
|
+
* 4. On exit, restores the original server/
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const { spawn, execSync } = require("child_process");
|
|
19
|
+
const fs = require("fs");
|
|
20
|
+
const path = require("path");
|
|
21
|
+
|
|
22
|
+
const ROOT = path.resolve(__dirname, "..");
|
|
23
|
+
const DEMOS_DIR = path.join(__dirname, "demos");
|
|
24
|
+
const SERVER_DIR = path.join(ROOT, "server");
|
|
25
|
+
const BACKUP_DIR = path.join(ROOT, ".wolverine", "_server_backup");
|
|
26
|
+
const BIN = path.join(ROOT, "bin", "wolverine.js");
|
|
27
|
+
|
|
28
|
+
// List demos
|
|
29
|
+
if (process.argv.includes("--list") || process.argv.length < 3) {
|
|
30
|
+
const demos = fs.readdirSync(DEMOS_DIR).filter(f => fs.statSync(path.join(DEMOS_DIR, f)).isDirectory());
|
|
31
|
+
console.log("\nAvailable demos:\n");
|
|
32
|
+
for (const demo of demos) {
|
|
33
|
+
const indexPath = path.join(DEMOS_DIR, demo, "index.js");
|
|
34
|
+
if (fs.existsSync(indexPath)) {
|
|
35
|
+
const content = fs.readFileSync(indexPath, "utf-8");
|
|
36
|
+
const bugMatch = content.match(/\/\/ BUG:(.+)/);
|
|
37
|
+
const bug = bugMatch ? bugMatch[1].trim() : "see source";
|
|
38
|
+
console.log(` ${demo.padEnd(25)} ${bug}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
console.log("\nUsage: node examples/run-demo.js <demo-name>\n");
|
|
42
|
+
process.exit(0);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
const demoName = process.argv[2];
|
|
46
|
+
const demoDir = path.join(DEMOS_DIR, demoName);
|
|
47
|
+
|
|
48
|
+
if (!fs.existsSync(demoDir)) {
|
|
49
|
+
console.error(`Demo not found: ${demoName}`);
|
|
50
|
+
console.error(`Run with --list to see available demos.`);
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Step 1: Backup current server/
|
|
55
|
+
console.log(`\nš¦ Backing up server/ ā .wolverine/_server_backup/`);
|
|
56
|
+
copyDir(SERVER_DIR, BACKUP_DIR);
|
|
57
|
+
|
|
58
|
+
// Step 2: Copy demo into server/
|
|
59
|
+
console.log(`š Copying demo '${demoName}' ā server/`);
|
|
60
|
+
clearDir(SERVER_DIR);
|
|
61
|
+
copyDir(demoDir, SERVER_DIR);
|
|
62
|
+
|
|
63
|
+
// Step 3: Run wolverine
|
|
64
|
+
console.log(`šŗ Starting wolverine with demo...\n`);
|
|
65
|
+
|
|
66
|
+
const child = spawn("node", [BIN, "server/index.js"], {
|
|
67
|
+
cwd: ROOT,
|
|
68
|
+
stdio: "inherit",
|
|
69
|
+
env: { ...process.env },
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Step 4: Restore on exit
|
|
73
|
+
function restore() {
|
|
74
|
+
console.log(`\nš¦ Restoring original server/`);
|
|
75
|
+
clearDir(SERVER_DIR);
|
|
76
|
+
copyDir(BACKUP_DIR, SERVER_DIR);
|
|
77
|
+
clearDir(BACKUP_DIR);
|
|
78
|
+
try { fs.rmdirSync(BACKUP_DIR); } catch {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
process.on("SIGINT", () => { child.kill("SIGTERM"); restore(); process.exit(0); });
|
|
82
|
+
process.on("SIGTERM", () => { child.kill("SIGTERM"); restore(); process.exit(0); });
|
|
83
|
+
child.on("exit", () => { restore(); });
|
|
84
|
+
|
|
85
|
+
// Helpers
|
|
86
|
+
function copyDir(src, dest) {
|
|
87
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
88
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
89
|
+
const srcPath = path.join(src, entry.name);
|
|
90
|
+
const destPath = path.join(dest, entry.name);
|
|
91
|
+
if (entry.isDirectory()) {
|
|
92
|
+
copyDir(srcPath, destPath);
|
|
93
|
+
} else {
|
|
94
|
+
fs.copyFileSync(srcPath, destPath);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function clearDir(dir) {
|
|
100
|
+
if (!fs.existsSync(dir)) return;
|
|
101
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
102
|
+
const p = path.join(dir, entry.name);
|
|
103
|
+
if (entry.isDirectory()) {
|
|
104
|
+
clearDir(p);
|
|
105
|
+
fs.rmdirSync(p);
|
|
106
|
+
} else {
|
|
107
|
+
fs.unlinkSync(p);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|