create-gardener 1.1.13 → 2.0.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.
Files changed (45) hide show
  1. package/package.json +1 -1
  2. package/starter.js +5 -2
  3. package/template/.env +0 -0
  4. package/template/buildHelper.js +3 -0
  5. package/template/jsconfig.json +27 -0
  6. package/template/package.json +6 -5
  7. package/template/src/backend/controllers/gardener/addComponent.ts +27 -0
  8. package/template/src/backend/controllers/gardener/addPage.ts +60 -0
  9. package/template/src/backend/controllers/gardener/createStatic.ts +84 -0
  10. package/template/src/backend/controllers/gardener/imageOptimiser.ts +67 -0
  11. package/template/src/backend/controllers/gardener/index.ts +4 -0
  12. package/template/src/backend/libs/generateWebp.ts +1 -0
  13. package/template/src/backend/routes/gardener.route.ts +15 -5
  14. package/template/src/backend/server.ts +8 -10
  15. package/template/src/frontend/assets/favicon.png +0 -0
  16. package/template/src/frontend/frontendtemplate.ejs +5 -17
  17. package/template/src/frontend/static/cache/favicon_500x500.webp +0 -0
  18. package/template/src/frontend/static/cache/favicon_50x50.webp +0 -0
  19. package/template/src/frontend/static/cache/gardener_50x50.webp +0 -0
  20. package/template/src/frontend/static/components/copybtn.js +86 -0
  21. package/template/src/frontend/static/components/nonui/api.js +33 -16
  22. package/template/src/frontend/static/components/nonui/navigation.js +59 -0
  23. package/template/src/frontend/static/components/secondtest.js +6 -0
  24. package/template/src/frontend/static/components/{emailsvg.js → testdd.js} +7 -7
  25. package/template/src/frontend/static/gardener.js +0 -388
  26. package/template/src/frontend/static/gardenerConfig.js +1 -0
  27. package/template/src/frontend/static/gardenerDev.js +408 -0
  28. package/template/src/frontend/static/global.js +2 -56
  29. package/template/src/frontend/static/pages/_.js +13 -0
  30. package/template/src/frontend/static/pages/_get-started.js +5 -0
  31. package/template/src/frontend/static/style.css +1039 -1
  32. package/template/src/frontend/static/style2.css +1 -1
  33. package/template/src/frontend/static/zod.js +8 -0
  34. package/template/src/frontend/views/_.ejs +96 -216
  35. package/template/src/frontend/views/_get-started.ejs +25 -0
  36. package/template/src/frontend/views/partials/icons/clipboard.ejs +1 -0
  37. package/template/src/frontend/views/partials/icons/clipboardok.ejs +1 -0
  38. package/template/tsconfig.json +1 -1
  39. package/template/src/backend/controllers/gardener.controller.ts +0 -193
  40. package/template/src/frontend/gardenerST.js +0 -431
  41. package/template/src/frontend/static/components/eyeoff.js +0 -50
  42. package/template/src/frontend/static/components/eyeon.js +0 -43
  43. package/template/src/frontend/static/components/passwordBox.js +0 -105
  44. package/template/src/frontend/views/_login.ejs +0 -75
  45. package/template/src/frontend/views/partials/loader.ejs +0 -3
@@ -1,234 +1,114 @@
1
1
  <!DOCTYPE html>
2
2
  <html lang="en">
3
- <head>
4
- <meta charset="UTF-8" />
5
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
- <title>Gardener Declarative DOM Library</title>
7
- <!-- Assume your custom styles are still here -->
8
- <link href="/static/style.css" rel="stylesheet"/>
9
- <link href="/static/style2.css" rel="stylesheet"/>
10
- </head>
11
-
12
- <body>
13
-
14
- <div id='body'>
15
-
16
- <!-- Remove this line when you are using hot reload as it will cause delay -->
17
- <%- include('partials/loader') %>
18
- <!-- Remove this line when you are using hot reload as it will cause delay -->
19
- <div class="max-w-7xl mx-auto px-6 py-12 space-y-20">
20
-
21
- <!-- Hero / Introduction -->
22
- <section class="bg-gradient-to-br from-emerald-50 to-cyan-50 rounded-3xl p-10 lg:p-16 shadow-xl">
23
- <div class="flex flex-col lg:flex-row items-center justify-between gap-12">
24
- <div class="max-w-2xl space-y-6">
25
- <h1 class="text-4xl lg:text-5xl font-bold text-slate-800">Gardener</h1>
26
- <p class="text-xl text-slate-700 leading-relaxed">
27
- Gardener is a lightweight front-end library for creating and manipulating DOM elements using a clean, declarative JavaScript object syntax.
28
- </p>
29
- <p class="text-lg text-slate-600 leading-relaxed">
30
- It comes with a development server featuring hot reload, on-the-fly component creation from existing HTML, dynamic image resizing & caching, and zero virtual DOM / JSX / build-step philosophy.
31
- </p>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <link rel="icon" type="image/svg+xml" href="/static/cache/favicon_50x50.webp" />
7
+ <link href="/static/style.css" rel="stylesheet"/>
8
+ <link href="/static/style2.css" rel="stylesheet"/>
9
+ <title>Gardener: The Mini Web Framework</title>
10
+ </head>
11
+ <body class="bg-slate-50 text-slate-900 font-sans">
12
+
13
+ <div class='loader w-screen h-screen bg-white fixed z-2'> </div>
14
+ <div class='notification'></div>
15
+
16
+ <div id='main'>
17
+
18
+
19
+ <!-- hero -->
20
+ <div class='hero flex-wrap w-full h-screen bg-green-200 flex justify-center items-center px-10'>
21
+ <div class='w-200'>
22
+ <a href="https://ritish.site">Home</a> > <a href="https://ritish.site/projects">Projects</a> > <a href="/">Gardener</a>
23
+ <h1 class='text-6xl my-10 font-bold text-green-900'>Gardener</h1>
24
+ <h2 class='text-2xl font-bold text-green-800 mb-4'>Develop With No Bloat</h2>
25
+ <p class="text-lg mb-6 max-w-md">Gardener is a lightweight, DOM-first front-end library. No virtual DOM, no JSX, and no compilation step. Just the real DOM.</p>
26
+ <a class="bg-green-700 text-white px-8 py-3 rounded-lg font-bold hover:bg-green-800 transition" href='/get-started'>Get Started</a>
27
+ <a class="border border-green-700 text-black px-8 py-3 rounded-lg font-bold hover:text-white hover:bg-green-800 transition" href='https://github.com/ritishDas/Gardener'>View On Github</a>
32
28
  </div>
33
-
34
- <img
35
- src="/static/cache/gardener_500x500.webp"
36
- alt="Gardener logo"
37
- class="w-64 lg:w-80 rounded-2xl shadow-2xl"
38
- />
29
+ <img src="/static/cache/gardener_500x500.webp" alt="Gardener Logo" class="w-96 h-96 object-contain ml-10">
39
30
  </div>
40
31
 
41
- <a href="/login" class='p-3 rounded-md bg-blue-400 inline-block'>Check A Demo Login Page</a>
42
- </section>
43
-
44
- <!-- Design Philosophy -->
45
- <section class="rounded-3xl p-10 lg:p-12 shadow-2xl space-y-6">
46
- <h2 class="text-3xl font-bold">Design Philosophy</h2>
47
- <ul class="list-disc list-inside space-y-3 text-lg">
48
- <li>No virtual DOM</li>
49
- <li>No JSX</li>
50
- <li>No compilation / build step</li>
51
- <li>DOM-first and fully deterministic</li>
52
- <li>Everything is explicit and inspectable in the browser</li>
53
- </ul>
54
- </section>
55
-
56
- <!-- Core API -->
57
- <section class="space-y-10">
58
- <h2 class="text-3xl font-bold text-center lg:text-left">Core Utilities</h2>
59
-
60
- <div class="grid md:grid-cols-2 gap-8">
61
- <div class="bg-white p-8 rounded-2xl shadow space-y-4">
62
- <h3 class="text-2xl font-semibold">DOM Helpers</h3>
63
- <ul class="list-disc list-inside space-y-2 text-slate-700">
64
- <li><code class="font-mono">fetchElement(selector)</code> — safe <code>querySelector</code> wrapper</li>
65
- <li><code>appendElement(parent, child)</code> — append child node</li>
66
- <li><code>replaceElement(oldElement, newElement)</code> — replace DOM node</li>
67
- <li><code>createElement(type, classes)</code> — low-level element factory</li>
68
- <li><code>imagePreloader(images)</code> — preload images efficiently</li>
32
+ <!-- Copy command -->
33
+ <div class="flex w-1/4 items-center mx-auto my-15 justify-between gap-4 bg-[#1e1e1e] border border-gray-700 rounded-lg px-4 py-3 font-mono shadow-sm group">
34
+ <span class="text-sm md:text-base text-gray-300 select-all">
35
+ <span class="text-emerald-400 mr-2">$</span><span class='initCommand'>pnpm create gardener app</span>
36
+ </span>
37
+
38
+ <button class="copybtn flex items-center justify-center p-2 text-gray-400 hover:text-white hover:bg-white/10 rounded transition-all duration-200 active:scale-95" title="Copy to clipboard">
39
+ <span class="w-5 h-5" >
40
+ <%- include('partials/icons/clipboard') %>
41
+ </span>
42
+ </button>
43
+ </div>
44
+ <!-- Philosophy -->
45
+ <div class="max-w-5xl mx-auto py-20 px-6 grid md:grid-cols-2 gap-12">
46
+ <div>
47
+ <h3 class="text-3xl font-bold mb-4 text-green-900">✨ Philosophy</h3>
48
+ <ul class="space-y-3 text-slate-700">
49
+ <li><strong>DOM-First:</strong> Renders directly to the real DOM.</li>
50
+ <li><strong>Deterministic:</strong> If you can inspect it, you can understand it.</li>
51
+ <li><strong>Native:</strong> Works in the browser via ES modules.</li>
52
+ <li><strong>Zero Build:</strong> No bundlers or magic required.</li>
69
53
  </ul>
70
54
  </div>
71
55
 
72
- <div class="bg-white p-8 rounded-2xl shadow space-y-4">
73
- <h3 class="text-2xl font-semibold">Gardener – The Declarative Builder</h3>
74
- <pre class="bg-slate-900 text-slate-100 text-sm p-5 rounded-xl overflow-x-auto font-mono">
75
- <code>gardener({
76
- t: 'div',
77
- cn: ['p-6', 'flex', 'gap-4'],
78
- attr: { id: 'hero', role: 'banner' },
79
- txt: 'Welcome',
80
- events: {
81
- click: () => console.log('clicked!')
82
- },
83
- children: [ /* nested objects */ ]
84
- })</code>
85
- </pre>
56
+ <!-- Features -->
57
+ <div>
58
+ <h3 class="text-3xl font-bold mb-4 text-green-900">🚀 Features</h3>
59
+ <div class="grid grid-cols-2 gap-4">
60
+ <div class="p-4 bg-white shadow-sm border border-green-100 rounded">Declarative UI</div>
61
+ <div class="p-4 bg-white shadow-sm border border-green-100 rounded">SVG Namespace Support</div>
62
+ <div class="p-4 bg-white shadow-sm border border-green-100 rounded">Hot Reload</div>
63
+ <div class="p-4 bg-white shadow-sm border border-green-100 rounded">Image Optimization</div>
64
+ </div>
86
65
  </div>
87
66
  </div>
88
67
 
89
- <div class="bg-white p-8 rounded-2xl shadow overflow-x-auto">
90
- <table class="w-full text-left border border-slate-200">
91
- <thead class="bg-slate-100">
92
- <tr>
93
- <th class="p-4 font-semibold">Key</th>
94
- <th class="p-4 font-semibold">Description</th>
95
- </tr>
96
- </thead>
97
- <tbody class="text-slate-700">
98
- <tr><td class="p-4 border-t"><code>t</code></td> <td class="p-4 border-t">HTML/SVG tag name</td></tr>
99
- <tr><td class="p-4 border-t"><code>cn</code></td> <td class="p-4 border-t">Array of class names</td></tr>
100
- <tr><td class="p-4 border-t"><code>attr</code></td> <td class="p-4 border-t">Attributes / properties object</td></tr>
101
- <tr><td class="p-4 border-t"><code>txt</code></td> <td class="p-4 border-t">Text content (string)</td></tr>
102
- <tr><td class="p-4 border-t"><code>events</code></td><td class="p-4 border-t">Event listeners <code>{ event: handler }</code></td></tr>
103
- <tr><td class="p-4 border-t"><code>children</code></td><td class="p-4 border-t">Array of nested gardener objects</td></tr>
104
- </tbody>
105
- </table>
106
- </div>
107
- </section>
108
-
109
- <!-- Runtime Configuration -->
110
- <section class="bg-white p-10 rounded-2xl shadow space-y-6">
111
- <h2 class="text-3xl font-bold">Runtime Configuration</h2>
112
- <pre class="bg-slate-900 text-slate-100 p-6 rounded-xl overflow-x-auto font-mono text-sm">
113
- <code>const config = {
114
- mode: 'dev', // 'dev' | 'prod'
115
- componentdir: 'components',
116
- hotreload: true
117
- }</code>
118
- </pre>
119
- <ul class="list-disc list-inside space-y-3 text-slate-700">
120
- <li><strong>mode</strong>: <code>dev</code> → enables parser, hot reload, dev UI; <code>prod</code> → disables everything</li>
121
- <li><strong>componentdir</strong>: folder for auto-generated component files</li>
122
- <li><strong>hotreload</strong>: persisted in localStorage</li>
123
- </ul>
124
- </section>
125
-
126
- <!-- Hot Reload -->
127
- <section class="bg-white p-10 rounded-2xl shadow space-y-6">
128
- <h2 class="text-3xl font-bold">Hot Reload (Dev Mode)</h2>
129
- <ul class="list-disc list-inside space-y-3 text-slate-700">
130
- <li>State saved in <code>localStorage</code></li>
131
- <li>Auto-reload after ~1 second when enabled</li>
132
- <li>Disabling cancels pending timers</li>
133
- </ul>
134
- <div class="inline-block bg-slate-100 px-5 py-3 rounded-xl mt-4">
135
- <p class="font-semibold">Toggle shortcut: <kbd class="bg-white px-2 py-1 rounded border border-slate-300">Ctrl + H</kbd></p>
136
- </div>
137
- </section>
138
-
139
- <!-- SVG Support -->
140
- <section class="bg-white p-10 rounded-2xl shadow space-y-6">
141
- <h2 class="text-3xl font-bold">SVG Support</h2>
142
- <p class="text-slate-700">
143
- Gardener automatically uses the SVG namespace for these tags:
144
- </p>
145
- <pre class="bg-slate-900 text-emerald-300 font-mono p-5 rounded-xl inline-block">
146
- <code>svg, path, circle, rect, line, polygon, polyline, g, defs, clipPath, use</code>
147
- </pre>
148
- <p class="text-slate-700">All attributes are applied via <code>setAttribute</code> safely.</p>
149
- </section>
150
-
151
- <!-- Creating & Using Components -->
152
- <section class="grid lg:grid-cols-2 gap-10">
153
- <div class="bg-white p-10 rounded-2xl shadow space-y-6">
154
- <h2 class="text-3xl font-bold">Creating Components</h2>
155
- <ol class="list-decimal list-inside space-y-4 text-slate-700">
156
- <li>Write normal HTML + classes (give parent an <code>id</code> or unique selector)</li>
157
- <li>Run: <code>parser(fetchElement('#your-id'))</code></li>
158
- <li>Popup appears → choose component name</li>
159
- <li>File saved to <code>/src/frontend/components/YourName.js</code></li>
160
- </ol>
161
- </div>
162
-
163
- <div class="bg-white p-10 rounded-2xl shadow space-y-6">
164
- <h2 class="text-3xl font-bold">Using Components</h2>
165
- <pre class="bg-slate-900 text-slate-100 p-6 rounded-xl font-mono text-sm overflow-x-auto">
166
- <code>import { myHeader } from '/components/myHeader.js'
167
-
168
- replaceElement(
169
- fetchElement('#header-placeholder'),
170
- myHeader()
171
- )</code>
172
- </pre>
173
- </div>
174
- </section>
175
-
176
- <!-- Image Optimization -->
177
- <section class="bg-white p-10 rounded-2xl shadow space-y-6">
178
- <h2 class="text-3xl font-bold">Image Optimization & Caching</h2>
179
- <ul class="list-disc list-inside space-y-3 text-slate-700">
180
- <li>Place originals in <code>/src/frontend/assets/</code></li>
181
- <li>Use: <code>&lt;img src="/static/cache/photo_800x600.webp"&gt;</code></li>
182
- </ul>
183
- <p class="text-slate-600 mt-4">
184
- Server auto-converts to WebP, resizes, and caches on demand.
185
- </p>
186
- </section>
187
-
188
- <!-- Development Tools -->
189
- <section class="space-y-10">
190
- <h2 class="text-3xl font-bold text-center">Development Tools</h2>
191
-
192
- <div class="grid md:grid-cols-2 gap-8">
193
- <div class="bg-white p-8 rounded-2xl shadow space-y-4">
194
- <h3 class="text-2xl font-semibold">Create New Page</h3>
195
- <ol class="list-decimal list-inside space-y-2 text-slate-700">
196
- <li>Click floating <strong>+</strong> button (dev mode only)</li>
197
- <li>Enter route (<code>/about</code>, etc.)</li>
198
- <li>New page created & browser navigates</li>
199
- </ol>
200
- <p class="text-sm text-slate-500 mt-4">
201
- Template: <code>/src/backend/frontendtemplate.ejs</code>
202
- </p>
68
+ <!-- Api Example -->
69
+ <div class="bg-slate-900 py-20 text-white">
70
+ <div class="max-w-5xl mx-auto px-6">
71
+ <h3 class="text-3xl font-bold mb-8 text-center text-green-400">The Core API</h3>
72
+ <div class="bg-slate-800 p-6 rounded-xl border border-slate-700 font-mono text-sm overflow-x-auto">
73
+ <pre class="text-blue-300">
74
+ gardener({
75
+ t: <span class="text-green-300">'div'</span>,
76
+ cn: [<span class="text-green-300">'p-6'</span>, <span class="text-green-300">'flex'</span>],
77
+ attr: { id: <span class="text-green-300">'hero'</span> },
78
+ txt: <span class="text-green-300">'Welcome to the Garden'</span>,
79
+ events: {
80
+ click: () => console.log(<span class="text-green-300">'Growth!'</span>)
81
+ },
82
+ children: [
83
+ { t: <span class="text-green-300">'span'</span>, txt: <span class="text-green-300">'Pure DOM nodes.'</span> }
84
+ ]
85
+ })
86
+ </pre>
87
+ </div>
203
88
  </div>
89
+ </div>
204
90
 
205
- <div class="bg-white p-8 rounded-2xl shadow space-y-4">
206
- <h3 class="text-2xl font-semibold">Generate Static Site</h3>
207
- <p class="text-slate-700">
208
- Visit <code>/createstatic</code> to build a fully static version in
209
- <code>/src/frontendStatic</code>
210
- </p>
91
+ <div class="max-w-5xl mx-auto py-20 px-6 text-center">
92
+ <h3 class="text-3xl font-bold mb-4 text-green-900">🧠 Simple State</h3>
93
+ <p class="mb-8 text-slate-600">No proxies. No diffing. Just clean, reactive callbacks.</p>
94
+ <div class="inline-block text-left bg-white p-6 rounded shadow-lg border-t-4 border-green-500 font-mono text-sm">
95
+ <p class="text-slate-500">// Initialize</p>
96
+ <p>const count = new State(0);</p>
97
+ <p class="text-slate-500 mt-2">// React</p>
98
+ <p>count.registerCb(val => updateUI(val));</p>
211
99
  </div>
212
100
  </div>
213
- </section>
214
-
215
- </div>
216
-
217
- <!-- <div class='test'> -->
218
- <!-- </div> -->
219
-
220
- <script src="/static/global.js" type="module"></script>
221
- <script type="module">
222
- import { gardener,parser, fetchElement,replaceElement } from "/static/gardener.js";
223
- // import parameterised from '/static/components/test.js';
224
-
225
- // parser('.test')
226
- // replaceElement('.test', parameterised())
227
101
 
102
+ <!-- Footer -->
103
+ <footer class="bg-green-900 text-green-100 py-12 text-center">
104
+ <p class="text-xl italic">"Because sometimes you don't need a forest. Just a garden."</p>
105
+ <div class="mt-6 text-sm opacity-70">
106
+ MIT Licensed | Built on Express & EJS
107
+ </div>
108
+ </footer>
109
+ </div>
228
110
 
229
- </script>
230
- <div>
231
-
232
- </body>
111
+ </body>
112
+ <script type='module' src='/static/global.js'> </script>
113
+ <script type='module' src='/static/pages/_.js'> </script>
233
114
  </html>
234
-
@@ -0,0 +1,25 @@
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.0">
6
+ <link rel="icon" type="image/svg+xml" href="/static/cache/favicon_50x50.webp" />
7
+ <link href="/static/style.css" rel="stylesheet"/>
8
+ <link href="/static/style2.css" rel="stylesheet"/>
9
+ <title>Gardener: Get Started</title>
10
+ </head>
11
+ <body>
12
+ <div class='loader w-screen h-screen bg-white fixed z-2'> </div>
13
+ <div class='notification'></div>
14
+ <div id='main'>
15
+
16
+
17
+
18
+
19
+ </body>
20
+
21
+ <script src="/static/pages/_get-started.js" type='module'></script>
22
+ <script type='module' src='/static/global.js'> </script>
23
+ </html>
24
+
25
+
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-clipboard"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 5a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2" /></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-clipboard-check"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 5h-2a2 2 0 0 0 -2 2v12a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-12a2 2 0 0 0 -2 -2h-2" /><path d="M9 5a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2a2 2 0 0 1 -2 2h-2a2 2 0 0 1 -2 -2" /><path d="M9 14l2 2l4 -4" /></svg>
@@ -38,6 +38,6 @@
38
38
  "isolatedModules": true,
39
39
  "noUncheckedSideEffectImports": true,
40
40
  "moduleDetection": "force",
41
- "skipLibCheck": true,
41
+ "skipLibCheck": true
42
42
  }
43
43
  }
@@ -1,193 +0,0 @@
1
- import type { Request, Response } from "express";
2
- import fs, { readFile, readFileSync } from "fs";
3
- import path from "path";
4
- import ejs from "ejs";
5
- import fsp from "fs/promises";
6
- import generateWebP from "../libs/generateWebp.js";
7
- import { fileURLToPath } from "url";
8
- const availableCache: Record<string, boolean> = {};
9
-
10
-
11
- const __filename = fileURLToPath(import.meta.url);
12
- const __dirname = path.dirname(__filename);
13
-
14
- interface AddComponentBody {
15
- path: string;
16
- component: string;
17
- }
18
-
19
- export async function addComponent(req: Request<{}, {}, AddComponentBody>, res: Response) {
20
- try {
21
- const { path: filePath, component } = req.body;
22
-
23
- await fsp.mkdir('./src/frontend/static/components', { recursive: true });
24
-
25
- fs.writeFileSync(`./src/frontend/${filePath}`, component, "utf8");
26
-
27
- res.json({ success: true });
28
- } catch (err) {
29
- const error = err as Error;
30
- res.json({ success: false, msg: error.message });
31
- }
32
- }
33
-
34
-
35
- export async function imageOptimiser(req: Request, res: Response) {
36
- try {
37
- const { name } = req.params;
38
-
39
- if (typeof name !== 'string') return;
40
- // name format: test_500x300.webp
41
- const match = name.match(/^(.+?)_(\d+)x(\d+)\.webp$/);
42
-
43
- if (!match) {
44
- return res.status(400).json({ error: "Invalid image format" });
45
- }
46
-
47
- const [, baseName, widthStr, heightStr] = match;
48
-
49
- if (!widthStr || !heightStr) return;
50
- const width = parseInt(widthStr, 10);
51
- const height = parseInt(heightStr, 10);
52
-
53
- const cacheDir = path.join(__dirname, "../../frontend/static/cache");
54
- await fsp.mkdir(cacheDir, { recursive: true });
55
-
56
- const outputPath = path.join(cacheDir, name);
57
-
58
- // 1️⃣ Return cached file if exists
59
- try {
60
- await fsp.access(outputPath);
61
- return res.sendFile(path.basename(outputPath), {
62
- root: path.dirname(outputPath),
63
- });
64
- } catch {
65
- // not cached → continue
66
- }
67
-
68
- // 2️⃣ Find source image with same base name
69
- const assetsDir = path.resolve("./src/frontend/assets");
70
- const files = await fsp.readdir(assetsDir);
71
-
72
- const sourceFile = files.find((file) => {
73
- const parsed = path.parse(file);
74
- return parsed.name === baseName;
75
- });
76
-
77
- if (!sourceFile) {
78
- return res.status(404).json({ error: "Source image not found" });
79
- }
80
-
81
- const inputPath = path.join(assetsDir, sourceFile);
82
-
83
- // 3️⃣ Generate optimized WebP
84
- await generateWebP(inputPath, outputPath, width, height);
85
-
86
- // 4️⃣ Return generated file
87
- return res.sendFile(path.basename(outputPath), {
88
- root: path.dirname(outputPath),
89
- });
90
- } catch (err) {
91
- console.error(err);
92
- return res.status(500).json({ error: "Image optimisation failed" });
93
- }
94
- }
95
-
96
- export async function addPage(req: Request, res: Response) {
97
- try {
98
- const pagename: string = req.body.page;
99
- const buffer = readFileSync('./src/frontend/frontendtemplate.ejs', 'utf8');
100
- const name = pagename.replaceAll('/', '_');
101
-
102
- fs.writeFileSync(`./src/frontend/views/${name}.ejs`, buffer, "utf8");
103
-
104
- fs.appendFileSync('./src/backend/routes/gardener.route.ts', ` router.route("${pagename}").get((req: Request, res: Response) => res.render("${name}"))\n `);
105
-
106
- await fsp.mkdir('src/frontend/static/pages', { recursive: true });
107
-
108
- fs.writeFileSync(`./src/frontend/static/pages/${name}.js`, 'import { log, parser, fetchElement, replaceElement, appendElement, State, addEL } from "/static/gardener.js";', "utf8");
109
- res.json({ success: true });
110
- }
111
- catch (err) {
112
- const error = err as Error;
113
- res.json({ success: false, msg: error.message });
114
- }
115
-
116
- }
117
-
118
-
119
- export async function createStatic(req: Request, res: Response) {
120
- try {
121
- const viewsDir = path.resolve("src/frontend/views");
122
- const outDir = path.resolve("src/tempfrontend");
123
- const finalOut = path.resolve("src/frontendStatic");
124
-
125
- await fsp.mkdir(outDir, { recursive: true });
126
- await fsp.mkdir(finalOut, { recursive: true });
127
-
128
- const entries = await fsp.readdir(viewsDir, { withFileTypes: true });
129
-
130
- const rendered: string[] = [];
131
-
132
-
133
- for (const entry of entries) {
134
- // skip folders (partials, layouts, etc.)
135
- if (!entry.isFile()) continue;
136
- if (!entry.name.endsWith(".ejs")) continue;
137
-
138
- const inputPath = path.join(viewsDir, entry.name);
139
- const outputName = entry.name.replace(/\.ejs$/, ".html");
140
- const outputPath = path.join(outDir, outputName);
141
-
142
- const html = await ejs.renderFile(
143
- inputPath,
144
- {
145
- },
146
- {
147
- // async: true,
148
- views: [viewsDir], // needed for includes
149
- }
150
- );
151
-
152
- await fsp.writeFile(outputPath, html, "utf8");
153
- rendered.push(outputName);
154
- }
155
-
156
- const entries3 = await fsp.readdir(outDir, { withFileTypes: true });
157
- for (const entry of entries3) {
158
-
159
- // "_path1_path2_path3.html" -> ["path1", "path2", "path3"]
160
- const parts = entry.name
161
- .replace(/^_/, "")
162
- .replace(/\.html$/, "")
163
- .split("_");
164
-
165
- const targetDir = path.join(finalOut, ...parts);
166
- const targetFile = path.join(targetDir, "index.html");
167
-
168
- // ensure directories exist
169
- await fsp.mkdir(targetDir, { recursive: true });
170
- console.log('done');
171
- // copy file
172
- await fsp.copyFile(path.join(outDir, entry.name), targetFile);
173
-
174
- }
175
- await fsp.rm(outDir, { recursive: true, force: true });
176
- await fsp.cp(
177
- path.resolve("src/frontend/static"),
178
- path.join(finalOut, 'static'),
179
- { recursive: true }
180
- );
181
-
182
- return res.json({
183
- success: true,
184
- generated: rendered,
185
- outDir,
186
- });
187
- } catch (err) {
188
- console.error(err);
189
- return res.status(500).json({ error: "Static build failed" });
190
- }
191
- }
192
-
193
-