@velox0/cerver 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/README.md +111 -0
- package/bin/cerver.js +44 -0
- package/lib/assets/discover.js +50 -0
- package/lib/assets/embed.js +124 -0
- package/lib/assets/minify.js +83 -0
- package/lib/codegen/emit.js +249 -0
- package/lib/codegen/generator.js +111 -0
- package/lib/codegen/route_table.js +44 -0
- package/lib/commands/build.js +122 -0
- package/lib/commands/new.js +96 -0
- package/lib/commands/run.js +47 -0
- package/lib/compiler/compile.js +91 -0
- package/lib/config.js +52 -0
- package/lib/ir/transform.js +335 -0
- package/lib/ir/types.js +204 -0
- package/lib/parser/discover.js +74 -0
- package/lib/parser/parse.js +50 -0
- package/lib/validator/validate.js +179 -0
- package/package.json +28 -0
- package/runtime/cerver.h +185 -0
- package/runtime/http_parser.c +195 -0
- package/runtime/http_writer.c +137 -0
- package/runtime/mime.c +84 -0
- package/runtime/router.c +150 -0
- package/runtime/server.c +344 -0
- package/runtime/static.c +145 -0
- package/templates/cerver.config.js +6 -0
- package/templates/index.route.js +3 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { handlerName, emitStatement } = require("./emit");
|
|
4
|
+
const { generateRouteTable } = require("./route_table");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Generate the complete C server source file from IR routes.
|
|
8
|
+
*
|
|
9
|
+
* @param {IRRoute[]} routes - All IR routes
|
|
10
|
+
* @param {object} config - Build config (port, embed, etc.)
|
|
11
|
+
* @param {string|null} assetsCode - Generated embedded assets C code, or null
|
|
12
|
+
* @returns {string} - Complete C source file
|
|
13
|
+
*/
|
|
14
|
+
function generateServer(routes, config, assetsCode) {
|
|
15
|
+
const lines = [];
|
|
16
|
+
|
|
17
|
+
/* ---- Header ---- */
|
|
18
|
+
lines.push("/*");
|
|
19
|
+
lines.push(" * Auto-generated by cerver — do not edit.");
|
|
20
|
+
lines.push(" */");
|
|
21
|
+
lines.push("");
|
|
22
|
+
lines.push('#include "cerver.h"');
|
|
23
|
+
lines.push("");
|
|
24
|
+
lines.push("#include <stdio.h>");
|
|
25
|
+
lines.push("#include <stdlib.h>");
|
|
26
|
+
lines.push("#include <string.h>");
|
|
27
|
+
lines.push("");
|
|
28
|
+
|
|
29
|
+
/* ---- Embedded assets (if any) ---- */
|
|
30
|
+
if (assetsCode) {
|
|
31
|
+
lines.push("/* ---- Embedded Assets ---- */");
|
|
32
|
+
lines.push("");
|
|
33
|
+
lines.push(assetsCode);
|
|
34
|
+
lines.push("");
|
|
35
|
+
} else {
|
|
36
|
+
lines.push("/* No embedded assets */");
|
|
37
|
+
lines.push("static cerver_asset_t *cerver_embedded_assets = NULL;");
|
|
38
|
+
lines.push("static const int cerver_embedded_asset_count = 0;");
|
|
39
|
+
lines.push("");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/* ---- Route table (forward decls + table) ---- */
|
|
43
|
+
lines.push("/* ---- Routes ---- */");
|
|
44
|
+
lines.push("");
|
|
45
|
+
lines.push(generateRouteTable(routes));
|
|
46
|
+
|
|
47
|
+
/* ---- Handler implementations ---- */
|
|
48
|
+
lines.push("/* ---- Handler Implementations ---- */");
|
|
49
|
+
lines.push("");
|
|
50
|
+
|
|
51
|
+
for (const route of routes) {
|
|
52
|
+
const name = handlerName(route.method, route.urlPath);
|
|
53
|
+
lines.push(
|
|
54
|
+
`static void ${name}(cerver_request_t *req, cerver_response_t *res) {`
|
|
55
|
+
);
|
|
56
|
+
|
|
57
|
+
/* Emit local variables */
|
|
58
|
+
for (const v of route.handler.variables) {
|
|
59
|
+
const varLines = emitStatement(v, 1);
|
|
60
|
+
lines.push(...varLines);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/* Emit body statements */
|
|
64
|
+
for (const stmt of route.handler.body) {
|
|
65
|
+
const stmtLines = emitStatement(stmt, 1);
|
|
66
|
+
lines.push(...stmtLines);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
lines.push("}");
|
|
70
|
+
lines.push("");
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/* ---- Main function ---- */
|
|
74
|
+
lines.push("/* ---- Entry Point ---- */");
|
|
75
|
+
lines.push("");
|
|
76
|
+
lines.push("int main(int argc, char *argv[]) {");
|
|
77
|
+
lines.push(" (void)argc;");
|
|
78
|
+
lines.push(" (void)argv;");
|
|
79
|
+
lines.push("");
|
|
80
|
+
lines.push(` int port = ${config.port};`);
|
|
81
|
+
lines.push("");
|
|
82
|
+
lines.push(" /* Allow PORT env override */");
|
|
83
|
+
lines.push(' const char *env_port = getenv("CERVER_PORT");');
|
|
84
|
+
lines.push(" if (!env_port) env_port = getenv(\"PORT\");");
|
|
85
|
+
lines.push(" if (env_port) port = atoi(env_port);");
|
|
86
|
+
lines.push("");
|
|
87
|
+
lines.push(" cerver_server_t srv;");
|
|
88
|
+
lines.push(" cerver_init(&srv, port);");
|
|
89
|
+
lines.push("");
|
|
90
|
+
lines.push(
|
|
91
|
+
" cerver_add_routes(&srv, cerver_routes, cerver_route_count);"
|
|
92
|
+
);
|
|
93
|
+
lines.push("");
|
|
94
|
+
|
|
95
|
+
if (assetsCode) {
|
|
96
|
+
lines.push(
|
|
97
|
+
" cerver_set_assets(&srv, cerver_embedded_assets, cerver_embedded_asset_count);"
|
|
98
|
+
);
|
|
99
|
+
} else {
|
|
100
|
+
lines.push(' cerver_set_public_dir(&srv, "./public");');
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
lines.push("");
|
|
104
|
+
lines.push(" return cerver_listen(&srv);");
|
|
105
|
+
lines.push("}");
|
|
106
|
+
lines.push("");
|
|
107
|
+
|
|
108
|
+
return lines.join("\n");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
module.exports = { generateServer };
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { cString, handlerName } = require("./emit");
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Generate the static route dispatch table as C source.
|
|
7
|
+
*
|
|
8
|
+
* @param {IRRoute[]} routes - All routes to include
|
|
9
|
+
* @returns {string} - C source for the route table
|
|
10
|
+
*/
|
|
11
|
+
function generateRouteTable(routes) {
|
|
12
|
+
const lines = [];
|
|
13
|
+
|
|
14
|
+
lines.push("/* Auto-generated route table — do not edit */");
|
|
15
|
+
lines.push("");
|
|
16
|
+
|
|
17
|
+
/* Forward declarations */
|
|
18
|
+
for (const route of routes) {
|
|
19
|
+
const name = handlerName(route.method, route.urlPath);
|
|
20
|
+
lines.push(
|
|
21
|
+
`static void ${name}(cerver_request_t *req, cerver_response_t *res);`
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
lines.push("");
|
|
26
|
+
|
|
27
|
+
/* Route table array */
|
|
28
|
+
lines.push("static cerver_route_t cerver_routes[] = {");
|
|
29
|
+
for (const route of routes) {
|
|
30
|
+
const name = handlerName(route.method, route.urlPath);
|
|
31
|
+
lines.push(
|
|
32
|
+
` { ${cString(route.method)}, ${cString(route.urlPath)}, ${name} },`
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
lines.push("};");
|
|
36
|
+
lines.push(
|
|
37
|
+
`static const int cerver_route_count = ${routes.length};`
|
|
38
|
+
);
|
|
39
|
+
lines.push("");
|
|
40
|
+
|
|
41
|
+
return lines.join("\n");
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
module.exports = { generateRouteTable };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const { loadConfig } = require("../config");
|
|
7
|
+
const { discoverRoutes } = require("../parser/discover");
|
|
8
|
+
const { parseFile } = require("../parser/parse");
|
|
9
|
+
const { validate } = require("../validator/validate");
|
|
10
|
+
const { transformFile } = require("../ir/transform");
|
|
11
|
+
const { generateServer } = require("../codegen/generator");
|
|
12
|
+
const { discoverAssets } = require("../assets/discover");
|
|
13
|
+
const { generateEmbeddedAssets } = require("../assets/embed");
|
|
14
|
+
const { compile: compileC } = require("../compiler/compile");
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Full build pipeline: parse → validate → IR → codegen → compile
|
|
18
|
+
*/
|
|
19
|
+
async function build(opts) {
|
|
20
|
+
const projectDir = process.cwd();
|
|
21
|
+
const startTime = Date.now();
|
|
22
|
+
|
|
23
|
+
console.log("\n cerver build\n");
|
|
24
|
+
|
|
25
|
+
/* ---- 1. Load config ---- */
|
|
26
|
+
console.log(" → loading config...");
|
|
27
|
+
const config = loadConfig(projectDir);
|
|
28
|
+
|
|
29
|
+
/* CLI overrides */
|
|
30
|
+
if (opts.embed !== undefined) config.embed = opts.embed;
|
|
31
|
+
if (opts.minify === false) config.minify = false;
|
|
32
|
+
|
|
33
|
+
console.log(
|
|
34
|
+
` port: ${config.port}, embed: ${config.embed}, minify: ${config.minify}`
|
|
35
|
+
);
|
|
36
|
+
|
|
37
|
+
/* ---- 2. Discover routes ---- */
|
|
38
|
+
const routesDir = path.join(projectDir, "app", "routes");
|
|
39
|
+
console.log(" → discovering routes...");
|
|
40
|
+
const routeFiles = discoverRoutes(routesDir);
|
|
41
|
+
|
|
42
|
+
if (routeFiles.length === 0) {
|
|
43
|
+
console.warn(" ⚠ no route files found in app/routes/");
|
|
44
|
+
} else {
|
|
45
|
+
for (const r of routeFiles) {
|
|
46
|
+
console.log(` ${r.urlPath} ← ${path.relative(projectDir, r.filePath)}`);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/* ---- 3. Parse & validate ---- */
|
|
51
|
+
console.log(" → parsing and validating...");
|
|
52
|
+
const allRoutes = [];
|
|
53
|
+
|
|
54
|
+
for (const routeFile of routeFiles) {
|
|
55
|
+
const { ast, source } = parseFile(routeFile.filePath);
|
|
56
|
+
validate(ast, routeFile.filePath, source);
|
|
57
|
+
|
|
58
|
+
const irRoutes = transformFile(ast, routeFile.urlPath);
|
|
59
|
+
allRoutes.push(...irRoutes);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
console.log(` ${allRoutes.length} handler(s) compiled`);
|
|
63
|
+
|
|
64
|
+
/* ---- 4. Asset pipeline ---- */
|
|
65
|
+
let assetsCode = null;
|
|
66
|
+
|
|
67
|
+
if (config.embed) {
|
|
68
|
+
const publicDir = path.join(projectDir, "public");
|
|
69
|
+
console.log(" → embedding assets...");
|
|
70
|
+
const assets = discoverAssets(publicDir);
|
|
71
|
+
|
|
72
|
+
if (assets.length > 0) {
|
|
73
|
+
let totalSize = 0;
|
|
74
|
+
for (const a of assets) {
|
|
75
|
+
totalSize += a.size;
|
|
76
|
+
console.log(
|
|
77
|
+
` ${a.servePath} (${(a.size / 1024).toFixed(1)} KB)`
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
assetsCode = await generateEmbeddedAssets(assets, config.minify);
|
|
82
|
+
console.log(
|
|
83
|
+
` ${assets.length} asset(s), ${(totalSize / 1024).toFixed(1)} KB total`
|
|
84
|
+
);
|
|
85
|
+
} else {
|
|
86
|
+
console.log(" no assets found in public/");
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/* ---- 5. Generate C source ---- */
|
|
91
|
+
console.log(" → generating C source...");
|
|
92
|
+
const cSource = generateServer(allRoutes, config, assetsCode);
|
|
93
|
+
|
|
94
|
+
/* Write to dist/ */
|
|
95
|
+
const distDir = path.join(projectDir, "dist");
|
|
96
|
+
fs.mkdirSync(distDir, { recursive: true });
|
|
97
|
+
|
|
98
|
+
const serverCPath = path.join(distDir, "server.c");
|
|
99
|
+
fs.writeFileSync(serverCPath, cSource);
|
|
100
|
+
console.log(` wrote ${(cSource.length / 1024).toFixed(1)} KB → dist/server.c`);
|
|
101
|
+
|
|
102
|
+
/* ---- 6. Copy runtime headers ---- */
|
|
103
|
+
const runtimeDir = path.join(__dirname, "..", "..", "runtime");
|
|
104
|
+
|
|
105
|
+
/* Copy cerver.h to dist/ so the generated server.c can include it */
|
|
106
|
+
fs.copyFileSync(
|
|
107
|
+
path.join(runtimeDir, "cerver.h"),
|
|
108
|
+
path.join(distDir, "cerver.h")
|
|
109
|
+
);
|
|
110
|
+
|
|
111
|
+
/* ---- 7. Compile ---- */
|
|
112
|
+
console.log(" → compiling...");
|
|
113
|
+
const binaryPath = compileC(distDir, runtimeDir, { static: opts.static });
|
|
114
|
+
|
|
115
|
+
/* ---- Done ---- */
|
|
116
|
+
const elapsed = Date.now() - startTime;
|
|
117
|
+
console.log(`\n ✓ build complete in ${elapsed}ms`);
|
|
118
|
+
console.log(` binary: ${path.relative(projectDir, binaryPath)}`);
|
|
119
|
+
console.log(` run with: cerver run\n`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
module.exports = { build };
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Scaffold a new cerver project.
|
|
8
|
+
*/
|
|
9
|
+
function newProject(name) {
|
|
10
|
+
const projectDir = path.resolve(process.cwd(), name);
|
|
11
|
+
|
|
12
|
+
if (fs.existsSync(projectDir)) {
|
|
13
|
+
console.error(`cerver: directory "${name}" already exists`);
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
console.log(`\n Creating cerver project: ${name}\n`);
|
|
18
|
+
|
|
19
|
+
// Create directory structure
|
|
20
|
+
const dirs = [
|
|
21
|
+
"",
|
|
22
|
+
"app",
|
|
23
|
+
"app/routes",
|
|
24
|
+
"public",
|
|
25
|
+
"dist",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
for (const dir of dirs) {
|
|
29
|
+
fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Copy templates
|
|
33
|
+
const templatesDir = path.join(__dirname, "..", "..", "templates");
|
|
34
|
+
|
|
35
|
+
// cerver.config.js
|
|
36
|
+
fs.copyFileSync(
|
|
37
|
+
path.join(templatesDir, "cerver.config.js"),
|
|
38
|
+
path.join(projectDir, "cerver.config.js")
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
// Default route
|
|
42
|
+
fs.copyFileSync(
|
|
43
|
+
path.join(templatesDir, "index.route.js"),
|
|
44
|
+
path.join(projectDir, "app", "routes", "index.js")
|
|
45
|
+
);
|
|
46
|
+
|
|
47
|
+
// Default public/index.html
|
|
48
|
+
fs.writeFileSync(
|
|
49
|
+
path.join(projectDir, "public", "index.html"),
|
|
50
|
+
`<!doctype html>
|
|
51
|
+
<html lang="en">
|
|
52
|
+
<head>
|
|
53
|
+
<meta charset="utf-8">
|
|
54
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
55
|
+
<title>${name}</title>
|
|
56
|
+
</head>
|
|
57
|
+
<body>
|
|
58
|
+
<h1>${name}</h1>
|
|
59
|
+
<p>Served by cerver.</p>
|
|
60
|
+
</body>
|
|
61
|
+
</html>
|
|
62
|
+
`
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// package.json
|
|
66
|
+
fs.writeFileSync(
|
|
67
|
+
path.join(projectDir, "package.json"),
|
|
68
|
+
JSON.stringify(
|
|
69
|
+
{
|
|
70
|
+
name: name,
|
|
71
|
+
version: "0.1.0",
|
|
72
|
+
private: true,
|
|
73
|
+
scripts: {
|
|
74
|
+
build: "cerver build",
|
|
75
|
+
start: "cerver run",
|
|
76
|
+
},
|
|
77
|
+
},
|
|
78
|
+
null,
|
|
79
|
+
2
|
|
80
|
+
) + "\n"
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
console.log(" Created:");
|
|
84
|
+
console.log(" app/routes/index.js");
|
|
85
|
+
console.log(" public/index.html");
|
|
86
|
+
console.log(" cerver.config.js");
|
|
87
|
+
console.log(" package.json");
|
|
88
|
+
console.log("");
|
|
89
|
+
console.log(" Next steps:");
|
|
90
|
+
console.log(` cd ${name}`);
|
|
91
|
+
console.log(" cerver build");
|
|
92
|
+
console.log(" cerver run");
|
|
93
|
+
console.log("");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
module.exports = { newProject };
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFileSync } = require("child_process");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const fs = require("fs");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Run the compiled binary from dist/.
|
|
9
|
+
*/
|
|
10
|
+
function run(opts) {
|
|
11
|
+
const projectDir = process.cwd();
|
|
12
|
+
const binaryPath = path.join(projectDir, "dist", "server");
|
|
13
|
+
|
|
14
|
+
if (!fs.existsSync(binaryPath)) {
|
|
15
|
+
console.error("cerver: no compiled binary found. Run `cerver build` first.");
|
|
16
|
+
process.exit(1);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// Make sure it's executable
|
|
20
|
+
try {
|
|
21
|
+
fs.chmodSync(binaryPath, 0o755);
|
|
22
|
+
} catch (_) {
|
|
23
|
+
// Ignore — may already be executable
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
const env = { ...process.env };
|
|
27
|
+
if (opts.port) {
|
|
28
|
+
env.CERVER_PORT = opts.port;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
console.log(`cerver: starting server from ${binaryPath}`);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
execFileSync(binaryPath, [], {
|
|
35
|
+
stdio: "inherit",
|
|
36
|
+
env,
|
|
37
|
+
cwd: projectDir,
|
|
38
|
+
});
|
|
39
|
+
} catch (err) {
|
|
40
|
+
if (err.status !== null) {
|
|
41
|
+
process.exit(err.status);
|
|
42
|
+
}
|
|
43
|
+
throw err;
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
module.exports = { run };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const { execFileSync, execSync } = require("child_process");
|
|
4
|
+
const fs = require("fs");
|
|
5
|
+
const path = require("path");
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Detect available C compiler.
|
|
9
|
+
*/
|
|
10
|
+
function detectCompiler() {
|
|
11
|
+
const candidates = ["cc", "gcc", "clang"];
|
|
12
|
+
for (const cc of candidates) {
|
|
13
|
+
try {
|
|
14
|
+
execSync(`which ${cc}`, { stdio: "ignore" });
|
|
15
|
+
return cc;
|
|
16
|
+
} catch {
|
|
17
|
+
continue;
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
throw new Error(
|
|
21
|
+
"cerver: no C compiler found. Install gcc or clang."
|
|
22
|
+
);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compile the generated C source into a binary.
|
|
27
|
+
*
|
|
28
|
+
* @param {string} distDir - Output directory (dist/)
|
|
29
|
+
* @param {string} runtimeDir - Path to runtime/ C sources
|
|
30
|
+
* @param {object} opts - { static: bool }
|
|
31
|
+
*/
|
|
32
|
+
function compile(distDir, runtimeDir, opts) {
|
|
33
|
+
const cc = detectCompiler();
|
|
34
|
+
const serverC = path.join(distDir, "server.c");
|
|
35
|
+
const outputBin = path.join(distDir, "server");
|
|
36
|
+
|
|
37
|
+
if (!fs.existsSync(serverC)) {
|
|
38
|
+
throw new Error(
|
|
39
|
+
`cerver: generated source not found at ${serverC}`
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/* Collect all runtime .c files */
|
|
44
|
+
const runtimeSources = fs
|
|
45
|
+
.readdirSync(runtimeDir)
|
|
46
|
+
.filter((f) => f.endsWith(".c"))
|
|
47
|
+
.map((f) => path.join(runtimeDir, f));
|
|
48
|
+
|
|
49
|
+
/* Build the compiler command */
|
|
50
|
+
const args = [
|
|
51
|
+
"-O2",
|
|
52
|
+
"-Wall",
|
|
53
|
+
"-Wextra",
|
|
54
|
+
"-Wno-unused-parameter",
|
|
55
|
+
"-o",
|
|
56
|
+
outputBin,
|
|
57
|
+
serverC,
|
|
58
|
+
...runtimeSources,
|
|
59
|
+
`-I${runtimeDir}`,
|
|
60
|
+
];
|
|
61
|
+
|
|
62
|
+
if (opts && opts.static) {
|
|
63
|
+
args.push("-static");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
console.log(` compiling with ${cc}...`);
|
|
67
|
+
console.log(` ${cc} ${args.join(" ")}`);
|
|
68
|
+
|
|
69
|
+
const start = Date.now();
|
|
70
|
+
|
|
71
|
+
try {
|
|
72
|
+
execFileSync(cc, args, {
|
|
73
|
+
stdio: "inherit",
|
|
74
|
+
cwd: distDir,
|
|
75
|
+
});
|
|
76
|
+
} catch (err) {
|
|
77
|
+
throw new Error(
|
|
78
|
+
`cerver: compilation failed (exit code ${err.status})`
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const elapsed = Date.now() - start;
|
|
83
|
+
const stat = fs.statSync(outputBin);
|
|
84
|
+
const sizeKB = (stat.size / 1024).toFixed(1);
|
|
85
|
+
|
|
86
|
+
console.log(` compiled in ${elapsed}ms — ${sizeKB} KB`);
|
|
87
|
+
|
|
88
|
+
return outputBin;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { compile, detectCompiler };
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
const DEFAULTS = {
|
|
7
|
+
port: 8080,
|
|
8
|
+
embed: true,
|
|
9
|
+
minify: true,
|
|
10
|
+
compression: "none",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Load cerver.config.js from the project root.
|
|
15
|
+
* Falls back to defaults for any missing fields.
|
|
16
|
+
*/
|
|
17
|
+
function loadConfig(projectDir) {
|
|
18
|
+
const configPath = path.join(projectDir, "cerver.config.js");
|
|
19
|
+
let userConfig = {};
|
|
20
|
+
|
|
21
|
+
if (fs.existsSync(configPath)) {
|
|
22
|
+
// Read the config file and evaluate it
|
|
23
|
+
const raw = fs.readFileSync(configPath, "utf8");
|
|
24
|
+
|
|
25
|
+
// Support both `module.exports = { ... }` and `export default { ... }`
|
|
26
|
+
// We transpile the export default form to module.exports for eval
|
|
27
|
+
const normalized = raw.replace(
|
|
28
|
+
/export\s+default\s+/,
|
|
29
|
+
"module.exports = "
|
|
30
|
+
);
|
|
31
|
+
|
|
32
|
+
// Create a mini-module context to evaluate the config
|
|
33
|
+
const mod = { exports: {} };
|
|
34
|
+
const fn = new Function("module", "exports", normalized);
|
|
35
|
+
fn(mod, mod.exports);
|
|
36
|
+
userConfig = mod.exports;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const config = { ...DEFAULTS, ...userConfig };
|
|
40
|
+
|
|
41
|
+
// Validate
|
|
42
|
+
if (typeof config.port !== "number" || config.port < 1 || config.port > 65535) {
|
|
43
|
+
throw new Error(`cerver: invalid port ${config.port}`);
|
|
44
|
+
}
|
|
45
|
+
if (!["none", "gzip", "brotli"].includes(config.compression)) {
|
|
46
|
+
throw new Error(`cerver: unsupported compression "${config.compression}"`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
return config;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
module.exports = { loadConfig, DEFAULTS };
|