@velox0/cerver 0.4.1 → 0.4.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.
- package/.github/workflows/ci.yml +35 -0
- package/.github/workflows/publish.yml +3 -0
- package/Makefile +26 -0
- package/README.md +22 -5
- package/bin/cerver.js +11 -0
- package/lib/codegen/dispatch_gen.js +3 -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 +3 -3
- package/runtime/cerver.h +55 -36
- package/runtime/http_parser.c +12 -11
- package/runtime/http_writer.c +152 -45
- package/runtime/mime.c +1 -0
- package/runtime/router.c +193 -10
- package/runtime/server.c +80 -61
- package/runtime/static.c +15 -55
- package/runtime/tests/minunit.c +76 -0
- package/runtime/tests/minunit.h +64 -0
- package/runtime/tests/runtime_tests.c +464 -0
- package/test/run.js +11 -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/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,11 +13,26 @@ 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
19
|
- **File-Based Routing**: Intuitive `app/routes/` directory structure, supporting dynamic segments (e.g., `/item/[id].js`).
|
|
18
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) |
|
|
35
|
+
|
|
19
36
|
## Getting Started
|
|
20
37
|
|
|
21
38
|
1. Install globally (requires `gcc` or `clang` on your system):
|
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")
|
|
@@ -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/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:");
|