@seip/blue-bird 0.6.4 → 0.7.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/.env_example +0 -6
- package/AGENTS.md +41 -156
- package/README.md +46 -130
- package/backend/routes/api.js +21 -17
- package/core/app.js +86 -81
- package/core/cli/init.js +120 -11
- package/core/logger.js +77 -78
- package/core/router.js +2 -6
- package/frontend/astro.config.mjs +35 -0
- package/frontend/public/css/app.css +319 -0
- package/frontend/public/favicon.ico +0 -0
- package/frontend/src/http/api.js +19 -0
- package/frontend/src/layouts/Layout.astro +20 -0
- package/frontend/src/pages/about.astro +54 -0
- package/frontend/src/pages/index.astro +104 -0
- package/{backend/index.js → index.js} +11 -4
- package/package.json +12 -4
- package/backend/routes/frontend.js +0 -39
- package/core/seo.js +0 -113
- package/core/template.js +0 -319
- package/frontend/public/js/blue-bird.js +0 -1465
- package/frontend/public/js/tailwind.js +0 -8
- package/frontend/templates/about.html +0 -105
- package/frontend/templates/index.html +0 -146
- package/frontend/templates/preact_example.html +0 -80
|
@@ -0,0 +1,319 @@
|
|
|
1
|
+
/* ============================================================
|
|
2
|
+
Blue Bird - Base Stylesheet
|
|
3
|
+
Vanilla CSS — no build step required.
|
|
4
|
+
Import this file in your Astro layout.
|
|
5
|
+
============================================================ */
|
|
6
|
+
|
|
7
|
+
/* -------------------------------------------------------
|
|
8
|
+
Design Tokens
|
|
9
|
+
------------------------------------------------------- */
|
|
10
|
+
:root {
|
|
11
|
+
--color-bg: #020617;
|
|
12
|
+
--color-bg-surface: #0f172a;
|
|
13
|
+
--color-bg-muted: #1e293b;
|
|
14
|
+
--color-border: rgba(255, 255, 255, 0.08);
|
|
15
|
+
--color-text: #f1f5f9;
|
|
16
|
+
--color-text-muted: #94a3b8;
|
|
17
|
+
--color-text-subtle: #64748b;
|
|
18
|
+
--color-primary: #3b82f6;
|
|
19
|
+
--color-primary-h: #2563eb;
|
|
20
|
+
--color-accent: #6366f1;
|
|
21
|
+
|
|
22
|
+
--space-xs: 0.25rem;
|
|
23
|
+
--space-sm: 0.5rem;
|
|
24
|
+
--space-md: 1rem;
|
|
25
|
+
--space-lg: 1.5rem;
|
|
26
|
+
--space-xl: 2rem;
|
|
27
|
+
--space-2xl: 3rem;
|
|
28
|
+
--space-3xl: 4rem;
|
|
29
|
+
|
|
30
|
+
--font-sans: system-ui, -apple-system, "Segoe UI", Helvetica, Arial, sans-serif;
|
|
31
|
+
--font-mono: ui-monospace, "SFMono-Regular", Menlo, Monaco, Consolas, monospace;
|
|
32
|
+
--text-sm: 0.875rem;
|
|
33
|
+
--text-base: 1rem;
|
|
34
|
+
--text-lg: 1.125rem;
|
|
35
|
+
--text-xl: 1.25rem;
|
|
36
|
+
--text-2xl: 1.5rem;
|
|
37
|
+
--text-3xl: 1.875rem;
|
|
38
|
+
--text-4xl: 2.25rem;
|
|
39
|
+
--text-5xl: 3rem;
|
|
40
|
+
--text-6xl: 3.75rem;
|
|
41
|
+
|
|
42
|
+
--radius-sm: 0.375rem;
|
|
43
|
+
--radius-md: 0.75rem;
|
|
44
|
+
--radius-lg: 1rem;
|
|
45
|
+
--radius-xl: 1.25rem;
|
|
46
|
+
--radius-2xl: 1.5rem;
|
|
47
|
+
--radius-full: 9999px;
|
|
48
|
+
|
|
49
|
+
--shadow-sm: 0 1px 2px rgba(0, 0, 0, 0.4);
|
|
50
|
+
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
51
|
+
--shadow-lg: 0 10px 30px rgba(0, 0, 0, 0.6);
|
|
52
|
+
--shadow-glow-blue: 0 0 24px rgba(59, 130, 246, 0.2);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/* -------------------------------------------------------
|
|
56
|
+
Reset & Base
|
|
57
|
+
------------------------------------------------------- */
|
|
58
|
+
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
|
|
59
|
+
html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }
|
|
60
|
+
body {
|
|
61
|
+
min-height: 100dvh;
|
|
62
|
+
background-color: var(--color-bg);
|
|
63
|
+
color: var(--color-text);
|
|
64
|
+
font-family: var(--font-sans);
|
|
65
|
+
font-size: var(--text-base);
|
|
66
|
+
line-height: 1.6;
|
|
67
|
+
-webkit-font-smoothing: antialiased;
|
|
68
|
+
-moz-osx-font-smoothing: grayscale;
|
|
69
|
+
}
|
|
70
|
+
::selection { background-color: var(--color-primary); color: #fff; }
|
|
71
|
+
|
|
72
|
+
/* -------------------------------------------------------
|
|
73
|
+
Typography
|
|
74
|
+
------------------------------------------------------- */
|
|
75
|
+
h1, h2, h3, h4, h5, h6 { line-height: 1.2; font-weight: 700; letter-spacing: -0.02em; }
|
|
76
|
+
h1 { font-size: clamp(2rem, 5vw, var(--text-6xl)); }
|
|
77
|
+
h2 { font-size: clamp(1.5rem, 4vw, var(--text-4xl)); }
|
|
78
|
+
h3 { font-size: clamp(1.25rem, 3vw, var(--text-3xl)); }
|
|
79
|
+
h4 { font-size: var(--text-xl); }
|
|
80
|
+
p { color: var(--color-text-muted); line-height: 1.75; }
|
|
81
|
+
a { color: var(--color-primary); text-decoration: none; transition: color 0.2s ease; }
|
|
82
|
+
a:hover { color: var(--color-primary-h); }
|
|
83
|
+
|
|
84
|
+
code, kbd {
|
|
85
|
+
font-family: var(--font-mono);
|
|
86
|
+
font-size: 0.875em;
|
|
87
|
+
background: var(--color-bg-muted);
|
|
88
|
+
color: var(--color-text);
|
|
89
|
+
padding: 0.15em 0.4em;
|
|
90
|
+
border-radius: var(--radius-sm);
|
|
91
|
+
}
|
|
92
|
+
pre {
|
|
93
|
+
font-family: var(--font-mono);
|
|
94
|
+
background: var(--color-bg-surface);
|
|
95
|
+
border: 1px solid var(--color-border);
|
|
96
|
+
border-radius: var(--radius-lg);
|
|
97
|
+
padding: var(--space-lg);
|
|
98
|
+
overflow-x: auto;
|
|
99
|
+
font-size: var(--text-sm);
|
|
100
|
+
}
|
|
101
|
+
pre code { background: none; padding: 0; font-size: inherit; }
|
|
102
|
+
img, svg { display: block; max-width: 100%; }
|
|
103
|
+
|
|
104
|
+
/* -------------------------------------------------------
|
|
105
|
+
Layout
|
|
106
|
+
------------------------------------------------------- */
|
|
107
|
+
.container {
|
|
108
|
+
width: 100%;
|
|
109
|
+
max-width: 80rem;
|
|
110
|
+
margin-inline: auto;
|
|
111
|
+
padding-inline: var(--space-lg);
|
|
112
|
+
}
|
|
113
|
+
.flex { display: flex; }
|
|
114
|
+
.flex-col { flex-direction: column; }
|
|
115
|
+
.items-center { align-items: center; }
|
|
116
|
+
.justify-between { justify-content: space-between; }
|
|
117
|
+
.justify-center { justify-content: center; }
|
|
118
|
+
.flex-wrap { flex-wrap: wrap; }
|
|
119
|
+
.gap-sm { gap: var(--space-sm); }
|
|
120
|
+
.gap-md { gap: var(--space-md); }
|
|
121
|
+
.gap-lg { gap: var(--space-lg); }
|
|
122
|
+
.gap-xl { gap: var(--space-xl); }
|
|
123
|
+
.grid-2 { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: var(--space-xl); }
|
|
124
|
+
.grid-3 { display: grid; grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); gap: var(--space-xl); }
|
|
125
|
+
.text-center { text-align: center; }
|
|
126
|
+
.text-muted { color: var(--color-text-muted); }
|
|
127
|
+
.text-subtle { color: var(--color-text-subtle); }
|
|
128
|
+
.text-sm { font-size: var(--text-sm); }
|
|
129
|
+
.text-lg { font-size: var(--text-lg); }
|
|
130
|
+
.font-mono { font-family: var(--font-mono); }
|
|
131
|
+
.font-bold { font-weight: 700; }
|
|
132
|
+
.uppercase { text-transform: uppercase; letter-spacing: 0.08em; }
|
|
133
|
+
.mt-sm { margin-top: var(--space-sm); }
|
|
134
|
+
.mt-md { margin-top: var(--space-md); }
|
|
135
|
+
.mt-lg { margin-top: var(--space-lg); }
|
|
136
|
+
.mt-xl { margin-top: var(--space-xl); }
|
|
137
|
+
.mt-2xl { margin-top: var(--space-2xl); }
|
|
138
|
+
.mb-sm { margin-bottom: var(--space-sm); }
|
|
139
|
+
.mb-md { margin-bottom: var(--space-md); }
|
|
140
|
+
.mb-lg { margin-bottom: var(--space-lg); }
|
|
141
|
+
.mb-xl { margin-bottom: var(--space-xl); }
|
|
142
|
+
.py-lg { padding-block: var(--space-lg); }
|
|
143
|
+
.py-xl { padding-block: var(--space-xl); }
|
|
144
|
+
.py-2xl { padding-block: var(--space-2xl); }
|
|
145
|
+
.py-3xl { padding-block: var(--space-3xl); }
|
|
146
|
+
.px-md { padding-inline: var(--space-md); }
|
|
147
|
+
.px-lg { padding-inline: var(--space-lg); }
|
|
148
|
+
|
|
149
|
+
/* -------------------------------------------------------
|
|
150
|
+
Gradient Text
|
|
151
|
+
------------------------------------------------------- */
|
|
152
|
+
.gradient-text {
|
|
153
|
+
background: linear-gradient(135deg, var(--color-primary), var(--color-accent), #fff);
|
|
154
|
+
-webkit-background-clip: text;
|
|
155
|
+
-webkit-text-fill-color: transparent;
|
|
156
|
+
background-clip: text;
|
|
157
|
+
}
|
|
158
|
+
.gradient-text-blue {
|
|
159
|
+
background: linear-gradient(90deg, #60a5fa, #a5b4fc);
|
|
160
|
+
-webkit-background-clip: text;
|
|
161
|
+
-webkit-text-fill-color: transparent;
|
|
162
|
+
background-clip: text;
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
/* -------------------------------------------------------
|
|
166
|
+
Header / Nav
|
|
167
|
+
------------------------------------------------------- */
|
|
168
|
+
.site-header {
|
|
169
|
+
position: sticky;
|
|
170
|
+
top: 0;
|
|
171
|
+
z-index: 50;
|
|
172
|
+
padding: var(--space-sm) var(--space-lg);
|
|
173
|
+
}
|
|
174
|
+
.nav-bar {
|
|
175
|
+
display: flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
justify-content: space-between;
|
|
178
|
+
background: rgba(15, 23, 42, 0.7);
|
|
179
|
+
backdrop-filter: blur(12px);
|
|
180
|
+
-webkit-backdrop-filter: blur(12px);
|
|
181
|
+
border: 1px solid var(--color-border);
|
|
182
|
+
border-radius: var(--radius-2xl);
|
|
183
|
+
padding: var(--space-sm) var(--space-lg);
|
|
184
|
+
box-shadow: var(--shadow-md);
|
|
185
|
+
max-width: 80rem;
|
|
186
|
+
margin-inline: auto;
|
|
187
|
+
}
|
|
188
|
+
.nav-brand {
|
|
189
|
+
display: flex;
|
|
190
|
+
align-items: center;
|
|
191
|
+
gap: var(--space-sm);
|
|
192
|
+
font-size: var(--text-xl);
|
|
193
|
+
font-weight: 700;
|
|
194
|
+
color: var(--color-text);
|
|
195
|
+
text-decoration: none;
|
|
196
|
+
}
|
|
197
|
+
.nav-brand .brand-icon {
|
|
198
|
+
width: 2.5rem;
|
|
199
|
+
height: 2.5rem;
|
|
200
|
+
background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
|
|
201
|
+
border-radius: var(--radius-md);
|
|
202
|
+
display: flex;
|
|
203
|
+
align-items: center;
|
|
204
|
+
justify-content: center;
|
|
205
|
+
box-shadow: var(--shadow-glow-blue);
|
|
206
|
+
}
|
|
207
|
+
nav.nav-links { display: flex; gap: var(--space-xl); }
|
|
208
|
+
.nav-links a { font-size: var(--text-sm); font-weight: 500; color: var(--color-text-muted); transition: color 0.2s ease; }
|
|
209
|
+
.nav-links a:hover { color: var(--color-primary); }
|
|
210
|
+
|
|
211
|
+
/* -------------------------------------------------------
|
|
212
|
+
Buttons
|
|
213
|
+
------------------------------------------------------- */
|
|
214
|
+
.btn {
|
|
215
|
+
display: inline-flex;
|
|
216
|
+
align-items: center;
|
|
217
|
+
gap: var(--space-sm);
|
|
218
|
+
padding: 0.75rem 1.75rem;
|
|
219
|
+
border-radius: var(--radius-xl);
|
|
220
|
+
font-weight: 600;
|
|
221
|
+
font-size: var(--text-base);
|
|
222
|
+
cursor: pointer;
|
|
223
|
+
border: none;
|
|
224
|
+
text-decoration: none;
|
|
225
|
+
transition: transform 0.2s ease, box-shadow 0.2s ease;
|
|
226
|
+
}
|
|
227
|
+
.btn:hover { transform: translateY(-2px); }
|
|
228
|
+
.btn:active { transform: translateY(0); }
|
|
229
|
+
.btn-primary {
|
|
230
|
+
background: linear-gradient(135deg, var(--color-primary), var(--color-accent));
|
|
231
|
+
color: #fff;
|
|
232
|
+
box-shadow: 0 4px 20px rgba(59, 130, 246, 0.3);
|
|
233
|
+
}
|
|
234
|
+
.btn-primary:hover { color: #fff; box-shadow: 0 6px 28px rgba(59, 130, 246, 0.45); }
|
|
235
|
+
.btn-outline {
|
|
236
|
+
background: transparent;
|
|
237
|
+
color: var(--color-text-muted);
|
|
238
|
+
border: 1px solid var(--color-border);
|
|
239
|
+
}
|
|
240
|
+
.btn-outline:hover { background: var(--color-bg-surface); color: var(--color-text); }
|
|
241
|
+
|
|
242
|
+
/* -------------------------------------------------------
|
|
243
|
+
Cards
|
|
244
|
+
------------------------------------------------------- */
|
|
245
|
+
.card {
|
|
246
|
+
background: var(--color-bg-surface);
|
|
247
|
+
border: 1px solid var(--color-border);
|
|
248
|
+
border-radius: var(--radius-2xl);
|
|
249
|
+
padding: var(--space-xl);
|
|
250
|
+
transition: transform 0.25s ease, box-shadow 0.25s ease;
|
|
251
|
+
}
|
|
252
|
+
.card:hover { transform: translateY(-4px); box-shadow: var(--shadow-lg); }
|
|
253
|
+
.card-glass {
|
|
254
|
+
background: rgba(15, 23, 42, 0.5);
|
|
255
|
+
backdrop-filter: blur(16px);
|
|
256
|
+
-webkit-backdrop-filter: blur(16px);
|
|
257
|
+
border: 1px solid var(--color-border);
|
|
258
|
+
border-radius: var(--radius-2xl);
|
|
259
|
+
padding: var(--space-xl);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/* -------------------------------------------------------
|
|
263
|
+
Badge
|
|
264
|
+
------------------------------------------------------- */
|
|
265
|
+
.badge {
|
|
266
|
+
display: inline-flex;
|
|
267
|
+
align-items: center;
|
|
268
|
+
padding: 0.3rem 0.9rem;
|
|
269
|
+
border-radius: var(--radius-full);
|
|
270
|
+
font-size: 0.7rem;
|
|
271
|
+
font-weight: 600;
|
|
272
|
+
letter-spacing: 0.1em;
|
|
273
|
+
text-transform: uppercase;
|
|
274
|
+
background: rgba(59, 130, 246, 0.1);
|
|
275
|
+
color: #60a5fa;
|
|
276
|
+
border: 1px solid rgba(59, 130, 246, 0.2);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/* -------------------------------------------------------
|
|
280
|
+
Hero
|
|
281
|
+
------------------------------------------------------- */
|
|
282
|
+
.hero {
|
|
283
|
+
position: relative;
|
|
284
|
+
overflow: hidden;
|
|
285
|
+
padding-block: var(--space-3xl);
|
|
286
|
+
text-align: center;
|
|
287
|
+
}
|
|
288
|
+
.hero-glow {
|
|
289
|
+
position: absolute;
|
|
290
|
+
border-radius: 50%;
|
|
291
|
+
filter: blur(80px);
|
|
292
|
+
pointer-events: none;
|
|
293
|
+
z-index: 0;
|
|
294
|
+
}
|
|
295
|
+
.hero-glow-1 { width: 24rem; height: 24rem; top: -6rem; left: -6rem; background: rgba(59, 130, 246, 0.08); }
|
|
296
|
+
.hero-glow-2 { width: 24rem; height: 24rem; bottom: -6rem; right: -6rem; background: rgba(99, 102, 241, 0.08); }
|
|
297
|
+
.hero-inner { position: relative; z-index: 1; max-width: 56rem; margin-inline: auto; }
|
|
298
|
+
|
|
299
|
+
/* -------------------------------------------------------
|
|
300
|
+
Section / Footer
|
|
301
|
+
------------------------------------------------------- */
|
|
302
|
+
.section { padding-block: var(--space-3xl); }
|
|
303
|
+
.site-footer {
|
|
304
|
+
border-top: 1px solid var(--color-border);
|
|
305
|
+
padding-block: var(--space-xl);
|
|
306
|
+
text-align: center;
|
|
307
|
+
color: var(--color-text-subtle);
|
|
308
|
+
font-size: var(--text-sm);
|
|
309
|
+
background: var(--color-bg);
|
|
310
|
+
margin-top: var(--space-3xl);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
/* -------------------------------------------------------
|
|
314
|
+
Scrollbar (Webkit)
|
|
315
|
+
------------------------------------------------------- */
|
|
316
|
+
::-webkit-scrollbar { width: 6px; }
|
|
317
|
+
::-webkit-scrollbar-track { background: var(--color-bg); }
|
|
318
|
+
::-webkit-scrollbar-thumb { background: var(--color-bg-muted); border-radius: var(--radius-full); }
|
|
319
|
+
::-webkit-scrollbar-thumb:hover { background: var(--color-primary); }
|
|
Binary file
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
const port = (import.meta.env.PORT || "3000").replace(/^["']|["']$/g, "");
|
|
2
|
+
const host = (import.meta.env.HOST || "localhost").replace(/^["']|["']$/g, "");
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Builds an absolute API URL for server-side fetch calls.
|
|
6
|
+
*
|
|
7
|
+
* @param {string} [path] - API path relative to the server root (e.g. "api/users").
|
|
8
|
+
* @param {URL} [requestUrl] - Current request URL (pass `Astro.url` from the page). Required in production.
|
|
9
|
+
* @returns {string} Absolute URL ready for fetch.
|
|
10
|
+
*/
|
|
11
|
+
export function apiUrl(path = "", requestUrl) {
|
|
12
|
+
if (import.meta.env.DEV) {
|
|
13
|
+
return `http://${host}:${port}/${path}`;
|
|
14
|
+
}
|
|
15
|
+
if (!requestUrl) {
|
|
16
|
+
throw new Error("apiUrl: requestUrl is required in production (pass Astro.url)");
|
|
17
|
+
}
|
|
18
|
+
return new URL(`/${path}`, requestUrl).href;
|
|
19
|
+
}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
---
|
|
2
|
+
import { ClientRouter } from "astro:transitions";
|
|
3
|
+
|
|
4
|
+
const { title = "Blue Bird" } = Astro.props;
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
<html lang="en">
|
|
8
|
+
<head>
|
|
9
|
+
<meta charset="UTF-8" />
|
|
10
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
11
|
+
<title>{title}</title>
|
|
12
|
+
<link rel="stylesheet" href="/css/app.css" />
|
|
13
|
+
<ClientRouter />
|
|
14
|
+
</head>
|
|
15
|
+
<body
|
|
16
|
+
class="min-h-screen bg-slate-950 text-white font-sans antialiased selection:bg-blue-500 selection:text-white"
|
|
17
|
+
>
|
|
18
|
+
<slot />
|
|
19
|
+
</body>
|
|
20
|
+
</html>
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Layout from "../layouts/Layout.astro";
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
<Layout title="About - Blue Bird">
|
|
6
|
+
<header class="site-header">
|
|
7
|
+
<div class="nav-bar">
|
|
8
|
+
<a href="/" class="nav-brand">
|
|
9
|
+
<div class="brand-icon">
|
|
10
|
+
<svg width="20" height="20" fill="none" stroke="white" viewBox="0 0 24 24">
|
|
11
|
+
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
|
12
|
+
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8" />
|
|
13
|
+
</svg>
|
|
14
|
+
</div>
|
|
15
|
+
Blue Bird
|
|
16
|
+
</a>
|
|
17
|
+
<nav class="nav-links">
|
|
18
|
+
<a href="/">Home</a>
|
|
19
|
+
<a href="/about">About</a>
|
|
20
|
+
</nav>
|
|
21
|
+
</div>
|
|
22
|
+
</header>
|
|
23
|
+
|
|
24
|
+
<main class="container py-3xl">
|
|
25
|
+
<a href="/" class="btn btn-outline text-sm mb-2xl" style="width:fit-content;">
|
|
26
|
+
← Back to Home
|
|
27
|
+
</a>
|
|
28
|
+
|
|
29
|
+
<div class="card-glass py-3xl px-lg" style="max-width:52rem;">
|
|
30
|
+
<span class="badge mb-lg" style="display:inline-flex;">Framework</span>
|
|
31
|
+
<h1 class="gradient-text mt-md">About Blue Bird</h1>
|
|
32
|
+
<p class="mt-lg">
|
|
33
|
+
Blue Bird is an Express-based framework with native support for Astro frontend rendering.
|
|
34
|
+
It provides raw performance, developer efficiency, and a clean project layout — batteries
|
|
35
|
+
included for auth, validation, caching, Docker orchestration, and more.
|
|
36
|
+
</p>
|
|
37
|
+
|
|
38
|
+
<div class="grid-2 mt-2xl">
|
|
39
|
+
<div class="card">
|
|
40
|
+
<h4 class="mb-md">Express Backend</h4>
|
|
41
|
+
<p class="text-sm">REST APIs, JWT auth, rate limiting, CORS, and Helmet security — all preconfigured via a clean constructor API.</p>
|
|
42
|
+
</div>
|
|
43
|
+
<div class="card">
|
|
44
|
+
<h4 class="mb-md">Astro Frontend</h4>
|
|
45
|
+
<p class="text-sm">File-based routing, SSR, View Transitions, and island architecture. Use Tailwind or any CSS framework you prefer.</p>
|
|
46
|
+
</div>
|
|
47
|
+
</div>
|
|
48
|
+
</div>
|
|
49
|
+
</main>
|
|
50
|
+
|
|
51
|
+
<footer class="site-footer">
|
|
52
|
+
<p>Powered by Blue Bird Framework & Astro. All rights reserved.</p>
|
|
53
|
+
</footer>
|
|
54
|
+
</Layout>
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
---
|
|
2
|
+
import Layout from "../layouts/Layout.astro";
|
|
3
|
+
import { apiUrl } from "../http/api";
|
|
4
|
+
|
|
5
|
+
let apiData = null;
|
|
6
|
+
try {
|
|
7
|
+
const response = await fetch(apiUrl("api/", Astro.url));
|
|
8
|
+
apiData = await response.json();
|
|
9
|
+
console.log("[index.astro] /api/ response:", apiData);
|
|
10
|
+
} catch (err) {
|
|
11
|
+
console.error("[index.astro] fetch error:", err.message);
|
|
12
|
+
}
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
<Layout title="Home - Blue Bird">
|
|
16
|
+
<header class="site-header">
|
|
17
|
+
<div class="nav-bar">
|
|
18
|
+
<a href="/" class="nav-brand">
|
|
19
|
+
<div class="brand-icon">
|
|
20
|
+
<svg
|
|
21
|
+
width="20"
|
|
22
|
+
height="20"
|
|
23
|
+
fill="none"
|
|
24
|
+
stroke="white"
|
|
25
|
+
viewBox="0 0 24 24"
|
|
26
|
+
>
|
|
27
|
+
<path
|
|
28
|
+
stroke-linecap="round"
|
|
29
|
+
stroke-linejoin="round"
|
|
30
|
+
stroke-width="2"
|
|
31
|
+
d="M12 19l9 2-9-18-9 18 9-2zm0 0v-8"></path>
|
|
32
|
+
</svg>
|
|
33
|
+
</div>
|
|
34
|
+
Blue Bird
|
|
35
|
+
</a>
|
|
36
|
+
<nav class="nav-links">
|
|
37
|
+
<a href="/">Home</a>
|
|
38
|
+
<a href="/about">About</a>
|
|
39
|
+
</nav>
|
|
40
|
+
</div>
|
|
41
|
+
</header>
|
|
42
|
+
|
|
43
|
+
<main class="container py-3xl">
|
|
44
|
+
<div class="hero">
|
|
45
|
+
<div class="hero-glow hero-glow-1"></div>
|
|
46
|
+
<div class="hero-glow hero-glow-2"></div>
|
|
47
|
+
|
|
48
|
+
<div class="hero-inner">
|
|
49
|
+
<span class="badge mb-xl" style="display:inline-flex;"
|
|
50
|
+
>Welcome to Blue Bird with Astro</span
|
|
51
|
+
>
|
|
52
|
+
|
|
53
|
+
<h1 class="gradient-text mt-lg">
|
|
54
|
+
Hello, Astro Developer!<br />Welcome to the next level.
|
|
55
|
+
</h1>
|
|
56
|
+
|
|
57
|
+
<p class="mt-lg" style="max-width:40rem;margin-inline:auto;">
|
|
58
|
+
Blue Bird is a fast, structured, and opinionated Express.js framework
|
|
59
|
+
for developers who want visual elegance and performance, now powered
|
|
60
|
+
by Astro on the frontend.
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
<div class="flex justify-center gap-md mt-2xl flex-wrap">
|
|
64
|
+
<a href="/about" class="btn btn-primary">About Us →</a>
|
|
65
|
+
<a
|
|
66
|
+
href="https://www.npmjs.com/package/@seip/blue-bird"
|
|
67
|
+
target="_blank"
|
|
68
|
+
class="btn btn-outline"
|
|
69
|
+
>
|
|
70
|
+
npm package
|
|
71
|
+
</a>
|
|
72
|
+
</div>
|
|
73
|
+
</div>
|
|
74
|
+
</div>
|
|
75
|
+
|
|
76
|
+
<section class="grid-3 mt-2xl">
|
|
77
|
+
<div class="card">
|
|
78
|
+
<h4 class="gradient-text-blue mb-md">Astro SSR</h4>
|
|
79
|
+
<p>
|
|
80
|
+
Astro (v7.0) renders pages server-side via the Node adapter in
|
|
81
|
+
middleware mode, mounted inside Express.
|
|
82
|
+
</p>
|
|
83
|
+
</div>
|
|
84
|
+
<div class="card">
|
|
85
|
+
<h4 class="gradient-text-blue mb-md">AES-256-GCM Auth</h4>
|
|
86
|
+
<p>
|
|
87
|
+
JWT tokens encrypted with military-grade AES-256-GCM and served
|
|
88
|
+
through secure signed cookies.
|
|
89
|
+
</p>
|
|
90
|
+
</div>
|
|
91
|
+
<div class="card">
|
|
92
|
+
<h4 class="gradient-text-blue mb-md">Docker CLI</h4>
|
|
93
|
+
<p>
|
|
94
|
+
One-command orchestration for local MySQL, production stacks, and
|
|
95
|
+
container lifecycle management.
|
|
96
|
+
</p>
|
|
97
|
+
</div>
|
|
98
|
+
</section>
|
|
99
|
+
</main>
|
|
100
|
+
|
|
101
|
+
<footer class="site-footer">
|
|
102
|
+
<p>Powered by Blue Bird Framework & Astro. All rights reserved.</p>
|
|
103
|
+
</footer>
|
|
104
|
+
</Layout>
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import App from "
|
|
2
|
-
import
|
|
3
|
-
import routerFrontendExample from "./routes/frontend.js";
|
|
1
|
+
import App from "./core/app.js";
|
|
2
|
+
import routerApi from "./backend/routes/api.js";
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* Main entry point for the Blue Bird application.
|
|
7
6
|
* Initializes the App instance with routes, configuration, and starts the server.
|
|
8
7
|
*/
|
|
9
8
|
const app = new App({
|
|
10
|
-
routes: [
|
|
9
|
+
routes: [routerApi],
|
|
11
10
|
|
|
12
11
|
cors: [],
|
|
13
12
|
|
|
@@ -16,6 +15,14 @@ const app = new App({
|
|
|
16
15
|
host: "http://localhost",
|
|
17
16
|
|
|
18
17
|
port: process.env.PORT,
|
|
18
|
+
|
|
19
|
+
astro: {
|
|
20
|
+
server: true,
|
|
21
|
+
serverEntry: "./frontend/dist/server/entry.mjs",
|
|
22
|
+
client: true,
|
|
23
|
+
clientDir: "./frontend/dist/client",
|
|
24
|
+
base: "/",
|
|
25
|
+
},
|
|
19
26
|
});
|
|
20
27
|
|
|
21
28
|
app.run();
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@seip/blue-bird",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.0",
|
|
4
4
|
"description": "Express opinionated framework with HTML rendering, API architecture, built-in JWT auth, validation, caching, and SEO",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -25,8 +25,11 @@
|
|
|
25
25
|
},
|
|
26
26
|
"homepage": "https://seip25.github.io/Blue-bird/",
|
|
27
27
|
"scripts": {
|
|
28
|
-
"dev": "node --watch --env-file=.env
|
|
29
|
-
"
|
|
28
|
+
"dev": "node --watch --env-file=.env index.js",
|
|
29
|
+
"dev:astro": "astro dev --root frontend",
|
|
30
|
+
"dev:api": "node --watch --env-file=.env index.js",
|
|
31
|
+
"start": "node --env-file=.env index.js",
|
|
32
|
+
"build": "astro build --root frontend",
|
|
30
33
|
"init": "node core/cli/init.js",
|
|
31
34
|
"route": "node core/cli/route.js",
|
|
32
35
|
"swagger-install": "node core/cli/swagger.js",
|
|
@@ -38,13 +41,18 @@
|
|
|
38
41
|
"files": [
|
|
39
42
|
"core",
|
|
40
43
|
"backend",
|
|
41
|
-
"frontend",
|
|
44
|
+
"frontend/src",
|
|
45
|
+
"frontend/public",
|
|
46
|
+
"frontend/astro.config.mjs",
|
|
42
47
|
"docker",
|
|
43
48
|
"docker-compose.yml",
|
|
44
49
|
"AGENTS.md",
|
|
50
|
+
"index.js",
|
|
45
51
|
".env_example"
|
|
46
52
|
],
|
|
47
53
|
"dependencies": {
|
|
54
|
+
"@astrojs/node": "^11.0.0",
|
|
55
|
+
"astro": "^7.0.0",
|
|
48
56
|
"chalk": "^5.6.2",
|
|
49
57
|
"compression": "^1.8.1",
|
|
50
58
|
"cookie-parser": "^1.4.7",
|
|
@@ -1,39 +0,0 @@
|
|
|
1
|
-
import Router from "@seip/blue-bird/core/router.js";
|
|
2
|
-
import Template from "@seip/blue-bird/core/template.js";
|
|
3
|
-
import App from "@seip/blue-bird/core/app.js";
|
|
4
|
-
|
|
5
|
-
const routerFrontendExample = new Router("/", { seo: true });
|
|
6
|
-
|
|
7
|
-
//routerFrontendExample.use(App.helmet());
|
|
8
|
-
|
|
9
|
-
routerFrontendExample.get("/", (req, res) => {
|
|
10
|
-
return Template.render(res, "index", {
|
|
11
|
-
metaTags: {
|
|
12
|
-
titleMeta: "Home - Blue Bird",
|
|
13
|
-
descriptionMeta: "Welcome to Blue Bird Framework",
|
|
14
|
-
keywordsMeta: "blue bird, framework, express",
|
|
15
|
-
},
|
|
16
|
-
});
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
routerFrontendExample.get("/about", (req, res) => {
|
|
20
|
-
return Template.render(res, "about", {
|
|
21
|
-
metaTags: {
|
|
22
|
-
titleMeta: "About - Blue Bird",
|
|
23
|
-
descriptionMeta: "About Blue Bird Framework",
|
|
24
|
-
keywordsMeta: "about, blue bird, framework",
|
|
25
|
-
},
|
|
26
|
-
});
|
|
27
|
-
});
|
|
28
|
-
|
|
29
|
-
routerFrontendExample.get("/preact_example", (req, res) => {
|
|
30
|
-
return Template.render(res, "preact_example", {
|
|
31
|
-
metaTags: {
|
|
32
|
-
titleMeta: "Preact Example - Blue Bird",
|
|
33
|
-
descriptionMeta: "Preact Example Blue Bird Framework",
|
|
34
|
-
keywordsMeta: "preact, example, blue bird, framework",
|
|
35
|
-
},
|
|
36
|
-
});
|
|
37
|
-
});
|
|
38
|
-
|
|
39
|
-
export default routerFrontendExample;
|