@velox0/cerver 0.4.1 → 0.4.2
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/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +3 -0
- package/Makefile +26 -0
- package/bin/cerver.js +11 -0
- package/lib/commands/dev.js +23 -9
- package/lib/commands/new.js +273 -79
- package/lib/ir/transform.js +14 -20
- package/package.json +2 -2
- package/runtime/cerver.h +46 -36
- package/runtime/http_parser.c +12 -11
- package/runtime/http_writer.c +24 -24
- package/runtime/mime.c +1 -0
- package/runtime/router.c +2 -1
- package/runtime/server.c +56 -55
- package/runtime/static.c +11 -11
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +414 -0
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
pull_request:
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
tests:
|
|
9
|
+
name: Tests
|
|
10
|
+
runs-on: ubuntu-latest
|
|
11
|
+
|
|
12
|
+
steps:
|
|
13
|
+
- name: Checkout
|
|
14
|
+
uses: actions/checkout@v6
|
|
15
|
+
|
|
16
|
+
- name: Setup pnpm
|
|
17
|
+
uses: pnpm/action-setup@v6
|
|
18
|
+
with:
|
|
19
|
+
version: 10
|
|
20
|
+
run_install: false
|
|
21
|
+
|
|
22
|
+
- name: Setup Node.js
|
|
23
|
+
uses: actions/setup-node@v6
|
|
24
|
+
with:
|
|
25
|
+
node-version: 24
|
|
26
|
+
cache: pnpm
|
|
27
|
+
|
|
28
|
+
- name: Install dependencies
|
|
29
|
+
run: pnpm install --frozen-lockfile
|
|
30
|
+
|
|
31
|
+
- name: Run runtime tests
|
|
32
|
+
run: make test-runtime
|
|
33
|
+
|
|
34
|
+
- name: Run JS tests
|
|
35
|
+
run: pnpm test
|
package/Makefile
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
CC ?= cc
|
|
2
|
+
CFLAGS ?= -std=c11 -Wall -Wextra -O2
|
|
3
|
+
|
|
4
|
+
RUNTIME_SRCS = \
|
|
5
|
+
runtime/http_parser.c \
|
|
6
|
+
runtime/http_writer.c \
|
|
7
|
+
runtime/router.c \
|
|
8
|
+
runtime/static.c \
|
|
9
|
+
runtime/mime.c \
|
|
10
|
+
runtime/server.c
|
|
11
|
+
|
|
12
|
+
TEST_SRCS = runtime/tests/runtime_tests.c \
|
|
13
|
+
runtime/tests/minunit.c
|
|
14
|
+
TEST_BIN = build/runtime_tests
|
|
15
|
+
|
|
16
|
+
.PHONY: test-runtime clean
|
|
17
|
+
|
|
18
|
+
test-runtime: $(TEST_BIN)
|
|
19
|
+
./$(TEST_BIN)
|
|
20
|
+
|
|
21
|
+
$(TEST_BIN): $(RUNTIME_SRCS) $(TEST_SRCS) runtime/cerver.h
|
|
22
|
+
mkdir -p build
|
|
23
|
+
$(CC) $(CFLAGS) -Iruntime -o $(TEST_BIN) $(RUNTIME_SRCS) $(TEST_SRCS) -pthread
|
|
24
|
+
|
|
25
|
+
clean:
|
|
26
|
+
rm -rf build
|
package/bin/cerver.js
CHANGED
|
@@ -7,6 +7,17 @@ const pkg = require("../package.json");
|
|
|
7
7
|
|
|
8
8
|
const program = new Command();
|
|
9
9
|
|
|
10
|
+
function restoreTty() {
|
|
11
|
+
const stdin = process.stdin;
|
|
12
|
+
if (!stdin || !stdin.isTTY || typeof stdin.setRawMode !== "function") return;
|
|
13
|
+
if (!stdin.isRaw) return;
|
|
14
|
+
try {
|
|
15
|
+
stdin.setRawMode(false);
|
|
16
|
+
} catch (_) {}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
process.on("exit", restoreTty);
|
|
20
|
+
|
|
10
21
|
program
|
|
11
22
|
.name("cerver")
|
|
12
23
|
.description("Compile restricted JavaScript into native C server binaries")
|
package/lib/commands/dev.js
CHANGED
|
@@ -16,6 +16,7 @@ async function dev(opts) {
|
|
|
16
16
|
let serverProcess = null;
|
|
17
17
|
let building = false;
|
|
18
18
|
let pendingRebuild = false;
|
|
19
|
+
let shuttingDown = false;
|
|
19
20
|
|
|
20
21
|
const port = opts.port || null;
|
|
21
22
|
|
|
@@ -40,7 +41,7 @@ async function dev(opts) {
|
|
|
40
41
|
try {
|
|
41
42
|
await build({
|
|
42
43
|
embed: opts.embed !== undefined ? opts.embed : true,
|
|
43
|
-
minify: false
|
|
44
|
+
minify: false /* Skip minification in dev for speed */,
|
|
44
45
|
static: false,
|
|
45
46
|
});
|
|
46
47
|
|
|
@@ -84,6 +85,16 @@ async function dev(opts) {
|
|
|
84
85
|
});
|
|
85
86
|
}
|
|
86
87
|
|
|
88
|
+
function waitForExit(proc) {
|
|
89
|
+
if (!proc) return Promise.resolve();
|
|
90
|
+
return new Promise((resolve) => {
|
|
91
|
+
const done = () => resolve();
|
|
92
|
+
proc.once("exit", done);
|
|
93
|
+
proc.once("close", done);
|
|
94
|
+
proc.once("error", done);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
87
98
|
/* ---- Watch for changes ---- */
|
|
88
99
|
const watchPaths = [
|
|
89
100
|
path.join(projectDir, "app"),
|
|
@@ -92,11 +103,7 @@ async function dev(opts) {
|
|
|
92
103
|
].filter((p) => fs.existsSync(p));
|
|
93
104
|
|
|
94
105
|
const watcher = chokidar.watch(watchPaths, {
|
|
95
|
-
ignored: [
|
|
96
|
-
/(^|[\/\\])\./, /* dotfiles */
|
|
97
|
-
/node_modules/,
|
|
98
|
-
/dist/,
|
|
99
|
-
],
|
|
106
|
+
ignored: [/(^|[\/\\])\./ /* dotfiles */, /node_modules/, /dist/],
|
|
100
107
|
persistent: true,
|
|
101
108
|
ignoreInitial: true,
|
|
102
109
|
awaitWriteFinish: {
|
|
@@ -125,18 +132,25 @@ async function dev(opts) {
|
|
|
125
132
|
.on("unlink", scheduleRebuild);
|
|
126
133
|
|
|
127
134
|
/* ---- Graceful shutdown ---- */
|
|
128
|
-
function shutdown() {
|
|
135
|
+
async function shutdown() {
|
|
136
|
+
if (shuttingDown) return;
|
|
137
|
+
shuttingDown = true;
|
|
129
138
|
console.log("\n cerver dev: shutting down...");
|
|
130
139
|
watcher.close();
|
|
131
140
|
if (debounceTimer) clearTimeout(debounceTimer);
|
|
132
141
|
if (serverProcess) {
|
|
133
142
|
serverProcess.kill("SIGTERM");
|
|
143
|
+
await waitForExit(serverProcess);
|
|
134
144
|
}
|
|
135
145
|
process.exit(0);
|
|
136
146
|
}
|
|
137
147
|
|
|
138
|
-
process.on("SIGINT",
|
|
139
|
-
|
|
148
|
+
process.on("SIGINT", () => {
|
|
149
|
+
void shutdown();
|
|
150
|
+
});
|
|
151
|
+
process.on("SIGTERM", () => {
|
|
152
|
+
void shutdown();
|
|
153
|
+
});
|
|
140
154
|
|
|
141
155
|
/* ---- Start ---- */
|
|
142
156
|
console.log("\n cerver dev — watching for changes\n");
|
package/lib/commands/new.js
CHANGED
|
@@ -17,13 +17,7 @@ function newProject(name) {
|
|
|
17
17
|
console.log(`\n Creating cerver project: ${name}\n`);
|
|
18
18
|
|
|
19
19
|
// Create directory structure
|
|
20
|
-
const dirs = [
|
|
21
|
-
"",
|
|
22
|
-
"app",
|
|
23
|
-
"app/routes",
|
|
24
|
-
"public",
|
|
25
|
-
"dist",
|
|
26
|
-
];
|
|
20
|
+
const dirs = ["", "app", "app/routes", "public", "dist"];
|
|
27
21
|
|
|
28
22
|
for (const dir of dirs) {
|
|
29
23
|
fs.mkdirSync(path.join(projectDir, dir), { recursive: true });
|
|
@@ -35,13 +29,13 @@ function newProject(name) {
|
|
|
35
29
|
// cerver.config.js
|
|
36
30
|
fs.copyFileSync(
|
|
37
31
|
path.join(templatesDir, "cerver.config.js"),
|
|
38
|
-
path.join(projectDir, "cerver.config.js")
|
|
32
|
+
path.join(projectDir, "cerver.config.js"),
|
|
39
33
|
);
|
|
40
34
|
|
|
41
35
|
// Default route
|
|
42
36
|
fs.copyFileSync(
|
|
43
37
|
path.join(templatesDir, "index.route.js"),
|
|
44
|
-
path.join(projectDir, "app", "routes", "index.js")
|
|
38
|
+
path.join(projectDir, "app", "routes", "index.js"),
|
|
45
39
|
);
|
|
46
40
|
|
|
47
41
|
// Default public/index.html
|
|
@@ -54,100 +48,300 @@ function newProject(name) {
|
|
|
54
48
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
55
49
|
<title>${name}</title>
|
|
56
50
|
<link rel="icon" href="/favicon.ico" type="image/x-icon">
|
|
51
|
+
<link rel="preconnect" href="https://fonts.googleapis.com">
|
|
52
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
|
53
|
+
<link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=Unbounded:wght@500;700&display=swap" rel="stylesheet">
|
|
57
54
|
<style>
|
|
58
55
|
:root {
|
|
59
|
-
--
|
|
60
|
-
--
|
|
61
|
-
--
|
|
62
|
-
--
|
|
56
|
+
--ink: #121316;
|
|
57
|
+
--muted: #4a4f57;
|
|
58
|
+
--paper: #f7f4f1;
|
|
59
|
+
--glass: rgba(255, 255, 255, 0.7);
|
|
60
|
+
--edge: rgba(255, 255, 255, 0.6);
|
|
61
|
+
--pink: #ff7abf;
|
|
62
|
+
--peach: #ffb380;
|
|
63
|
+
--mint: #7de3c9;
|
|
64
|
+
--blue: #6aa9ff;
|
|
65
|
+
--shadow: rgba(18, 19, 22, 0.18);
|
|
66
|
+
}
|
|
67
|
+
* {
|
|
68
|
+
box-sizing: border-box;
|
|
63
69
|
}
|
|
64
70
|
body {
|
|
65
71
|
margin: 0;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
background-color: var(--bg-color);
|
|
69
|
-
color: var(--text-color);
|
|
70
|
-
display: flex;
|
|
71
|
-
flex-direction: column;
|
|
72
|
-
align-items: center;
|
|
73
|
-
justify-content: center;
|
|
72
|
+
font-family: "Space Grotesk", "Segoe UI", sans-serif;
|
|
73
|
+
color: var(--ink);
|
|
74
74
|
min-height: 100vh;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
background:
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
}
|
|
103
|
-
.
|
|
104
|
-
|
|
75
|
+
display: grid;
|
|
76
|
+
place-items: center;
|
|
77
|
+
background-color: var(--paper);
|
|
78
|
+
background-image:
|
|
79
|
+
radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.35), transparent 45%),
|
|
80
|
+
radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.4), transparent 50%),
|
|
81
|
+
radial-gradient(circle at 82% 82%, rgba(122, 214, 255, 0.35), transparent 55%),
|
|
82
|
+
radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.35), transparent 55%),
|
|
83
|
+
linear-gradient(120deg, #f7f4f1 0%, #f2f7ff 100%);
|
|
84
|
+
}
|
|
85
|
+
body::before,
|
|
86
|
+
body::after {
|
|
87
|
+
content: "";
|
|
88
|
+
position: fixed;
|
|
89
|
+
inset: -20% -10%;
|
|
90
|
+
pointer-events: none;
|
|
91
|
+
}
|
|
92
|
+
body::before {
|
|
93
|
+
background:
|
|
94
|
+
conic-gradient(from 200deg at 50% 50%, rgba(255, 122, 191, 0.08), rgba(122, 214, 255, 0.08), rgba(125, 227, 201, 0.08), rgba(255, 179, 128, 0.08));
|
|
95
|
+
filter: blur(60px);
|
|
96
|
+
opacity: 0.6;
|
|
97
|
+
}
|
|
98
|
+
body::after {
|
|
99
|
+
background-image: radial-gradient(circle, rgba(18, 19, 22, 0.04) 1px, transparent 1px);
|
|
100
|
+
background-size: 24px 24px;
|
|
101
|
+
opacity: 0.6;
|
|
102
|
+
}
|
|
103
|
+
.stage {
|
|
104
|
+
width: min(1100px, 92vw);
|
|
105
|
+
padding: 4rem 0;
|
|
106
|
+
}
|
|
107
|
+
.frame {
|
|
108
|
+
position: relative;
|
|
109
|
+
display: grid;
|
|
110
|
+
grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
|
|
111
|
+
gap: 2.5rem;
|
|
112
|
+
align-items: center;
|
|
113
|
+
padding: clamp(2.5rem, 4vw, 4rem);
|
|
114
|
+
border-radius: 32px;
|
|
115
|
+
background: var(--glass);
|
|
116
|
+
border: 1px solid var(--edge);
|
|
117
|
+
box-shadow:
|
|
118
|
+
0 40px 90px -40px var(--shadow),
|
|
119
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.7);
|
|
120
|
+
backdrop-filter: blur(18px);
|
|
121
|
+
-webkit-backdrop-filter: blur(18px);
|
|
122
|
+
animation: rise 0.8s cubic-bezier(0.16, 1, 0.3, 1) both;
|
|
105
123
|
}
|
|
106
124
|
h1 {
|
|
107
|
-
margin: 0 0 1rem
|
|
108
|
-
font-
|
|
109
|
-
font-
|
|
110
|
-
letter-spacing:
|
|
125
|
+
margin: 0.6rem 0 1rem;
|
|
126
|
+
font-family: "Unbounded", "Space Grotesk", sans-serif;
|
|
127
|
+
font-size: clamp(2.2rem, 4vw, 4.4rem);
|
|
128
|
+
letter-spacing: 0.08em;
|
|
129
|
+
text-transform: uppercase;
|
|
111
130
|
}
|
|
112
131
|
p {
|
|
132
|
+
margin: 0 0 1.5rem;
|
|
133
|
+
font-size: 1.1rem;
|
|
134
|
+
line-height: 1.7;
|
|
135
|
+
color: var(--muted);
|
|
136
|
+
max-width: 32rem;
|
|
137
|
+
}
|
|
138
|
+
.steps {
|
|
139
|
+
margin: 0 0 1.8rem;
|
|
140
|
+
}
|
|
141
|
+
.steps-box {
|
|
142
|
+
padding: 1rem 1.2rem;
|
|
143
|
+
border-radius: 18px;
|
|
144
|
+
background: rgba(255, 255, 255, 0.35);
|
|
145
|
+
border: 1px solid rgba(18, 19, 22, 0.08);
|
|
146
|
+
backdrop-filter: blur(8px);
|
|
147
|
+
-webkit-backdrop-filter: blur(8px);
|
|
148
|
+
}
|
|
149
|
+
.steps-title {
|
|
150
|
+
margin: 0 0 0.7rem;
|
|
151
|
+
font-size: 0.75rem;
|
|
152
|
+
text-transform: uppercase;
|
|
153
|
+
letter-spacing: 0.18em;
|
|
154
|
+
color: var(--muted);
|
|
155
|
+
}
|
|
156
|
+
.steps-list {
|
|
113
157
|
margin: 0;
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
color:
|
|
124
|
-
|
|
125
|
-
|
|
158
|
+
padding-left: 1.1rem;
|
|
159
|
+
display: grid;
|
|
160
|
+
gap: 0.55rem;
|
|
161
|
+
color: var(--muted);
|
|
162
|
+
}
|
|
163
|
+
.steps-list li::marker {
|
|
164
|
+
color: var(--muted);
|
|
165
|
+
}
|
|
166
|
+
.steps-list strong {
|
|
167
|
+
color: #2c3138;
|
|
168
|
+
font-weight: 600;
|
|
169
|
+
}
|
|
170
|
+
.meta {
|
|
171
|
+
display: inline-flex;
|
|
172
|
+
align-items: center;
|
|
173
|
+
gap: 0.6rem;
|
|
174
|
+
font-weight: 600;
|
|
175
|
+
color: #2c3138;
|
|
176
|
+
}
|
|
177
|
+
.dot {
|
|
178
|
+
width: 10px;
|
|
179
|
+
height: 10px;
|
|
180
|
+
border-radius: 50%;
|
|
181
|
+
background: linear-gradient(135deg, var(--pink), var(--blue));
|
|
182
|
+
box-shadow: 0 0 12px rgba(122, 214, 255, 0.8);
|
|
183
|
+
}
|
|
184
|
+
.footer {
|
|
185
|
+
margin-top: 1.8rem;
|
|
186
|
+
text-align: center;
|
|
187
|
+
font-size: 0.9rem;
|
|
188
|
+
color: var(--muted);
|
|
189
|
+
}
|
|
190
|
+
.footer a {
|
|
191
|
+
color: inherit;
|
|
192
|
+
text-decoration: none;
|
|
126
193
|
font-weight: 600;
|
|
127
|
-
|
|
194
|
+
}
|
|
195
|
+
.footer a:hover {
|
|
196
|
+
text-decoration: underline;
|
|
197
|
+
}
|
|
198
|
+
.art {
|
|
199
|
+
position: relative;
|
|
200
|
+
display: grid;
|
|
201
|
+
place-items: center;
|
|
202
|
+
min-height: 280px;
|
|
203
|
+
}
|
|
204
|
+
.art::before {
|
|
205
|
+
content: "";
|
|
206
|
+
position: absolute;
|
|
207
|
+
width: min(320px, 70vw);
|
|
208
|
+
aspect-ratio: 1;
|
|
209
|
+
border-radius: 28%;
|
|
210
|
+
background: linear-gradient(140deg, rgba(255, 122, 191, 0.25), rgba(122, 214, 255, 0.2), rgba(125, 227, 201, 0.25));
|
|
211
|
+
filter: blur(10px);
|
|
212
|
+
transform: rotate(18deg);
|
|
213
|
+
}
|
|
214
|
+
.art img {
|
|
215
|
+
width: min(300px, 65vw);
|
|
216
|
+
height: auto;
|
|
217
|
+
border-radius: 18%;
|
|
218
|
+
filter: drop-shadow(0 25px 40px rgba(18, 19, 22, 0.25));
|
|
219
|
+
animation: float 6s ease-in-out infinite;
|
|
220
|
+
}
|
|
221
|
+
@keyframes float {
|
|
222
|
+
0%, 100% { transform: translateY(0px) rotate(-2deg); }
|
|
223
|
+
50% { transform: translateY(-12px) rotate(2deg); }
|
|
224
|
+
}
|
|
225
|
+
@keyframes rise {
|
|
226
|
+
from { opacity: 0; transform: translateY(18px) scale(0.98); }
|
|
227
|
+
to { opacity: 1; transform: translateY(0) scale(1); }
|
|
228
|
+
}
|
|
229
|
+
@media (max-width: 760px) {
|
|
230
|
+
.stage {
|
|
231
|
+
padding: 2.5rem 0;
|
|
232
|
+
}
|
|
233
|
+
.frame {
|
|
234
|
+
padding: 2.2rem;
|
|
235
|
+
}
|
|
236
|
+
h1 {
|
|
237
|
+
letter-spacing: 0.05em;
|
|
238
|
+
}
|
|
239
|
+
.art {
|
|
240
|
+
order: -1;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
@media (prefers-color-scheme: dark) {
|
|
244
|
+
:root {
|
|
245
|
+
--ink: #f7f8fb;
|
|
246
|
+
--muted: #c2c8d0;
|
|
247
|
+
--paper: #0b0e13;
|
|
248
|
+
--glass: rgba(15, 19, 28, 0.78);
|
|
249
|
+
--edge: rgba(255, 255, 255, 0.12);
|
|
250
|
+
--shadow: rgba(0, 0, 0, 0.65);
|
|
251
|
+
}
|
|
252
|
+
body {
|
|
253
|
+
background-image:
|
|
254
|
+
radial-gradient(circle at 15% 10%, rgba(255, 122, 191, 0.45), transparent 45%),
|
|
255
|
+
radial-gradient(circle at 85% 12%, rgba(255, 179, 128, 0.45), transparent 50%),
|
|
256
|
+
radial-gradient(circle at 82% 82%, rgba(106, 169, 255, 0.45), transparent 55%),
|
|
257
|
+
radial-gradient(circle at 20% 80%, rgba(125, 227, 201, 0.4), transparent 55%),
|
|
258
|
+
linear-gradient(120deg, #0b0e13 0%, #121826 100%);
|
|
259
|
+
}
|
|
260
|
+
body::before {
|
|
261
|
+
opacity: 0.95;
|
|
262
|
+
filter: blur(90px);
|
|
263
|
+
}
|
|
264
|
+
body::after {
|
|
265
|
+
opacity: 0.2;
|
|
266
|
+
}
|
|
267
|
+
.frame {
|
|
268
|
+
box-shadow:
|
|
269
|
+
0 50px 120px -40px rgba(0, 0, 0, 0.75),
|
|
270
|
+
0 0 120px rgba(106, 169, 255, 0.25),
|
|
271
|
+
inset 0 1px 0 rgba(255, 255, 255, 0.08);
|
|
272
|
+
}
|
|
273
|
+
.steps-box {
|
|
274
|
+
background: rgba(6, 8, 14, 0.45);
|
|
275
|
+
border: 1px solid rgba(255, 255, 255, 0.12);
|
|
276
|
+
}
|
|
277
|
+
.steps-list strong {
|
|
278
|
+
color: #eef1f6;
|
|
279
|
+
}
|
|
280
|
+
.meta {
|
|
281
|
+
color: #e6e9ef;
|
|
282
|
+
}
|
|
283
|
+
.dot {
|
|
284
|
+
box-shadow: 0 0 18px rgba(255, 122, 191, 0.9);
|
|
285
|
+
}
|
|
286
|
+
.art::before {
|
|
287
|
+
filter: blur(18px);
|
|
288
|
+
}
|
|
289
|
+
.art img {
|
|
290
|
+
filter: drop-shadow(0 30px 60px rgba(106, 169, 255, 0.45));
|
|
291
|
+
}
|
|
292
|
+
.footer {
|
|
293
|
+
color: #c2c8d0;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
@media (prefers-reduced-motion: reduce) {
|
|
297
|
+
.frame,
|
|
298
|
+
.art img {
|
|
299
|
+
animation: none;
|
|
300
|
+
}
|
|
128
301
|
}
|
|
129
302
|
</style>
|
|
130
303
|
</head>
|
|
131
304
|
<body>
|
|
132
|
-
<
|
|
133
|
-
<
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
305
|
+
<main class="stage">
|
|
306
|
+
<section class="frame">
|
|
307
|
+
<div class="copy">
|
|
308
|
+
<h1>${name}</h1>
|
|
309
|
+
<p>Native-speed web apps with a glassy sheen. Your new cerver project is wired, built, and ready to ship.</p>
|
|
310
|
+
<div class="steps">
|
|
311
|
+
<div class="steps-box">
|
|
312
|
+
<div class="steps-title">Next steps</div>
|
|
313
|
+
<ul class="steps-list">
|
|
314
|
+
<li>Edit <strong>public/index.html</strong> to change this page.</li>
|
|
315
|
+
<li>Config lives in <strong>cerver.config.js</strong> at the project root.</li>
|
|
316
|
+
</ul>
|
|
317
|
+
</div>
|
|
318
|
+
</div>
|
|
319
|
+
<div class="meta">
|
|
320
|
+
<span class="dot"></span>
|
|
321
|
+
<span>Powered by cerver</span>
|
|
322
|
+
</div>
|
|
323
|
+
</div>
|
|
324
|
+
<div class="art">
|
|
325
|
+
<img src="/cerver.png" alt="cerver logo">
|
|
326
|
+
</div>
|
|
327
|
+
</section>
|
|
328
|
+
<footer class="footer">
|
|
329
|
+
<a href="https://github.com/velox0/cerver" target="_blank" rel="noreferrer">GitHub: github.com/velox0/cerver</a>
|
|
330
|
+
</footer>
|
|
331
|
+
</main>
|
|
138
332
|
</body>
|
|
139
333
|
</html>
|
|
140
|
-
|
|
334
|
+
`,
|
|
141
335
|
);
|
|
142
336
|
|
|
143
337
|
// Copy standard static assets
|
|
144
338
|
fs.copyFileSync(
|
|
145
339
|
path.join(templatesDir, "cerver.png"),
|
|
146
|
-
path.join(projectDir, "public", "cerver.png")
|
|
340
|
+
path.join(projectDir, "public", "cerver.png"),
|
|
147
341
|
);
|
|
148
342
|
fs.copyFileSync(
|
|
149
343
|
path.join(templatesDir, "favicon.ico"),
|
|
150
|
-
path.join(projectDir, "public", "favicon.ico")
|
|
344
|
+
path.join(projectDir, "public", "favicon.ico"),
|
|
151
345
|
);
|
|
152
346
|
|
|
153
347
|
// package.json
|
|
@@ -164,8 +358,8 @@ function newProject(name) {
|
|
|
164
358
|
},
|
|
165
359
|
},
|
|
166
360
|
null,
|
|
167
|
-
2
|
|
168
|
-
) + "\n"
|
|
361
|
+
2,
|
|
362
|
+
) + "\n",
|
|
169
363
|
);
|
|
170
364
|
|
|
171
365
|
console.log(" Created:");
|
package/lib/ir/transform.js
CHANGED
|
@@ -6,7 +6,7 @@ const IR = require("./types");
|
|
|
6
6
|
* Transform a validated AST into an IR route descriptor.
|
|
7
7
|
*
|
|
8
8
|
* @param {object} ast - ESTree AST (validated)
|
|
9
|
-
* @param {string} urlPath - The route path (e.g. "/
|
|
9
|
+
* @param {string} urlPath - The route path (e.g. "/groups/:group_id")
|
|
10
10
|
* @returns {IRRoute[]} — one IRRoute per exported handler (GET, POST)
|
|
11
11
|
*/
|
|
12
12
|
function transformFile(ast, urlPath) {
|
|
@@ -135,11 +135,7 @@ function transformReturn(node, ctx) {
|
|
|
135
135
|
}
|
|
136
136
|
|
|
137
137
|
/* Plain expression return — treat as text */
|
|
138
|
-
return IR.IRReturn(
|
|
139
|
-
"text",
|
|
140
|
-
200,
|
|
141
|
-
transformExpression(arg, ctx)
|
|
142
|
-
);
|
|
138
|
+
return IR.IRReturn("text", 200, transformExpression(arg, ctx));
|
|
143
139
|
}
|
|
144
140
|
|
|
145
141
|
/**
|
|
@@ -148,9 +144,13 @@ function transformReturn(node, ctx) {
|
|
|
148
144
|
function transformIf(node, ctx) {
|
|
149
145
|
const condition = transformExpression(node.test, ctx);
|
|
150
146
|
|
|
151
|
-
const thenBlock =
|
|
152
|
-
|
|
153
|
-
|
|
147
|
+
const thenBlock =
|
|
148
|
+
node.consequent.type === "BlockStatement"
|
|
149
|
+
? transformBlock(node.consequent, ctx)
|
|
150
|
+
: {
|
|
151
|
+
variables: [],
|
|
152
|
+
body: [transformStatement(node.consequent, ctx)].filter(Boolean),
|
|
153
|
+
};
|
|
154
154
|
|
|
155
155
|
let elseBody = null;
|
|
156
156
|
if (node.alternate) {
|
|
@@ -208,28 +208,25 @@ function transformExpression(node, ctx) {
|
|
|
208
208
|
return IR.IRComparison(
|
|
209
209
|
node.operator,
|
|
210
210
|
transformExpression(node.left, ctx),
|
|
211
|
-
transformExpression(node.right, ctx)
|
|
211
|
+
transformExpression(node.right, ctx),
|
|
212
212
|
);
|
|
213
213
|
|
|
214
214
|
case "LogicalExpression":
|
|
215
215
|
return IR.IRLogical(
|
|
216
216
|
node.operator,
|
|
217
217
|
transformExpression(node.left, ctx),
|
|
218
|
-
transformExpression(node.right, ctx)
|
|
218
|
+
transformExpression(node.right, ctx),
|
|
219
219
|
);
|
|
220
220
|
|
|
221
221
|
case "UnaryExpression":
|
|
222
|
-
return IR.IRUnary(
|
|
223
|
-
node.operator,
|
|
224
|
-
transformExpression(node.argument, ctx)
|
|
225
|
-
);
|
|
222
|
+
return IR.IRUnary(node.operator, transformExpression(node.argument, ctx));
|
|
226
223
|
|
|
227
224
|
case "ConditionalExpression":
|
|
228
225
|
/* a ? b : c → IR.IRIf as expression — simplify to if/else for now */
|
|
229
226
|
return IR.IRComparison(
|
|
230
227
|
"?:",
|
|
231
228
|
transformExpression(node.test, ctx),
|
|
232
|
-
transformExpression(node.consequent, ctx)
|
|
229
|
+
transformExpression(node.consequent, ctx),
|
|
233
230
|
);
|
|
234
231
|
|
|
235
232
|
case "MemberExpression":
|
|
@@ -274,10 +271,7 @@ function transformMemberExpr(node, ctx) {
|
|
|
274
271
|
}
|
|
275
272
|
|
|
276
273
|
/* req.method, req.path */
|
|
277
|
-
if (
|
|
278
|
-
node.object.type === "Identifier" &&
|
|
279
|
-
node.object.name === ctx.reqName
|
|
280
|
-
) {
|
|
274
|
+
if (node.object.type === "Identifier" && node.object.name === ctx.reqName) {
|
|
281
275
|
const prop = node.property.name || node.property.value;
|
|
282
276
|
if (prop === "method") return IR.IRIdentifier("req->method");
|
|
283
277
|
if (prop === "path") return IR.IRIdentifier("req->path");
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@velox0/cerver",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.2",
|
|
4
4
|
"description": "Compile restricted JavaScript server logic into optimized native C binaries",
|
|
5
|
-
"main": "
|
|
5
|
+
"main": "bin/cerver.js",
|
|
6
6
|
"bin": {
|
|
7
7
|
"cerver": "bin/cerver.js"
|
|
8
8
|
},
|