@gjsify/example-node-express-webserver 0.1.8

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.
@@ -0,0 +1,75 @@
1
+ // Client-side script that fetches posts + runtime info from the Express API.
2
+ // Uses safe DOM APIs (textContent / createElement) — no innerHTML.
3
+
4
+ async function loadRuntime() {
5
+ const el = document.getElementById('runtime');
6
+ if (!el) return;
7
+ try {
8
+ const res = await fetch('/api/runtime');
9
+ const data = await res.json();
10
+ el.textContent = `Running on ${data.runtime}`;
11
+ } catch {
12
+ el.textContent = 'Runtime unknown';
13
+ }
14
+ }
15
+
16
+ function createPostElement(post) {
17
+ const article = document.createElement('article');
18
+ article.className = 'post';
19
+
20
+ const h2 = document.createElement('h2');
21
+ const link = document.createElement('a');
22
+ link.href = `/posts/${encodeURIComponent(post.slug)}`;
23
+ link.textContent = post.title;
24
+ h2.appendChild(link);
25
+ article.appendChild(h2);
26
+
27
+ const meta = document.createElement('p');
28
+ meta.className = 'meta';
29
+ const author = document.createElement('span');
30
+ author.className = 'author';
31
+ author.textContent = post.author;
32
+ const date = document.createElement('span');
33
+ date.className = 'date';
34
+ date.textContent = post.date;
35
+ meta.append(author, date);
36
+ article.appendChild(meta);
37
+
38
+ const excerpt = document.createElement('p');
39
+ excerpt.className = 'excerpt';
40
+ excerpt.textContent = post.excerpt;
41
+ article.appendChild(excerpt);
42
+
43
+ return article;
44
+ }
45
+
46
+ async function loadPosts() {
47
+ const container = document.getElementById('posts');
48
+ if (!container) return;
49
+ try {
50
+ const res = await fetch('/api/posts');
51
+ if (!res.ok) throw new Error(`HTTP ${res.status}`);
52
+ const data = await res.json();
53
+ const posts = data.posts || [];
54
+ container.replaceChildren();
55
+ if (posts.length === 0) {
56
+ const empty = document.createElement('p');
57
+ empty.className = 'loading';
58
+ empty.textContent = 'No posts yet.';
59
+ container.appendChild(empty);
60
+ return;
61
+ }
62
+ for (const post of posts) {
63
+ container.appendChild(createPostElement(post));
64
+ }
65
+ } catch (err) {
66
+ container.replaceChildren();
67
+ const msg = document.createElement('p');
68
+ msg.className = 'loading';
69
+ msg.textContent = `Failed to load posts: ${err.message}`;
70
+ container.appendChild(msg);
71
+ }
72
+ }
73
+
74
+ loadRuntime();
75
+ loadPosts();
@@ -0,0 +1,29 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>GJSify Blog — Express on GJS</title>
7
+ <link rel="stylesheet" href="/style.css" />
8
+ </head>
9
+ <body>
10
+ <header class="site-header">
11
+ <div class="brand">
12
+ <h1>GJSify Blog</h1>
13
+ <p class="tagline">Express.js, running natively on Linux via GJS</p>
14
+ </div>
15
+ <div class="runtime" id="runtime">Loading runtime…</div>
16
+ </header>
17
+
18
+ <main class="posts" id="posts">
19
+ <p class="loading">Loading posts…</p>
20
+ </main>
21
+
22
+ <footer class="site-footer">
23
+ <p>Served by <code>@gjsify/example-node-express-webserver</code></p>
24
+ <p class="hint">Try the API directly: <a href="/api/posts">/api/posts</a></p>
25
+ </footer>
26
+
27
+ <script src="/app.js" defer></script>
28
+ </body>
29
+ </html>
@@ -0,0 +1,232 @@
1
+ /* GJSify Blog — Adwaita-inspired theme */
2
+
3
+ :root {
4
+ --bg: #1d1d20;
5
+ --surface: #2e2e32;
6
+ --border: rgba(255, 255, 255, 0.08);
7
+ --text: #ffffff;
8
+ --muted: rgba(255, 255, 255, 0.55);
9
+ --accent: #78aeed;
10
+ --accent-strong: #3584e4;
11
+ --code-bg: rgba(120, 174, 237, 0.12);
12
+ --radius: 12px;
13
+ }
14
+
15
+ * {
16
+ box-sizing: border-box;
17
+ }
18
+
19
+ html, body {
20
+ margin: 0;
21
+ padding: 0;
22
+ background: var(--bg);
23
+ color: var(--text);
24
+ font-family: 'Adwaita Sans', 'Cantarell', -apple-system, sans-serif;
25
+ line-height: 1.6;
26
+ }
27
+
28
+ body {
29
+ max-width: 780px;
30
+ margin: 0 auto;
31
+ padding: 3rem 1.5rem 4rem;
32
+ }
33
+
34
+ /* --- Header --- */
35
+ .site-header {
36
+ display: flex;
37
+ align-items: flex-end;
38
+ justify-content: space-between;
39
+ gap: 1.5rem;
40
+ padding-bottom: 1.5rem;
41
+ margin-bottom: 2.5rem;
42
+ border-bottom: 1px solid var(--border);
43
+ }
44
+
45
+ .brand h1 {
46
+ margin: 0 0 0.25rem;
47
+ font-size: 2rem;
48
+ font-weight: 800;
49
+ letter-spacing: -0.02em;
50
+ background: linear-gradient(135deg, var(--accent) 0%, #9141ac 100%);
51
+ -webkit-background-clip: text;
52
+ background-clip: text;
53
+ color: transparent;
54
+ }
55
+
56
+ .tagline {
57
+ margin: 0;
58
+ color: var(--muted);
59
+ font-size: 0.95rem;
60
+ }
61
+
62
+ .runtime {
63
+ display: inline-flex;
64
+ align-items: center;
65
+ gap: 0.5rem;
66
+ padding: 0.4rem 0.85rem;
67
+ background: var(--surface);
68
+ border: 1px solid var(--border);
69
+ border-radius: 999px;
70
+ font-size: 0.8rem;
71
+ color: var(--muted);
72
+ white-space: nowrap;
73
+ }
74
+
75
+ .runtime::before {
76
+ content: '';
77
+ display: inline-block;
78
+ width: 8px;
79
+ height: 8px;
80
+ border-radius: 50%;
81
+ background: #33d17a;
82
+ box-shadow: 0 0 8px #33d17a;
83
+ }
84
+
85
+ /* --- Posts --- */
86
+ .posts {
87
+ display: flex;
88
+ flex-direction: column;
89
+ gap: 1.25rem;
90
+ }
91
+
92
+ .loading {
93
+ color: var(--muted);
94
+ text-align: center;
95
+ }
96
+
97
+ article.post {
98
+ padding: 1.5rem 1.75rem;
99
+ background: var(--surface);
100
+ border: 1px solid var(--border);
101
+ border-radius: var(--radius);
102
+ transition: transform 0.15s ease, border-color 0.15s ease;
103
+ }
104
+
105
+ article.post:hover {
106
+ transform: translateY(-2px);
107
+ border-color: rgba(120, 174, 237, 0.3);
108
+ }
109
+
110
+ article.post h2 {
111
+ margin: 0 0 0.5rem;
112
+ font-size: 1.3rem;
113
+ font-weight: 700;
114
+ }
115
+
116
+ article.post h2 a {
117
+ color: var(--text);
118
+ text-decoration: none;
119
+ }
120
+
121
+ article.post h2 a:hover {
122
+ color: var(--accent);
123
+ }
124
+
125
+ article.post .meta {
126
+ display: flex;
127
+ gap: 0.75rem;
128
+ margin: 0 0 0.75rem;
129
+ font-size: 0.825rem;
130
+ color: var(--muted);
131
+ }
132
+
133
+ article.post .meta .author {
134
+ color: var(--accent);
135
+ }
136
+
137
+ article.post .excerpt {
138
+ margin: 0;
139
+ color: rgba(255, 255, 255, 0.75);
140
+ }
141
+
142
+ /* --- Footer --- */
143
+ .site-footer {
144
+ margin-top: 3rem;
145
+ padding-top: 1.5rem;
146
+ border-top: 1px solid var(--border);
147
+ color: var(--muted);
148
+ font-size: 0.85rem;
149
+ text-align: center;
150
+ }
151
+
152
+ .site-footer p {
153
+ margin: 0.25rem 0;
154
+ }
155
+
156
+ .site-footer code {
157
+ padding: 0.15rem 0.4rem;
158
+ background: var(--code-bg);
159
+ border-radius: 4px;
160
+ font-size: 0.8rem;
161
+ color: var(--accent);
162
+ }
163
+
164
+ .site-footer a {
165
+ color: var(--accent);
166
+ text-decoration: none;
167
+ }
168
+
169
+ .site-footer a:hover {
170
+ text-decoration: underline;
171
+ }
172
+
173
+ .hint {
174
+ opacity: 0.75;
175
+ }
176
+
177
+ /* --- Brand link --- */
178
+ .brand-link {
179
+ text-decoration: none;
180
+ color: inherit;
181
+ }
182
+
183
+ /* --- Back link --- */
184
+ .back-link {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ gap: 0.4rem;
188
+ margin-bottom: 2rem;
189
+ color: var(--accent);
190
+ text-decoration: none;
191
+ font-size: 0.9rem;
192
+ font-weight: 500;
193
+ }
194
+
195
+ .back-link:hover {
196
+ text-decoration: underline;
197
+ }
198
+
199
+ /* --- Post detail --- */
200
+ .post-detail {
201
+ padding-bottom: 2rem;
202
+ }
203
+
204
+ .post-title {
205
+ margin: 0 0 0.5rem;
206
+ font-size: 2rem;
207
+ font-weight: 800;
208
+ letter-spacing: -0.02em;
209
+ line-height: 1.2;
210
+ }
211
+
212
+ .post-detail .meta {
213
+ display: flex;
214
+ gap: 0.75rem;
215
+ margin: 0 0 2rem;
216
+ font-size: 0.825rem;
217
+ color: var(--muted);
218
+ }
219
+
220
+ .post-detail .meta .author {
221
+ color: var(--accent);
222
+ }
223
+
224
+ .post-content p {
225
+ margin: 0 0 1.25rem;
226
+ color: rgba(255, 255, 255, 0.85);
227
+ line-height: 1.75;
228
+ }
229
+
230
+ .post-content p:last-child {
231
+ margin-bottom: 0;
232
+ }
package/package.json ADDED
@@ -0,0 +1,34 @@
1
+ {
2
+ "name": "@gjsify/example-node-express-webserver",
3
+ "version": "0.1.8",
4
+ "description": "Express.js blog showcase with JSON API and static frontend — a real Node.js web app running on GJS",
5
+ "main": "dist/index.gjs.js",
6
+ "type": "module",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "exports": {
11
+ "./package.json": "./package.json"
12
+ },
13
+ "scripts": {
14
+ "clear": "rm -rf dist tsconfig.tsbuildinfo",
15
+ "check": "tsc --noEmit --skipLibCheck --strictNullChecks false --noImplicitAny false",
16
+ "start": "gjsify run dist/index.gjs.js",
17
+ "start:node": "node dist/index.node.mjs",
18
+ "build": "yarn build:gjs && yarn build:node && yarn build:public",
19
+ "build:gjs": "gjsify build src/index.ts --app gjs --outfile dist/index.gjs.js --globals node,web",
20
+ "build:node": "gjsify build src/index.ts --app node --outfile dist/index.node.mjs",
21
+ "build:public": "mkdir -p dist/public && cp -r src/public/* dist/public/"
22
+ },
23
+ "devDependencies": {
24
+ "@gjsify/cli": "^0.1.8",
25
+ "@gjsify/node-globals": "^0.1.8",
26
+ "@gjsify/runtime": "^0.1.8",
27
+ "@types/express": "^5.0.6",
28
+ "@types/node": "^25.5.2",
29
+ "express": "^5.2.1",
30
+ "typescript": "^6.0.2"
31
+ },
32
+ "author": "Pascal Garber <pascal@artandcode.studio>",
33
+ "license": "MIT"
34
+ }