blumenjs 0.2.1 → 0.2.3

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.
@@ -0,0 +1,204 @@
1
+ // cli/commands/audit.ts
2
+ import { execSync } from "child_process";
3
+
4
+ // cli/utils.ts
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { fileURLToPath } from "url";
8
+ var c = {
9
+ reset: "\x1B[0m",
10
+ bold: "\x1B[1m",
11
+ dim: "\x1B[2m",
12
+ red: "\x1B[31m",
13
+ green: "\x1B[32m",
14
+ yellow: "\x1B[33m",
15
+ blue: "\x1B[34m",
16
+ magenta: "\x1B[35m",
17
+ cyan: "\x1B[36m",
18
+ white: "\x1B[37m",
19
+ gray: "\x1B[90m"
20
+ };
21
+ var log = {
22
+ info: (msg) => console.log(` ${c.magenta}\u25CF${c.reset} ${msg}`),
23
+ success: (msg) => console.log(` ${c.green}\u2713${c.reset} ${msg}`),
24
+ error: (msg) => console.error(` ${c.red}\u2717${c.reset} ${msg}`),
25
+ warn: (msg) => console.log(` ${c.yellow}\u26A0${c.reset} ${msg}`),
26
+ step: (msg) => console.log(` ${c.dim}\u2192${c.reset} ${msg}`),
27
+ blank: () => console.log("")
28
+ };
29
+ function getVersion() {
30
+ try {
31
+ const thisFile = fileURLToPath(import.meta.url);
32
+ let dir = path.dirname(thisFile);
33
+ for (let i = 0; i < 5; i++) {
34
+ const pkgFile = path.join(dir, "package.json");
35
+ if (fs.existsSync(pkgFile)) {
36
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf-8"));
37
+ if (pkg.name === "blumenjs" || pkg.name === "go-react-ssr") {
38
+ return pkg.version;
39
+ }
40
+ }
41
+ dir = path.dirname(dir);
42
+ }
43
+ return "0.0.0";
44
+ } catch {
45
+ return "0.0.0";
46
+ }
47
+ }
48
+ function banner() {
49
+ const version = getVersion();
50
+ console.log("");
51
+ console.log(
52
+ ` ${c.magenta}${c.bold}\u{1F338} Blumen${c.reset} ${c.dim}v${version}${c.reset}`
53
+ );
54
+ console.log(
55
+ ` ${c.dim}The React framework powered by Go${c.reset}`
56
+ );
57
+ console.log("");
58
+ }
59
+ function divider() {
60
+ console.log(` ${c.dim}${"\u2500".repeat(48)}${c.reset}`);
61
+ }
62
+
63
+ // cli/commands/audit.ts
64
+ function severityColor(severity) {
65
+ switch (severity) {
66
+ case "critical":
67
+ return c.red;
68
+ case "high":
69
+ return c.red;
70
+ case "moderate":
71
+ return c.yellow;
72
+ case "low":
73
+ return c.dim;
74
+ default:
75
+ return c.dim;
76
+ }
77
+ }
78
+ function severityIcon(severity) {
79
+ switch (severity) {
80
+ case "critical":
81
+ return "\u{1F534}";
82
+ case "high":
83
+ return "\u{1F7E0}";
84
+ case "moderate":
85
+ return "\u{1F7E1}";
86
+ case "low":
87
+ return "\u{1F535}";
88
+ default:
89
+ return "\u26AA";
90
+ }
91
+ }
92
+ async function audit(args = []) {
93
+ banner();
94
+ const fix = args.includes("--fix");
95
+ const ci = args.includes("--ci");
96
+ const jsonOutput = args.includes("--json");
97
+ if (fix) {
98
+ log.info("Attempting to fix vulnerabilities...");
99
+ log.blank();
100
+ try {
101
+ execSync("npm audit fix", {
102
+ stdio: "inherit",
103
+ cwd: process.cwd()
104
+ });
105
+ log.blank();
106
+ log.success("Audit fix completed.");
107
+ } catch {
108
+ log.warn("Some vulnerabilities could not be fixed automatically.");
109
+ log.info(`Run ${c.bold}npm audit fix --force${c.reset} to force-fix (may include breaking changes).`);
110
+ }
111
+ return;
112
+ }
113
+ if (jsonOutput) {
114
+ try {
115
+ execSync("npm audit --json", {
116
+ stdio: "inherit",
117
+ cwd: process.cwd()
118
+ });
119
+ } catch {
120
+ process.exit(1);
121
+ }
122
+ return;
123
+ }
124
+ log.info("Scanning dependencies for known vulnerabilities...");
125
+ log.blank();
126
+ let auditOutput;
127
+ let hasVulnerabilities = false;
128
+ try {
129
+ auditOutput = execSync("npm audit --json 2>/dev/null", {
130
+ cwd: process.cwd(),
131
+ encoding: "utf-8"
132
+ });
133
+ } catch (err) {
134
+ auditOutput = err.stdout || "{}";
135
+ hasVulnerabilities = true;
136
+ }
137
+ try {
138
+ const report = JSON.parse(auditOutput);
139
+ const meta = report.metadata || {};
140
+ const vulns = meta.vulnerabilities || {
141
+ info: 0,
142
+ low: 0,
143
+ moderate: 0,
144
+ high: 0,
145
+ critical: 0,
146
+ total: 0
147
+ };
148
+ const deps = meta.dependencies || {};
149
+ const total = vulns.total || vulns.critical + vulns.high + vulns.moderate + vulns.low + (vulns.info || 0);
150
+ divider();
151
+ log.blank();
152
+ if (total === 0) {
153
+ log.success(`${c.bold}No vulnerabilities found!${c.reset} \u2728`);
154
+ log.blank();
155
+ if (deps.total) {
156
+ log.info(`Scanned ${c.bold}${deps.total}${c.reset} dependencies.`);
157
+ }
158
+ } else {
159
+ log.warn(`${c.bold}${total} vulnerabilit${total === 1 ? "y" : "ies"} found${c.reset}`);
160
+ log.blank();
161
+ const levels = [
162
+ { name: "critical", count: vulns.critical },
163
+ { name: "high", count: vulns.high },
164
+ { name: "moderate", count: vulns.moderate },
165
+ { name: "low", count: vulns.low }
166
+ ];
167
+ for (const level of levels) {
168
+ if (level.count > 0) {
169
+ const color = severityColor(level.name);
170
+ const icon = severityIcon(level.name);
171
+ console.log(` ${icon} ${color}${level.count} ${level.name}${c.reset}`);
172
+ }
173
+ }
174
+ log.blank();
175
+ if (deps.total) {
176
+ log.info(`Scanned ${c.bold}${deps.total}${c.reset} dependencies.`);
177
+ }
178
+ log.info(`Run ${c.bold}blumen audit --fix${c.reset} to attempt automatic fixes.`);
179
+ }
180
+ log.blank();
181
+ divider();
182
+ log.blank();
183
+ if (ci && (vulns.critical > 0 || vulns.high > 0)) {
184
+ log.error("CI check failed: high or critical vulnerabilities detected.");
185
+ process.exit(1);
186
+ }
187
+ } catch {
188
+ log.info("Detailed report:");
189
+ log.blank();
190
+ try {
191
+ execSync("npm audit", {
192
+ stdio: "inherit",
193
+ cwd: process.cwd()
194
+ });
195
+ } catch {
196
+ if (ci) {
197
+ process.exit(1);
198
+ }
199
+ }
200
+ }
201
+ }
202
+ export {
203
+ audit
204
+ };
@@ -0,0 +1,227 @@
1
+ // cli/commands/bench.ts
2
+ import * as http from "http";
3
+
4
+ // cli/utils.ts
5
+ import * as fs from "fs";
6
+ import * as path from "path";
7
+ import { fileURLToPath } from "url";
8
+ var c = {
9
+ reset: "\x1B[0m",
10
+ bold: "\x1B[1m",
11
+ dim: "\x1B[2m",
12
+ red: "\x1B[31m",
13
+ green: "\x1B[32m",
14
+ yellow: "\x1B[33m",
15
+ blue: "\x1B[34m",
16
+ magenta: "\x1B[35m",
17
+ cyan: "\x1B[36m",
18
+ white: "\x1B[37m",
19
+ gray: "\x1B[90m"
20
+ };
21
+ var log = {
22
+ info: (msg) => console.log(` ${c.magenta}\u25CF${c.reset} ${msg}`),
23
+ success: (msg) => console.log(` ${c.green}\u2713${c.reset} ${msg}`),
24
+ error: (msg) => console.error(` ${c.red}\u2717${c.reset} ${msg}`),
25
+ warn: (msg) => console.log(` ${c.yellow}\u26A0${c.reset} ${msg}`),
26
+ step: (msg) => console.log(` ${c.dim}\u2192${c.reset} ${msg}`),
27
+ blank: () => console.log("")
28
+ };
29
+ function getVersion() {
30
+ try {
31
+ const thisFile = fileURLToPath(import.meta.url);
32
+ let dir = path.dirname(thisFile);
33
+ for (let i = 0; i < 5; i++) {
34
+ const pkgFile = path.join(dir, "package.json");
35
+ if (fs.existsSync(pkgFile)) {
36
+ const pkg = JSON.parse(fs.readFileSync(pkgFile, "utf-8"));
37
+ if (pkg.name === "blumenjs" || pkg.name === "go-react-ssr") {
38
+ return pkg.version;
39
+ }
40
+ }
41
+ dir = path.dirname(dir);
42
+ }
43
+ return "0.0.0";
44
+ } catch {
45
+ return "0.0.0";
46
+ }
47
+ }
48
+ function banner() {
49
+ const version = getVersion();
50
+ console.log("");
51
+ console.log(
52
+ ` ${c.magenta}${c.bold}\u{1F338} Blumen${c.reset} ${c.dim}v${version}${c.reset}`
53
+ );
54
+ console.log(
55
+ ` ${c.dim}The React framework powered by Go${c.reset}`
56
+ );
57
+ console.log("");
58
+ }
59
+ function divider() {
60
+ console.log(` ${c.dim}${"\u2500".repeat(48)}${c.reset}`);
61
+ }
62
+
63
+ // cli/commands/bench.ts
64
+ function percentile(sorted, p) {
65
+ const idx = Math.ceil(p / 100 * sorted.length) - 1;
66
+ return sorted[Math.max(0, idx)];
67
+ }
68
+ function makeRequest(url) {
69
+ return new Promise((resolve, reject) => {
70
+ const start = performance.now();
71
+ const req = http.get(url, (res) => {
72
+ let body = "";
73
+ res.on("data", (chunk) => {
74
+ body += chunk;
75
+ });
76
+ res.on("end", () => {
77
+ resolve({
78
+ latency: performance.now() - start,
79
+ status: res.statusCode || 0,
80
+ cacheHeader: res.headers["x-blumen-cache"] || ""
81
+ });
82
+ });
83
+ });
84
+ req.on("error", reject);
85
+ req.setTimeout(1e4, () => {
86
+ req.destroy();
87
+ reject(new Error("Timeout"));
88
+ });
89
+ });
90
+ }
91
+ async function runBenchmark(url, totalRequests, concurrency) {
92
+ const latencies = [];
93
+ let successful = 0;
94
+ let failed = 0;
95
+ let cacheHits = 0;
96
+ let cacheMisses = 0;
97
+ let streamed = 0;
98
+ let completed = 0;
99
+ const start = performance.now();
100
+ for (let i = 0; i < totalRequests; i += concurrency) {
101
+ const batchSize = Math.min(concurrency, totalRequests - i);
102
+ const batch = Array.from(
103
+ { length: batchSize },
104
+ () => makeRequest(url).then((result) => {
105
+ latencies.push(result.latency);
106
+ if (result.status >= 200 && result.status < 400) {
107
+ successful++;
108
+ } else {
109
+ failed++;
110
+ }
111
+ if (result.cacheHeader === "HIT" || result.cacheHeader === "STALE")
112
+ cacheHits++;
113
+ else if (result.cacheHeader === "MISS")
114
+ cacheMisses++;
115
+ else if (result.cacheHeader === "STREAM")
116
+ streamed++;
117
+ completed++;
118
+ }).catch(() => {
119
+ failed++;
120
+ completed++;
121
+ })
122
+ );
123
+ await Promise.all(batch);
124
+ const pct = Math.round(completed / totalRequests * 100);
125
+ process.stdout.write(`\r \u25CF Progress: ${pct}% (${completed}/${totalRequests})`);
126
+ }
127
+ process.stdout.write("\r" + " ".repeat(60) + "\r");
128
+ const totalTimeMs = performance.now() - start;
129
+ const sorted = latencies.sort((a, b) => a - b);
130
+ return {
131
+ url,
132
+ totalRequests,
133
+ successfulRequests: successful,
134
+ failedRequests: failed,
135
+ totalTimeMs,
136
+ requestsPerSec: successful / totalTimeMs * 1e3,
137
+ latencies: sorted,
138
+ p50: sorted.length ? percentile(sorted, 50) : 0,
139
+ p95: sorted.length ? percentile(sorted, 95) : 0,
140
+ p99: sorted.length ? percentile(sorted, 99) : 0,
141
+ min: sorted.length ? sorted[0] : 0,
142
+ max: sorted.length ? sorted[sorted.length - 1] : 0,
143
+ avg: sorted.length ? sorted.reduce((a, b) => a + b, 0) / sorted.length : 0,
144
+ cacheHits,
145
+ cacheMisses,
146
+ streamed
147
+ };
148
+ }
149
+ function formatMs(ms) {
150
+ if (ms < 1)
151
+ return `${(ms * 1e3).toFixed(0)}\xB5s`;
152
+ if (ms < 1e3)
153
+ return `${ms.toFixed(1)}ms`;
154
+ return `${(ms / 1e3).toFixed(2)}s`;
155
+ }
156
+ function printResult(result) {
157
+ console.log(` ${c.bold}URL${c.reset} ${result.url}`);
158
+ console.log(` ${c.bold}Requests${c.reset} ${result.successfulRequests}/${result.totalRequests} successful${result.failedRequests > 0 ? ` (${result.failedRequests} failed)` : ""}`);
159
+ console.log(` ${c.bold}Duration${c.reset} ${formatMs(result.totalTimeMs)}`);
160
+ console.log(` ${c.bold}Throughput${c.reset} ${c.green}${result.requestsPerSec.toFixed(1)} req/sec${c.reset}`);
161
+ console.log("");
162
+ console.log(` ${c.bold}Latency${c.reset}`);
163
+ console.log(` min ${formatMs(result.min)}`);
164
+ console.log(` avg ${formatMs(result.avg)}`);
165
+ console.log(` p50 ${formatMs(result.p50)}`);
166
+ console.log(` p95 ${c.yellow}${formatMs(result.p95)}${c.reset}`);
167
+ console.log(` p99 ${c.yellow}${formatMs(result.p99)}${c.reset}`);
168
+ console.log(` max ${formatMs(result.max)}`);
169
+ if (result.cacheHits > 0 || result.cacheMisses > 0 || result.streamed > 0) {
170
+ console.log("");
171
+ console.log(` ${c.bold}Cache${c.reset}`);
172
+ if (result.cacheHits > 0)
173
+ console.log(` HIT ${result.cacheHits} (${(result.cacheHits / result.totalRequests * 100).toFixed(0)}%)`);
174
+ if (result.cacheMisses > 0)
175
+ console.log(` MISS ${result.cacheMisses}`);
176
+ if (result.streamed > 0)
177
+ console.log(` STREAM ${result.streamed}`);
178
+ }
179
+ }
180
+ async function bench(args = []) {
181
+ banner();
182
+ let baseUrl = "http://localhost:3000";
183
+ let totalRequests = 200;
184
+ let concurrency = 10;
185
+ for (let i = 0; i < args.length; i++) {
186
+ if (args[i] === "--url" && args[i + 1]) {
187
+ baseUrl = args[++i];
188
+ } else if (args[i] === "--requests" && args[i + 1]) {
189
+ totalRequests = parseInt(args[++i], 10);
190
+ } else if (args[i] === "--concurrency" && args[i + 1]) {
191
+ concurrency = parseInt(args[++i], 10);
192
+ }
193
+ }
194
+ log.info(`Benchmarking ${c.bold}${baseUrl}${c.reset}`);
195
+ log.info(`${totalRequests} requests, ${concurrency} concurrent connections`);
196
+ log.blank();
197
+ divider();
198
+ const endpoints = [
199
+ { name: "Homepage (SSR)", path: "/" },
200
+ { name: "About (SSR)", path: "/about" },
201
+ { name: "Static Asset", path: "/static/js/runtime.js" }
202
+ ];
203
+ for (const endpoint of endpoints) {
204
+ const url = `${baseUrl}${endpoint.path}`;
205
+ log.blank();
206
+ console.log(` ${c.bold}${c.cyan}\u25B8 ${endpoint.name}${c.reset}`);
207
+ log.blank();
208
+ try {
209
+ const result = await runBenchmark(url, totalRequests, concurrency);
210
+ printResult(result);
211
+ } catch (err) {
212
+ log.error(` Failed to benchmark ${url}: ${err.message}`);
213
+ log.info(` Make sure the server is running (${c.bold}blumen dev${c.reset} or ${c.bold}blumen start${c.reset})`);
214
+ }
215
+ log.blank();
216
+ divider();
217
+ }
218
+ log.blank();
219
+ log.success("Benchmark complete! \u2728");
220
+ log.blank();
221
+ log.info(`For production load testing, use k6:`);
222
+ log.info(` ${c.bold}k6 run benchmarks/k6-load-test.js${c.reset}`);
223
+ log.blank();
224
+ }
225
+ export {
226
+ bench
227
+ };
@@ -1,5 +1,7 @@
1
1
  // cli/commands/build.ts
2
2
  import { execSync } from "child_process";
3
+ import * as fs2 from "fs";
4
+ import * as path2 from "path";
3
5
 
4
6
  // cli/utils.ts
5
7
  import * as fs from "fs";
@@ -61,6 +63,37 @@ function divider() {
61
63
  }
62
64
 
63
65
  // cli/commands/build.ts
66
+ function generateChunkManifest() {
67
+ const jsDir = path2.resolve("static/js");
68
+ const chunksDir = path2.resolve("static/js/chunks");
69
+ const manifest = {};
70
+ if (fs2.existsSync(jsDir)) {
71
+ for (const file of fs2.readdirSync(jsDir)) {
72
+ if (!file.endsWith(".js") || file.endsWith(".map"))
73
+ continue;
74
+ const match = file.match(/^([^.]+)\.[a-f0-9]+\.js$/);
75
+ if (match) {
76
+ manifest[match[1]] = `/static/js/${file}`;
77
+ }
78
+ }
79
+ }
80
+ if (fs2.existsSync(chunksDir)) {
81
+ for (const file of fs2.readdirSync(chunksDir)) {
82
+ if (!file.endsWith(".js") || file.endsWith(".map"))
83
+ continue;
84
+ const match = file.match(/^([^.]+)\.[a-f0-9]+\.js$/);
85
+ if (match) {
86
+ manifest[match[1]] = `/static/js/chunks/${file}`;
87
+ }
88
+ }
89
+ }
90
+ fs2.mkdirSync("dist", { recursive: true });
91
+ fs2.writeFileSync("dist/chunk-manifest.json", JSON.stringify(manifest, null, 2));
92
+ const coreChunks = ["runtime", "vendor", "framework", "main"].filter((k) => manifest[k]);
93
+ const pageChunks = Object.keys(manifest).filter((k) => k.startsWith("page-"));
94
+ log.info(` Core chunks: ${coreChunks.join(", ")} (${coreChunks.length})`);
95
+ log.info(` Page chunks: ${pageChunks.length} page(s)`);
96
+ }
64
97
  async function build(args = []) {
65
98
  banner();
66
99
  const analyze = args.includes("--analyze");
@@ -75,10 +108,14 @@ async function build(args = []) {
75
108
  cmd: "npx tsx scripts/generate-routes.ts"
76
109
  },
77
110
  {
78
- label: "Building client bundle",
111
+ label: "Building client bundle (code splitting)",
79
112
  cmd: "npx webpack --mode production",
80
113
  env: analyze ? { BLUMEN_ANALYZE: "1" } : void 0
81
114
  },
115
+ {
116
+ label: "Generating chunk manifest",
117
+ fn: generateChunkManifest
118
+ },
82
119
  {
83
120
  label: "Building SSR server",
84
121
  cmd: "npx esbuild node-ssr/server.ts --bundle --platform=node --format=esm --outfile=dist/ssr-server.js --packages=external --alias:@=./app"
@@ -95,11 +132,15 @@ async function build(args = []) {
95
132
  const step = steps[i];
96
133
  log.step(`[${i + 1}/${steps.length}] ${step.label}...`);
97
134
  try {
98
- execSync(step.cmd, {
99
- stdio: "inherit",
100
- cwd: process.cwd(),
101
- env: { ...process.env, ...step.env || {} }
102
- });
135
+ if (step.fn) {
136
+ step.fn();
137
+ } else if (step.cmd) {
138
+ execSync(step.cmd, {
139
+ stdio: "inherit",
140
+ cwd: process.cwd(),
141
+ env: { ...process.env, ...step.env || {} }
142
+ });
143
+ }
103
144
  log.success(step.label);
104
145
  } catch {
105
146
  if (step.optional) {