@velox0/cerver 0.4.2 → 0.5.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/README.md +37 -10
- package/lib/assets/embed.js +33 -8
- package/lib/codegen/dispatch_gen.js +4 -1
- package/lib/codegen/generator.js +90 -55
- package/lib/codegen/route_table.js +2 -2
- package/lib/commands/build.js +31 -8
- package/lib/commands/dev.js +9 -2
- package/lib/commands/new.js +667 -258
- package/lib/commands/run.js +8 -1
- package/lib/compiler/compile.js +9 -4
- package/lib/config.js +20 -1
- package/lib/parser/discover.js +6 -6
- package/package.json +2 -2
- package/runtime/cerver.h +10 -0
- package/runtime/http_writer.c +134 -27
- package/runtime/router.c +194 -10
- package/runtime/server.c +40 -6
- package/runtime/static.c +80 -64
- package/runtime/tests/runtime_tests.c +142 -13
- package/.github/workflows/ci.yml +0 -35
- package/.github/workflows/publish.yml +0 -50
- package/test/run.js +0 -355
package/README.md
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
|
-
<div align="center">
|
|
2
|
-
<img src="templates/cerver.png" alt="Cerver Logo" width="120" />
|
|
3
|
-
</div>
|
|
4
|
-
|
|
5
1
|
# Cerver
|
|
6
2
|
|
|
3
|
+
[](https://github.com/velox0/cerver/actions/workflows/publish.yml)
|
|
4
|
+
[](https://github.com/velox0/cerver/actions/workflows/ci.yml)
|
|
5
|
+
[](https://www.npmjs.com/package/@velox0/cerver)
|
|
6
|
+
|
|
7
|
+
<img src="templates/cerver.png" alt="Cerver Logo" width="200px" align="right" />
|
|
8
|
+
|
|
7
9
|
A lightweight, compile-time web framework that transpiles restricted JavaScript server logic into highly optimized native C HTTP server binaries.
|
|
8
10
|
|
|
9
11
|
Cerver takes a Next.js-style file-based routing structure (written in a strict subset of JavaScript), parses it, generates equivalent C code, embeds your static assets, and compiles it all into a single, standalone executable that runs with zero Node.js dependency.
|
|
@@ -11,10 +13,25 @@ Cerver takes a Next.js-style file-based routing structure (written in a strict s
|
|
|
11
13
|
## Features
|
|
12
14
|
|
|
13
15
|
- **Compile-Time Framework**: Your JavaScript is parsed and compiled to native C. There is no JavaScript engine (like V8) or interpreter included in the final binary.
|
|
14
|
-
- **Microscopic Footprint**: Generated executables are
|
|
16
|
+
- **Microscopic Footprint**: Generated executables are tiny and start in milliseconds.
|
|
15
17
|
- **Single-Binary Deployment**: Static assets (HTML, CSS, JS, images) are automatically minified and embedded directly into the executable as C byte arrays.
|
|
16
18
|
- **Native Performance**: Uses `kqueue` (macOS) or `epoll` (Linux) event loops for high-performance non-blocking I/O.
|
|
17
|
-
- **File-Based Routing**: Intuitive `
|
|
19
|
+
- **File-Based Routing**: Intuitive `routes/` directory structure, supporting dynamic segments (e.g., `/item/[id].js`).
|
|
20
|
+
|
|
21
|
+
## Benchmarks (Autocannon)
|
|
22
|
+
|
|
23
|
+
Local loopback runs against `localhost`, 20s per run.
|
|
24
|
+
|
|
25
|
+
Note: timeouts only appear at the highest concurrency (240 connections). On a single machine, autocannon and the server compete for CPU and kernel resources; the timeouts are likely client/loopback saturation rather than server errors.
|
|
26
|
+
|
|
27
|
+
| Connections | Pipelining | Avg req/s | Avg latency | p99 latency | Total read | Errors (timeouts) |
|
|
28
|
+
| ----------- | ---------- | --------- | ----------- | ----------- | ---------- | ----------------- |
|
|
29
|
+
| 60 | 1 | 123,005 | 0.01 ms | 0 ms | 21.0 GB | 0 |
|
|
30
|
+
| 60 | 10 | 125,973 | 4.26 ms | 10 ms | 21.5 GB | 0 |
|
|
31
|
+
| 120 | 1 | 124,214 | 0.15 ms | 1 ms | 21.2 GB | 0 |
|
|
32
|
+
| 120 | 10 | 131,245 | 8.64 ms | 15 ms | 22.4 GB | 0 |
|
|
33
|
+
| 240 | 1 | 124,890 | 0.54 ms | 1 ms | 21.3 GB | 118 (timeout) |
|
|
34
|
+
| 240 | 10 | 123,677 | 9.87 ms | 14 ms | 21.1 GB | 1540 (timeout) |
|
|
18
35
|
|
|
19
36
|
## Getting Started
|
|
20
37
|
|
|
@@ -40,9 +57,9 @@ cerver run
|
|
|
40
57
|
|
|
41
58
|
## Routing
|
|
42
59
|
|
|
43
|
-
Routes are defined in the `
|
|
60
|
+
Routes are defined in the `routes/` directory.
|
|
44
61
|
|
|
45
|
-
`
|
|
62
|
+
`routes/index.js` (maps to `/`)
|
|
46
63
|
|
|
47
64
|
```javascript
|
|
48
65
|
export function GET(req, res) {
|
|
@@ -50,7 +67,7 @@ export function GET(req, res) {
|
|
|
50
67
|
}
|
|
51
68
|
```
|
|
52
69
|
|
|
53
|
-
`
|
|
70
|
+
`routes/api/status.js` (maps to `/api/status`)
|
|
54
71
|
|
|
55
72
|
```javascript
|
|
56
73
|
export function GET(req, res) {
|
|
@@ -58,7 +75,7 @@ export function GET(req, res) {
|
|
|
58
75
|
}
|
|
59
76
|
```
|
|
60
77
|
|
|
61
|
-
`
|
|
78
|
+
`routes/users/[id].js` (maps to `/users/:id`)
|
|
62
79
|
|
|
63
80
|
```javascript
|
|
64
81
|
export function GET(req, res) {
|
|
@@ -67,6 +84,16 @@ export function GET(req, res) {
|
|
|
67
84
|
}
|
|
68
85
|
```
|
|
69
86
|
|
|
87
|
+
### Clean URL Asset Mapping
|
|
88
|
+
|
|
89
|
+
Cerver has a special asset routing convention that prevents folder clutter:
|
|
90
|
+
- **Root Index**: `public/index.html` is automatically served at the root `/`.
|
|
91
|
+
- **Directory Indexing**: Nested `index.html` files inside directories (e.g. `public/page/index.html`) are **not** implicitly aliased to `/page` (they remain at `/page/index.html`).
|
|
92
|
+
- **Clean Folder Slugs**: If an HTML file has the exact same name as its parent directory, it is mapped to the clean directory URL. For example:
|
|
93
|
+
- `public/about/about.html` maps to `/about` (and `/about/`)
|
|
94
|
+
- `public/blog/posts/posts.html` maps to `/blog/posts` (and `/blog/posts/`)
|
|
95
|
+
- **Other Static Assets**: All other assets like CSS, JS, images, and non-directory-matching HTML files serve directly at their file paths (e.g., `public/about/style.css` maps to `/about/style.css`).
|
|
96
|
+
|
|
70
97
|
## The Request & Response Objects
|
|
71
98
|
|
|
72
99
|
Because Cerver compiles to C, the API surface is restricted.
|
package/lib/assets/embed.js
CHANGED
|
@@ -82,6 +82,8 @@ async function generateEmbeddedAssets(assets, shouldMinify, compression) {
|
|
|
82
82
|
const algo = compression || "none";
|
|
83
83
|
|
|
84
84
|
lines.push("/* Auto-generated embedded assets — do not edit */");
|
|
85
|
+
lines.push('#include "cerver.h"');
|
|
86
|
+
lines.push("#include <stddef.h>");
|
|
85
87
|
lines.push("");
|
|
86
88
|
|
|
87
89
|
const assetEntries = [];
|
|
@@ -155,14 +157,37 @@ async function generateEmbeddedAssets(assets, shouldMinify, compression) {
|
|
|
155
157
|
brLen,
|
|
156
158
|
});
|
|
157
159
|
|
|
158
|
-
/* Auto-alias:
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
160
|
+
/* Auto-alias rule:
|
|
161
|
+
1. /index.html → /
|
|
162
|
+
2. /path/to/name/name.html → /path/to/name
|
|
163
|
+
*/
|
|
164
|
+
let shouldAlias = false;
|
|
165
|
+
let aliasPath = null;
|
|
166
|
+
|
|
167
|
+
if (asset.servePath === "/index.html") {
|
|
168
|
+
shouldAlias = true;
|
|
169
|
+
aliasPath = "/";
|
|
170
|
+
} else if (asset.servePath.endsWith(".html")) {
|
|
171
|
+
const parts = asset.servePath.split("/");
|
|
172
|
+
if (parts.length >= 3) {
|
|
173
|
+
const fileNameWithExt = parts[parts.length - 1];
|
|
174
|
+
const parentDir = parts[parts.length - 2];
|
|
175
|
+
const baseName = fileNameWithExt.slice(0, -5); // Remove ".html"
|
|
176
|
+
if (baseName === parentDir) {
|
|
177
|
+
shouldAlias = true;
|
|
178
|
+
const lengthToRemove = fileNameWithExt.length + 1; // filename plus the slash before it
|
|
179
|
+
aliasPath = asset.servePath.slice(0, -lengthToRemove);
|
|
180
|
+
if (aliasPath === "") {
|
|
181
|
+
aliasPath = "/";
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (shouldAlias && aliasPath !== null) {
|
|
163
188
|
assetEntries.push({
|
|
164
189
|
name,
|
|
165
|
-
servePath:
|
|
190
|
+
servePath: aliasPath,
|
|
166
191
|
mime,
|
|
167
192
|
gzName,
|
|
168
193
|
gzLen,
|
|
@@ -173,7 +198,7 @@ async function generateEmbeddedAssets(assets, shouldMinify, compression) {
|
|
|
173
198
|
}
|
|
174
199
|
|
|
175
200
|
/* Generate the asset table */
|
|
176
|
-
lines.push("
|
|
201
|
+
lines.push("cerver_asset_t cerver_embedded_assets[] = {");
|
|
177
202
|
for (const entry of assetEntries) {
|
|
178
203
|
lines.push(
|
|
179
204
|
` { "${entry.servePath}", "${entry.mime}", ` +
|
|
@@ -185,7 +210,7 @@ async function generateEmbeddedAssets(assets, shouldMinify, compression) {
|
|
|
185
210
|
}
|
|
186
211
|
lines.push("};");
|
|
187
212
|
lines.push(
|
|
188
|
-
`
|
|
213
|
+
`const int cerver_embedded_asset_count = ${assetEntries.length};`
|
|
189
214
|
);
|
|
190
215
|
|
|
191
216
|
return lines.join("\n");
|
|
@@ -40,7 +40,7 @@ function generateDispatch(routes) {
|
|
|
40
40
|
|
|
41
41
|
/* Generate the dispatch function */
|
|
42
42
|
lines.push(
|
|
43
|
-
"
|
|
43
|
+
"cerver_handler_fn cerver_generated_dispatch(cerver_request_t *req) {"
|
|
44
44
|
);
|
|
45
45
|
lines.push(" const char *path = req->path;");
|
|
46
46
|
lines.push(" size_t path_len = 0;");
|
|
@@ -162,6 +162,9 @@ function generateDispatch(routes) {
|
|
|
162
162
|
lines.push(
|
|
163
163
|
` req->params[req->params_count].value = seg${i}_start;`
|
|
164
164
|
);
|
|
165
|
+
lines.push(
|
|
166
|
+
` ((char*)seg${i}_start)[seg${i}_len] = '\\0';`
|
|
167
|
+
);
|
|
165
168
|
lines.push(` req->params_count++;`);
|
|
166
169
|
paramIdx++;
|
|
167
170
|
}
|
package/lib/codegen/generator.js
CHANGED
|
@@ -5,33 +5,36 @@ const { generateRouteTable } = require("./route_table");
|
|
|
5
5
|
const { generateDispatch } = require("./dispatch_gen");
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
|
-
* Generate
|
|
8
|
+
* Generate server.c content.
|
|
9
9
|
*
|
|
10
|
-
* @param {
|
|
11
|
-
* @param {
|
|
12
|
-
* @
|
|
13
|
-
* @returns {string} - Complete C source file
|
|
10
|
+
* @param {object} config - Build config (port, threads, etc.)
|
|
11
|
+
* @param {boolean} hasAssets - Whether embedding assets is enabled
|
|
12
|
+
* @returns {string} - C source for server.c
|
|
14
13
|
*/
|
|
15
|
-
function
|
|
14
|
+
function generateServerC(config, hasAssets) {
|
|
16
15
|
const lines = [];
|
|
17
16
|
|
|
18
|
-
/* ---- Header ---- */
|
|
19
17
|
lines.push("/*");
|
|
20
18
|
lines.push(" * Auto-generated by cerver — do not edit.");
|
|
21
19
|
lines.push(" */");
|
|
22
20
|
lines.push("");
|
|
23
21
|
lines.push('#include "cerver.h"');
|
|
24
|
-
lines.push("");
|
|
25
22
|
lines.push("#include <stdio.h>");
|
|
26
23
|
lines.push("#include <stdlib.h>");
|
|
27
|
-
lines.push("#include <string.h>");
|
|
28
24
|
lines.push("");
|
|
29
25
|
|
|
30
|
-
/*
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
26
|
+
/* Routing externs */
|
|
27
|
+
lines.push("/* Routing and Handler declarations */");
|
|
28
|
+
lines.push("extern cerver_route_t cerver_routes[];");
|
|
29
|
+
lines.push("extern const int cerver_route_count;");
|
|
30
|
+
lines.push("extern cerver_handler_fn cerver_generated_dispatch(cerver_request_t *req);");
|
|
31
|
+
lines.push("");
|
|
32
|
+
|
|
33
|
+
/* Asset declarations */
|
|
34
|
+
if (hasAssets) {
|
|
35
|
+
lines.push("/* Embedded Assets */");
|
|
36
|
+
lines.push("extern cerver_asset_t cerver_embedded_assets[];");
|
|
37
|
+
lines.push("extern const int cerver_embedded_asset_count;");
|
|
35
38
|
lines.push("");
|
|
36
39
|
} else {
|
|
37
40
|
lines.push("/* No embedded assets */");
|
|
@@ -40,6 +43,63 @@ function generateServer(routes, config, assetsCode) {
|
|
|
40
43
|
lines.push("");
|
|
41
44
|
}
|
|
42
45
|
|
|
46
|
+
/* Main function */
|
|
47
|
+
lines.push("/* ---- Entry Point ---- */");
|
|
48
|
+
lines.push("");
|
|
49
|
+
lines.push("int main(int argc, char *argv[]) {");
|
|
50
|
+
lines.push(" (void)argc;");
|
|
51
|
+
lines.push(" (void)argv;");
|
|
52
|
+
lines.push("");
|
|
53
|
+
lines.push(` int port = ${config.port};`);
|
|
54
|
+
lines.push("");
|
|
55
|
+
lines.push(" /* Allow PORT env override */");
|
|
56
|
+
lines.push(' const char *env_port = getenv("CERVER_PORT");');
|
|
57
|
+
lines.push(' if (!env_port) env_port = getenv("PORT");');
|
|
58
|
+
lines.push(" if (env_port) port = atoi(env_port);");
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push(" cerver_server_t srv;");
|
|
61
|
+
lines.push(` cerver_init(&srv, port, ${config.threads});`);
|
|
62
|
+
lines.push("");
|
|
63
|
+
lines.push(
|
|
64
|
+
" cerver_add_routes(&srv, cerver_routes, cerver_route_count);"
|
|
65
|
+
);
|
|
66
|
+
lines.push("");
|
|
67
|
+
lines.push(" /* Use generated compile-time dispatch for fast routing */");
|
|
68
|
+
lines.push(" cerver_set_dispatch(&srv, cerver_generated_dispatch);");
|
|
69
|
+
lines.push("");
|
|
70
|
+
if (hasAssets) {
|
|
71
|
+
lines.push(
|
|
72
|
+
" cerver_set_assets(&srv, cerver_embedded_assets, cerver_embedded_asset_count);"
|
|
73
|
+
);
|
|
74
|
+
} else {
|
|
75
|
+
lines.push(' cerver_set_public_dir(&srv, "./public");');
|
|
76
|
+
}
|
|
77
|
+
lines.push("");
|
|
78
|
+
lines.push(" return cerver_listen(&srv);");
|
|
79
|
+
lines.push("}");
|
|
80
|
+
lines.push("");
|
|
81
|
+
|
|
82
|
+
return lines.join("\n");
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Generate routes.c content.
|
|
87
|
+
*
|
|
88
|
+
* @param {IRRoute[]} routes - All IR routes
|
|
89
|
+
* @returns {string} - C source for routes.c
|
|
90
|
+
*/
|
|
91
|
+
function generateRoutesC(routes) {
|
|
92
|
+
const lines = [];
|
|
93
|
+
|
|
94
|
+
lines.push("/*");
|
|
95
|
+
lines.push(" * Auto-generated route handler logic — do not edit.");
|
|
96
|
+
lines.push(" */");
|
|
97
|
+
lines.push("");
|
|
98
|
+
lines.push('#include "cerver.h"');
|
|
99
|
+
lines.push("#include <string.h>");
|
|
100
|
+
lines.push("#include <stdlib.h>");
|
|
101
|
+
lines.push("");
|
|
102
|
+
|
|
43
103
|
/* ---- Route table (forward decls + table) ---- */
|
|
44
104
|
lines.push("/* ---- Routes ---- */");
|
|
45
105
|
lines.push("");
|
|
@@ -76,47 +136,22 @@ function generateServer(routes, config, assetsCode) {
|
|
|
76
136
|
lines.push("");
|
|
77
137
|
lines.push(generateDispatch(routes));
|
|
78
138
|
|
|
79
|
-
/* ---- Main function ---- */
|
|
80
|
-
lines.push("/* ---- Entry Point ---- */");
|
|
81
|
-
lines.push("");
|
|
82
|
-
lines.push("int main(int argc, char *argv[]) {");
|
|
83
|
-
lines.push(" (void)argc;");
|
|
84
|
-
lines.push(" (void)argv;");
|
|
85
|
-
lines.push("");
|
|
86
|
-
lines.push(` int port = ${config.port};`);
|
|
87
|
-
lines.push("");
|
|
88
|
-
lines.push(" /* Allow PORT env override */");
|
|
89
|
-
lines.push(' const char *env_port = getenv("CERVER_PORT");');
|
|
90
|
-
lines.push(' if (!env_port) env_port = getenv("PORT");');
|
|
91
|
-
lines.push(" if (env_port) port = atoi(env_port);");
|
|
92
|
-
lines.push("");
|
|
93
|
-
lines.push(" cerver_server_t srv;");
|
|
94
|
-
lines.push(` cerver_init(&srv, port, ${config.threads});`);
|
|
95
|
-
lines.push("");
|
|
96
|
-
lines.push(
|
|
97
|
-
" cerver_add_routes(&srv, cerver_routes, cerver_route_count);"
|
|
98
|
-
);
|
|
99
|
-
lines.push("");
|
|
100
|
-
|
|
101
|
-
/* Wire up generated dispatch */
|
|
102
|
-
lines.push(" /* Use generated compile-time dispatch for fast routing */");
|
|
103
|
-
lines.push(" cerver_set_dispatch(&srv, cerver_generated_dispatch);");
|
|
104
|
-
lines.push("");
|
|
105
|
-
|
|
106
|
-
if (assetsCode) {
|
|
107
|
-
lines.push(
|
|
108
|
-
" cerver_set_assets(&srv, cerver_embedded_assets, cerver_embedded_asset_count);"
|
|
109
|
-
);
|
|
110
|
-
} else {
|
|
111
|
-
lines.push(' cerver_set_public_dir(&srv, "./public");');
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
lines.push("");
|
|
115
|
-
lines.push(" return cerver_listen(&srv);");
|
|
116
|
-
lines.push("}");
|
|
117
|
-
lines.push("");
|
|
118
|
-
|
|
119
139
|
return lines.join("\n");
|
|
120
140
|
}
|
|
121
141
|
|
|
122
|
-
|
|
142
|
+
/**
|
|
143
|
+
* Generate the complete C server source structures from IR routes.
|
|
144
|
+
*
|
|
145
|
+
* @param {IRRoute[]} routes - All IR routes
|
|
146
|
+
* @param {object} config - Build config (port, embed, etc.)
|
|
147
|
+
* @param {boolean} hasAssets - Whether embedding assets is enabled
|
|
148
|
+
* @returns {{ serverC: string, routesC: string }} - Generated files object
|
|
149
|
+
*/
|
|
150
|
+
function generateServer(routes, config, hasAssets) {
|
|
151
|
+
return {
|
|
152
|
+
serverC: generateServerC(config, hasAssets),
|
|
153
|
+
routesC: generateRoutesC(routes),
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
module.exports = { generateServer, generateServerC, generateRoutesC };
|
|
@@ -25,7 +25,7 @@ function generateRouteTable(routes) {
|
|
|
25
25
|
lines.push("");
|
|
26
26
|
|
|
27
27
|
/* Route table array */
|
|
28
|
-
lines.push("
|
|
28
|
+
lines.push("cerver_route_t cerver_routes[] = {");
|
|
29
29
|
for (const route of routes) {
|
|
30
30
|
const name = handlerName(route.method, route.urlPath);
|
|
31
31
|
lines.push(
|
|
@@ -34,7 +34,7 @@ function generateRouteTable(routes) {
|
|
|
34
34
|
}
|
|
35
35
|
lines.push("};");
|
|
36
36
|
lines.push(
|
|
37
|
-
`
|
|
37
|
+
`const int cerver_route_count = ${routes.length};`
|
|
38
38
|
);
|
|
39
39
|
lines.push("");
|
|
40
40
|
|
package/lib/commands/build.js
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
const fs = require("fs");
|
|
4
4
|
const path = require("path");
|
|
5
5
|
|
|
6
|
-
const { loadConfig } = require("../config");
|
|
6
|
+
const { loadConfig, findProjectRoot } = require("../config");
|
|
7
7
|
const { discoverRoutes } = require("../parser/discover");
|
|
8
8
|
const { parseFile } = require("../parser/parse");
|
|
9
9
|
const { validate } = require("../validator/validate");
|
|
@@ -17,7 +17,13 @@ const { compile: compileC } = require("../compiler/compile");
|
|
|
17
17
|
* Full build pipeline: parse → validate → IR → codegen → compile
|
|
18
18
|
*/
|
|
19
19
|
async function build(opts) {
|
|
20
|
-
const projectDir =
|
|
20
|
+
const projectDir = findProjectRoot();
|
|
21
|
+
if (!projectDir) {
|
|
22
|
+
console.log("Not a cerver project");
|
|
23
|
+
process.exit(1);
|
|
24
|
+
}
|
|
25
|
+
process.chdir(projectDir);
|
|
26
|
+
|
|
21
27
|
const startTime = Date.now();
|
|
22
28
|
|
|
23
29
|
console.log("\n cerver build\n");
|
|
@@ -35,12 +41,15 @@ async function build(opts) {
|
|
|
35
41
|
);
|
|
36
42
|
|
|
37
43
|
/* ---- 2. Discover routes ---- */
|
|
38
|
-
|
|
44
|
+
let routesDir = path.join(projectDir, "routes");
|
|
45
|
+
if (!fs.existsSync(routesDir)) {
|
|
46
|
+
routesDir = path.join(projectDir, "app", "routes");
|
|
47
|
+
}
|
|
39
48
|
console.log(" → discovering routes...");
|
|
40
49
|
const routeFiles = discoverRoutes(routesDir);
|
|
41
50
|
|
|
42
51
|
if (routeFiles.length === 0) {
|
|
43
|
-
console.warn(
|
|
52
|
+
console.warn(` ⚠ no route files found in ${path.relative(projectDir, routesDir)}/`);
|
|
44
53
|
} else {
|
|
45
54
|
for (const r of routeFiles) {
|
|
46
55
|
console.log(` ${r.urlPath} ← ${path.relative(projectDir, r.filePath)}`);
|
|
@@ -81,7 +90,7 @@ async function build(opts) {
|
|
|
81
90
|
assetsCode = await generateEmbeddedAssets(assets, config.minify, config.compression);
|
|
82
91
|
console.log(
|
|
83
92
|
` ${assets.length} asset(s), ${(totalSize / 1024).toFixed(1)} KB total` +
|
|
84
|
-
|
|
93
|
+
(config.compression !== "none" ? ` (${config.compression} compressed)` : "")
|
|
85
94
|
);
|
|
86
95
|
} else {
|
|
87
96
|
console.log(" no assets found in public/");
|
|
@@ -90,15 +99,29 @@ async function build(opts) {
|
|
|
90
99
|
|
|
91
100
|
/* ---- 5. Generate C source ---- */
|
|
92
101
|
console.log(" → generating C source...");
|
|
93
|
-
const
|
|
102
|
+
const { serverC, routesC } = generateServer(allRoutes, config, !!assetsCode);
|
|
94
103
|
|
|
95
104
|
/* Write to dist/ */
|
|
96
105
|
const distDir = path.join(projectDir, "dist");
|
|
97
106
|
fs.mkdirSync(distDir, { recursive: true });
|
|
98
107
|
|
|
99
108
|
const serverCPath = path.join(distDir, "server.c");
|
|
100
|
-
fs.writeFileSync(serverCPath,
|
|
101
|
-
console.log(` wrote ${(
|
|
109
|
+
fs.writeFileSync(serverCPath, serverC);
|
|
110
|
+
console.log(` wrote ${(serverC.length / 1024).toFixed(1)} KB → dist/server.c`);
|
|
111
|
+
|
|
112
|
+
const routesCPath = path.join(distDir, "routes.c");
|
|
113
|
+
fs.writeFileSync(routesCPath, routesC);
|
|
114
|
+
console.log(` wrote ${(routesC.length / 1024).toFixed(1)} KB → dist/routes.c`);
|
|
115
|
+
|
|
116
|
+
const assetsCPath = path.join(distDir, "assets.c");
|
|
117
|
+
if (assetsCode) {
|
|
118
|
+
fs.writeFileSync(assetsCPath, assetsCode);
|
|
119
|
+
console.log(` wrote ${(assetsCode.length / 1024).toFixed(1)} KB → dist/assets.c`);
|
|
120
|
+
} else {
|
|
121
|
+
if (fs.existsSync(assetsCPath)) {
|
|
122
|
+
fs.unlinkSync(assetsCPath);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
102
125
|
|
|
103
126
|
/* ---- 6. Copy runtime headers ---- */
|
|
104
127
|
const runtimeDir = path.join(__dirname, "..", "..", "runtime");
|
package/lib/commands/dev.js
CHANGED
|
@@ -5,12 +5,19 @@ const path = require("path");
|
|
|
5
5
|
const { spawn } = require("child_process");
|
|
6
6
|
const chokidar = require("chokidar");
|
|
7
7
|
const { build } = require("./build");
|
|
8
|
+
const { findProjectRoot } = require("../config");
|
|
8
9
|
|
|
9
10
|
/**
|
|
10
11
|
* Dev mode: watch for changes, auto-rebuild, auto-restart.
|
|
11
12
|
*/
|
|
12
13
|
async function dev(opts) {
|
|
13
|
-
const projectDir =
|
|
14
|
+
const projectDir = findProjectRoot();
|
|
15
|
+
if (!projectDir) {
|
|
16
|
+
console.log("Not a cerver project");
|
|
17
|
+
process.exit(1);
|
|
18
|
+
}
|
|
19
|
+
process.chdir(projectDir);
|
|
20
|
+
|
|
14
21
|
const binaryPath = path.join(projectDir, "dist", "server");
|
|
15
22
|
|
|
16
23
|
let serverProcess = null;
|
|
@@ -97,7 +104,7 @@ async function dev(opts) {
|
|
|
97
104
|
|
|
98
105
|
/* ---- Watch for changes ---- */
|
|
99
106
|
const watchPaths = [
|
|
100
|
-
path.join(projectDir, "
|
|
107
|
+
path.join(projectDir, "routes"),
|
|
101
108
|
path.join(projectDir, "public"),
|
|
102
109
|
path.join(projectDir, "cerver.config.js"),
|
|
103
110
|
].filter((p) => fs.existsSync(p));
|