create-gardener 2.0.9 → 2.1.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/README.md +328 -140
- package/package.json +1 -1
- package/template/build/backend/controllers/gardener/addComponent.d.ts +8 -0
- package/template/build/backend/controllers/gardener/addComponent.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/addComponent.js +19 -0
- package/template/build/backend/controllers/gardener/addComponent.js.map +1 -0
- package/template/build/backend/controllers/gardener/addPage.d.ts +3 -0
- package/template/build/backend/controllers/gardener/addPage.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/addPage.js +76 -0
- package/template/build/backend/controllers/gardener/addPage.js.map +1 -0
- package/template/build/backend/controllers/gardener/createStatic.d.ts +3 -0
- package/template/build/backend/controllers/gardener/createStatic.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/createStatic.js +61 -0
- package/template/build/backend/controllers/gardener/createStatic.js.map +1 -0
- package/template/build/backend/controllers/gardener/imageOptimiser.d.ts +3 -0
- package/template/build/backend/controllers/gardener/imageOptimiser.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/imageOptimiser.js +54 -0
- package/template/build/backend/controllers/gardener/imageOptimiser.js.map +1 -0
- package/template/build/backend/controllers/gardener/index.d.ts +6 -0
- package/template/build/backend/controllers/gardener/index.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/index.js +6 -0
- package/template/build/backend/controllers/gardener/index.js.map +1 -0
- package/template/build/backend/controllers/gardener/saveTemplate.d.ts +3 -0
- package/template/build/backend/controllers/gardener/saveTemplate.d.ts.map +1 -0
- package/template/build/backend/controllers/gardener/saveTemplate.js +36 -0
- package/template/build/backend/controllers/gardener/saveTemplate.js.map +1 -0
- package/template/build/backend/libs/generateWebp.d.ts +2 -0
- package/template/build/backend/libs/generateWebp.d.ts.map +1 -0
- package/template/build/backend/libs/generateWebp.js +18 -0
- package/template/build/backend/libs/generateWebp.js.map +1 -0
- package/template/build/backend/routes/gardener.route.d.ts +4 -0
- package/template/build/backend/routes/gardener.route.d.ts.map +1 -0
- package/template/build/backend/routes/gardener.route.js +13 -0
- package/template/build/backend/routes/gardener.route.js.map +1 -0
- package/template/build/backend/server.d.ts +2 -0
- package/template/build/backend/server.d.ts.map +1 -0
- package/template/build/backend/server.js +20 -0
- package/template/build/backend/server.js.map +1 -0
- package/template/build/frontend/assets/favicon.png +0 -0
- package/template/build/frontend/assets/gardener.jpg +0 -0
- package/template/build/frontend/static/cache/favicon_500x500.webp +0 -0
- package/template/build/frontend/static/cache/favicon_50x50.webp +0 -0
- package/template/build/frontend/static/cache/gardener_500x500.webp +0 -0
- package/template/build/frontend/static/cache/gardener_50x50.webp +0 -0
- package/template/build/frontend/static/components/copybtn.js +86 -0
- package/template/build/frontend/static/components/nonui/api.js +39 -0
- package/template/build/frontend/static/components/nonui/navigation.js +59 -0
- package/template/build/frontend/static/components/notification.js +67 -0
- package/template/build/frontend/static/gardener.js +89 -0
- package/template/build/frontend/static/gardener.test.js +364 -0
- package/template/build/frontend/static/gardenerConfig.js +1 -0
- package/template/build/frontend/static/gardenerDev.js +499 -0
- package/template/build/frontend/static/global.js +4 -0
- package/template/build/frontend/static/pages/pages._.js +20 -0
- package/template/build/frontend/static/style.css +2 -0
- package/template/build/frontend/static/style2.css +26 -0
- package/template/build/frontend/static/zod.js +8 -0
- package/template/build/frontend/style.css +1045 -0
- package/template/build/frontend/tailwind.css +1 -0
- package/template/build/frontend/views/_.ejs +121 -0
- package/template/build/frontend/views/partials/icons/clipboard.ejs +1 -0
- package/template/build/frontend/views/partials/icons/clipboardok.ejs +1 -0
- package/template/src/backend/controllers/gardener/addPage.ts +25 -21
- package/template/src/backend/controllers/gardener/createStatic.ts +1 -0
- package/template/src/backend/libs/generateWebp.ts +0 -2
- package/template/src/frontend/static/cache/gardener_100x100.webp +0 -0
- package/template/src/frontend/static/components/copybtn.js +16 -3
- package/template/src/frontend/static/components/footer.js +33 -0
- package/template/src/frontend/static/components/gardener/errorBox.js +47 -0
- package/template/src/frontend/static/components/gardener/hotReloadbtn.js +82 -0
- package/template/src/frontend/static/components/gardener/pageOverlayBtn.js +138 -0
- package/template/src/frontend/static/components/gardener/parserWindow.js +159 -0
- package/template/src/frontend/static/components/nonui/api.js +15 -2
- package/template/src/frontend/static/gardener.js +129 -58
- package/template/src/frontend/static/gardenerDev.js +65 -399
- package/template/src/frontend/static/global.js +1 -1
- package/template/src/frontend/static/pages/pages._.js +5 -0
- package/template/src/frontend/static/style.css +101 -0
- package/template/src/frontend/static/style2.css +2 -2
- package/template/src/frontend/template/template._.ejs +121 -0
package/README.md
CHANGED
|
@@ -1,187 +1,375 @@
|
|
|
1
|
-
# Gardener
|
|
1
|
+
# 🌱 Gardener
|
|
2
2
|
|
|
3
|
-
A
|
|
3
|
+
A full-stack web framework that combines Express.js backend with EJS templating and a unique DOM-to-JSON component system. Build web apps with a visual-first approach where you can convert any DOM element into reusable components with a single function call.
|
|
4
4
|
|
|
5
|
-
## 🚀
|
|
5
|
+
## 🚀 Quick Start
|
|
6
6
|
|
|
7
|
-
|
|
8
|
-
- **EJS Templating** - Server-side rendering with EJS view engine
|
|
9
|
-
- **Tailwind CSS** - Utility-first CSS framework with JIT compilation
|
|
10
|
-
- **Hot Reload Development** - Live reload for both backend and frontend during development
|
|
11
|
-
- **Production Ready** - Optimized build process with minification
|
|
12
|
-
- **Testing Setup** - Jest configuration for unit and integration tests
|
|
13
|
-
- **Image Processing** - Sharp integration for image optimization
|
|
14
|
-
- **Validation** - Zod schema validation
|
|
15
|
-
- **Environment Configuration** - dotenv support for environment variables
|
|
7
|
+
Create a new Gardener app:
|
|
16
8
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
- pnpm (preferred) or npm
|
|
9
|
+
```bash
|
|
10
|
+
pnpm create gardener app
|
|
11
|
+
```
|
|
21
12
|
|
|
22
|
-
|
|
13
|
+
Install dependencies and start development:
|
|
23
14
|
|
|
24
15
|
```bash
|
|
25
|
-
# Install dependencies
|
|
26
16
|
pnpm install
|
|
27
|
-
|
|
28
|
-
npm install
|
|
17
|
+
pnpm dev
|
|
29
18
|
```
|
|
30
19
|
|
|
31
|
-
|
|
20
|
+
Your app will be running with hot reload enabled!
|
|
32
21
|
|
|
33
|
-
|
|
22
|
+
## 🌟 Core Features
|
|
34
23
|
|
|
35
|
-
|
|
24
|
+
### Backend
|
|
25
|
+
- **Express.js**: Familiar Node.js backend with full Express capabilities
|
|
26
|
+
- **TypeScript Ready**: Built-in TypeScript support for type-safe development
|
|
27
|
+
- **Static Site Generation**: Export your dynamic app to static HTML with one API call
|
|
36
28
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
29
|
+
### Frontend
|
|
30
|
+
- **EJS Templating**: Server-side rendering with EJS views
|
|
31
|
+
- **Tailwind CSS**: Integrated with watch mode for rapid styling
|
|
32
|
+
- **Gardener Component System**: Unique DOM-to-JSON conversion for reusable components
|
|
33
|
+
- **Live Development Tools**: Browser-based route and component creation
|
|
34
|
+
|
|
35
|
+
### Developer Experience
|
|
36
|
+
- **Hot Reload**: Toggle with `Alt + H` for instant CSS updates
|
|
37
|
+
- **Visual Component Parser**: Convert DOM elements to JSON components in the browser
|
|
38
|
+
- **Browser-Based Routing**: Create new routes without touching your code
|
|
39
|
+
- **Image Optimization**: Automatic WebP conversion with smart sizing
|
|
40
|
+
|
|
41
|
+
## 📖 Complete Workflow
|
|
42
|
+
|
|
43
|
+
### 1. Backend Development
|
|
44
|
+
|
|
45
|
+
Write familiar Express.js code in TypeScript:
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// src/backend/server.ts
|
|
49
|
+
app.get('/api/posts', (req, res) => {
|
|
50
|
+
res.json({ posts: [] });
|
|
51
|
+
});
|
|
41
52
|
```
|
|
42
53
|
|
|
43
|
-
|
|
44
|
-
- Start the TypeScript backend server with watch mode on port 3000 (or PORT from .env)
|
|
45
|
-
- Run Tailwind CSS in watch mode for live style updates
|
|
54
|
+
### 2. Creating Pages & Routes
|
|
46
55
|
|
|
47
|
-
|
|
56
|
+
**Two ways to create routes:**
|
|
48
57
|
|
|
49
|
-
|
|
58
|
+
#### Browser Method (Recommended for Development)
|
|
59
|
+
1. Click the "**New Page**" button that appears in development mode
|
|
60
|
+
2. Enter your route path (e.g., `/about`, `/blog/post`)
|
|
61
|
+
3. Press Enter - the route and EJS file are created automatically!
|
|
50
62
|
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
63
|
+
#### Manual Method
|
|
64
|
+
Create an EJS file in `src/frontend/views/` and add the Express route manually.
|
|
65
|
+
|
|
66
|
+
### 3. Working with Components
|
|
67
|
+
|
|
68
|
+
Gardener has **two component types**:
|
|
69
|
+
|
|
70
|
+
#### A. EJS Partials (Traditional)
|
|
71
|
+
Static components that render once:
|
|
72
|
+
```ejs
|
|
73
|
+
<%- include('partials/header') %>
|
|
55
74
|
```
|
|
56
75
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
#### B. Gardener Components (Dynamic)
|
|
77
|
+
JSON-based components with the Gardener system:
|
|
78
|
+
|
|
79
|
+
**Creating a Component:**
|
|
80
|
+
1. Write your HTML structure in an EJS file or browser
|
|
81
|
+
2. In your JavaScript file, call:
|
|
82
|
+
```javascript
|
|
83
|
+
import { parser } from '/static/gardenerDev.js';
|
|
84
|
+
|
|
85
|
+
parser('.my-component-selector');
|
|
86
|
+
```
|
|
87
|
+
3. A window appears in the browser with the JSON representation
|
|
88
|
+
4. Name and save the component
|
|
89
|
+
5. The component is now reusable!
|
|
90
|
+
|
|
91
|
+
**Using a Component:**
|
|
92
|
+
```javascript
|
|
93
|
+
import { myComponent } from '/static/components/myComponent.js';
|
|
94
|
+
import { gardener, appendElement } from '/static/gardener.js';
|
|
95
|
+
|
|
96
|
+
// Render the component
|
|
97
|
+
const element = gardener(myComponent);
|
|
98
|
+
appendElement('#container', element);
|
|
99
|
+
```
|
|
62
100
|
|
|
63
|
-
|
|
101
|
+
**Component Structure:**
|
|
102
|
+
```javascript
|
|
103
|
+
// Gardener components are JSON objects
|
|
104
|
+
export const button = {
|
|
105
|
+
t: 'button', // tag
|
|
106
|
+
cn: ['btn', 'primary'], // classNames
|
|
107
|
+
txt: 'Click me', // text content
|
|
108
|
+
attr: { id: 'submit' }, // attributes
|
|
109
|
+
events: { click: handleClick }, // event handlers
|
|
110
|
+
children: [...] // nested components
|
|
111
|
+
};
|
|
112
|
+
```
|
|
64
113
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
114
|
+
### 4. Parameterized Components
|
|
115
|
+
|
|
116
|
+
Create dynamic components with parameters using `??`:
|
|
117
|
+
|
|
118
|
+
```javascript
|
|
119
|
+
// In your component definition, wrap dynamic parts with ??
|
|
120
|
+
const card = {
|
|
121
|
+
t: 'div',
|
|
122
|
+
cn: ['card'],
|
|
123
|
+
children: [
|
|
124
|
+
{ t: 'h2', txt: '??title??' },
|
|
125
|
+
{ t: 'p', txt: '??description??' }
|
|
126
|
+
]
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
// Use with parameters
|
|
130
|
+
const myCard = gardener(card, {
|
|
131
|
+
title: 'Hello',
|
|
132
|
+
description: 'World'
|
|
133
|
+
});
|
|
69
134
|
```
|
|
70
135
|
|
|
71
|
-
|
|
136
|
+
### 5. Template System
|
|
72
137
|
|
|
73
|
-
**
|
|
138
|
+
**Save Current Page as Template:**
|
|
139
|
+
- Click "**Save Template**" button in the browser
|
|
140
|
+
- This saves the current page structure as a template
|
|
141
|
+
- Used automatically for deeper routes (e.g., `/blog/` template for `/blog/post-1`)
|
|
74
142
|
|
|
75
|
-
|
|
143
|
+
### 6. Dynamic Routes & Parameters
|
|
76
144
|
|
|
77
|
-
|
|
145
|
+
For parameterized routes like `/post/:id`:
|
|
78
146
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
147
|
+
**Backend:**
|
|
148
|
+
```javascript
|
|
149
|
+
app.get('/post/:id', (req, res) => {
|
|
150
|
+
res.render('post', {
|
|
151
|
+
id: req.params.id,
|
|
152
|
+
title: 'My Post'
|
|
153
|
+
});
|
|
154
|
+
});
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
**Frontend (EJS):**
|
|
158
|
+
```ejs
|
|
159
|
+
<h1>Post <%= id %></h1>
|
|
160
|
+
<p><%= title %></p>
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
**Query Parameters:**
|
|
164
|
+
```javascript
|
|
165
|
+
// Backend
|
|
166
|
+
app.get('/search', (req, res) => {
|
|
167
|
+
const query = req.query.q;
|
|
168
|
+
res.render('search', { query });
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
// Frontend EJS
|
|
172
|
+
<p>Searching for: <%= query %></p>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
### 7. Hot Reload for Styling
|
|
176
|
+
|
|
177
|
+
Press **`Alt + H`** to toggle hot reload mode:
|
|
178
|
+
- Automatically refreshes CSS changes
|
|
179
|
+
- No page reload needed
|
|
180
|
+
- Perfect for rapid styling iterations
|
|
181
|
+
|
|
182
|
+
### 8. Image Optimization
|
|
183
|
+
|
|
184
|
+
Use the built-in image optimizer:
|
|
185
|
+
|
|
186
|
+
```html
|
|
187
|
+
<img src="/static/image_800x600.webp" alt="Optimized">
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Format: `/static/image_{width}x{height}.webp`
|
|
191
|
+
|
|
192
|
+
The server automatically:
|
|
193
|
+
- Converts to WebP format
|
|
194
|
+
- Resizes to specified dimensions
|
|
195
|
+
- Caches for performance
|
|
196
|
+
|
|
197
|
+
### 9. Static Site Generation
|
|
198
|
+
|
|
199
|
+
Generate a static version of your entire app:
|
|
200
|
+
|
|
201
|
+
```javascript
|
|
202
|
+
// Make a GET request
|
|
203
|
+
fetch('/createStatic')
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
Or visit `http://localhost:3000/createStatic` in your browser.
|
|
207
|
+
|
|
208
|
+
Your static site is generated in the `/build` directory!
|
|
209
|
+
|
|
210
|
+
## 🧩 API Reference
|
|
211
|
+
|
|
212
|
+
### Gardener Core (`/static/gardener.js`)
|
|
213
|
+
|
|
214
|
+
```javascript
|
|
215
|
+
import {
|
|
216
|
+
gardener, // Convert JSON to DOM elements
|
|
217
|
+
fetchElement, // Query selector wrapper
|
|
218
|
+
appendElement, // Append child with error handling
|
|
219
|
+
createElement, // Create element with classes
|
|
220
|
+
insertText, // Set text content
|
|
221
|
+
replaceElement // Replace element in DOM
|
|
222
|
+
} from '/static/gardener.js';
|
|
223
|
+
|
|
224
|
+
// Create element from JSON
|
|
225
|
+
const el = gardener({
|
|
226
|
+
t: 'div',
|
|
227
|
+
cn: ['container'],
|
|
228
|
+
attr: { id: 'main' },
|
|
229
|
+
children: [...]
|
|
230
|
+
});
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
### Gardener Dev Tools (`/static/gardenerDev.js`)
|
|
234
|
+
|
|
235
|
+
```javascript
|
|
236
|
+
import {
|
|
237
|
+
parser, // Convert DOM to JSON
|
|
238
|
+
parserWindow, // Show parser UI
|
|
239
|
+
State, // Reactive state management
|
|
240
|
+
addEl // Add event listener helper
|
|
241
|
+
} from '/static/gardenerDev.js';
|
|
242
|
+
|
|
243
|
+
// Reactive State
|
|
244
|
+
const count = new State(0);
|
|
245
|
+
count.registerCb((value) => {
|
|
246
|
+
console.log('Count:', value);
|
|
247
|
+
});
|
|
248
|
+
count.setTo(1); // Triggers callback
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Navigation (`/static/components/nonui/navigation.js`)
|
|
252
|
+
|
|
253
|
+
```javascript
|
|
254
|
+
import {
|
|
255
|
+
nextPage, // Navigate with animation
|
|
256
|
+
nextPagehandler, // Set up link handlers
|
|
257
|
+
pageloader // Handle page loader
|
|
258
|
+
} from '/static/components/nonui/navigation.js';
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
### API Utilities (`/static/components/nonui/api.js`)
|
|
262
|
+
|
|
263
|
+
```javascript
|
|
264
|
+
import { Fetch } from '/static/components/nonui/api.js';
|
|
265
|
+
|
|
266
|
+
// Make API requests
|
|
267
|
+
const response = await Fetch('/api/data', { key: 'value' }, 'POST');
|
|
268
|
+
const data = await response.json();
|
|
83
269
|
```
|
|
84
270
|
|
|
85
271
|
## 📁 Project Structure
|
|
86
272
|
|
|
87
273
|
```
|
|
88
|
-
|
|
274
|
+
your-app/
|
|
89
275
|
├── src/
|
|
90
|
-
│ ├── backend/
|
|
91
|
-
│ │ ├──
|
|
92
|
-
│ │ ├── routes/
|
|
93
|
-
│ │
|
|
94
|
-
│
|
|
95
|
-
│
|
|
96
|
-
│
|
|
97
|
-
│ ├── static/
|
|
98
|
-
│ ├──
|
|
99
|
-
│ ├──
|
|
100
|
-
│
|
|
101
|
-
|
|
102
|
-
├──
|
|
103
|
-
├──
|
|
104
|
-
|
|
105
|
-
├──
|
|
106
|
-
└── .
|
|
107
|
-
```
|
|
108
|
-
|
|
109
|
-
##
|
|
110
|
-
|
|
111
|
-
###
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
-
|
|
133
|
-
-
|
|
134
|
-
|
|
135
|
-
##
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
276
|
+
│ ├── backend/
|
|
277
|
+
│ │ ├── server.ts # Main server file
|
|
278
|
+
│ │ ├── routes/ # Express routes
|
|
279
|
+
│ │ └── controllers/ # Route controllers
|
|
280
|
+
│ └── frontend/
|
|
281
|
+
│ ├── views/ # EJS templates
|
|
282
|
+
│ │ └── partials/ # EJS partials
|
|
283
|
+
│ ├── static/ # Client-side JS
|
|
284
|
+
│ │ ├── gardener.js # Core framework
|
|
285
|
+
│ │ ├── gardenerDev.js # Dev tools
|
|
286
|
+
│ │ ├── components/ # Gardener components
|
|
287
|
+
│ │ └── pages/ # Page-specific JS
|
|
288
|
+
│ ├── template/ # Page templates
|
|
289
|
+
│ ├── style.css # Compiled Tailwind
|
|
290
|
+
│ └── tailwind.css # Tailwind source
|
|
291
|
+
├── build/ # Static site output
|
|
292
|
+
└── package.json
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
## 🎯 Key Concepts
|
|
296
|
+
|
|
297
|
+
### 1. DOM-to-JSON Parser
|
|
298
|
+
The `parser()` function converts any DOM element into a JSON representation that can be saved as a reusable component. This allows you to:
|
|
299
|
+
- Build UI visually in the browser
|
|
300
|
+
- Extract components without manual coding
|
|
301
|
+
- Create a library of reusable elements
|
|
302
|
+
|
|
303
|
+
### 2. Component Composition
|
|
304
|
+
Components are composable JSON objects. Build complex UIs by nesting components:
|
|
305
|
+
|
|
306
|
+
```javascript
|
|
307
|
+
const page = {
|
|
308
|
+
t: 'div',
|
|
309
|
+
children: [
|
|
310
|
+
header,
|
|
311
|
+
mainContent,
|
|
312
|
+
footer
|
|
313
|
+
]
|
|
314
|
+
};
|
|
315
|
+
```
|
|
316
|
+
|
|
317
|
+
### 3. Development vs Production
|
|
318
|
+
- **Development**: Full dev tools, component parser, hot reload
|
|
319
|
+
- **Production**: Minimal runtime, optimized assets, static export option
|
|
320
|
+
|
|
321
|
+
## 🔧 Configuration
|
|
322
|
+
|
|
323
|
+
Edit `src/frontend/static/gardenerConfig.js`:
|
|
324
|
+
|
|
325
|
+
```javascript
|
|
326
|
+
export const mode = 'dev'; // or 'production'
|
|
327
|
+
```
|
|
328
|
+
|
|
329
|
+
## 📚 Documentation
|
|
330
|
+
|
|
331
|
+
Visit [gardener.ritish.site](https://gardener.ritish.site) for full documentation and examples.
|
|
332
|
+
|
|
333
|
+
## 💡 Examples
|
|
334
|
+
|
|
335
|
+
### Creating a Blog Post Component
|
|
336
|
+
|
|
337
|
+
```javascript
|
|
338
|
+
// 1. Create HTML in your EJS file
|
|
339
|
+
<div class="post">
|
|
340
|
+
<h2 class="title">?title?</h2>
|
|
341
|
+
<p class="content">?content?</p>
|
|
342
|
+
</div>
|
|
343
|
+
|
|
344
|
+
<div class="post2">
|
|
345
|
+
</div>
|
|
346
|
+
|
|
347
|
+
// 2. Parse it by calling
|
|
348
|
+
parser('.post');
|
|
349
|
+
|
|
350
|
+
// 3. accept the component by giving a name in browser
|
|
351
|
+
|
|
352
|
+
// 4. Use it
|
|
353
|
+
|
|
354
|
+
replaceElement('.post2', component({title:'new component', content:'this is the new content'}))
|
|
355
|
+
|
|
356
|
+
```
|
|
167
357
|
|
|
168
358
|
## 🤝 Contributing
|
|
169
359
|
|
|
170
|
-
|
|
360
|
+
Contributions are welcome! Visit the [GitHub repository](https://github.com/ritishDas/gardener).
|
|
171
361
|
|
|
172
362
|
## 📄 License
|
|
173
363
|
|
|
174
|
-
MIT
|
|
364
|
+
MIT License - See LICENSE file for details.
|
|
175
365
|
|
|
176
366
|
## 👤 Author
|
|
177
367
|
|
|
178
|
-
ritishDas
|
|
179
|
-
|
|
180
|
-
## 🔗 Links
|
|
368
|
+
**ritishDas**
|
|
181
369
|
|
|
182
|
-
- [
|
|
183
|
-
- [
|
|
370
|
+
- GitHub: [@ritishDas](https://github.com/ritishDas)
|
|
371
|
+
- Website: [gardener.ritish.site](https://gardener.ritish.site)
|
|
184
372
|
|
|
185
373
|
---
|
|
186
374
|
|
|
187
|
-
|
|
375
|
+
Built with ❤️ for developers who want to move fast without breaking things.
|
package/package.json
CHANGED
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { Request, Response } from "express";
|
|
2
|
+
interface AddComponentBody {
|
|
3
|
+
path: string;
|
|
4
|
+
component: string;
|
|
5
|
+
}
|
|
6
|
+
export declare function addComponent(req: Request<{}, {}, AddComponentBody>, res: Response): Promise<void>;
|
|
7
|
+
export {};
|
|
8
|
+
//# sourceMappingURL=addComponent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addComponent.d.ts","sourceRoot":"","sources":["../../../../src/backend/controllers/gardener/addComponent.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAIjD,UAAU,gBAAgB;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,SAAS,EAAE,MAAM,CAAC;CACnB;AAQD,wBAAsB,YAAY,CAAC,GAAG,EAAE,OAAO,CAAC,EAAE,EAAE,EAAE,EAAE,gBAAgB,CAAC,EAAE,GAAG,EAAE,QAAQ,iBAWvF"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import fsp from "fs/promises";
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from "url";
|
|
4
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
5
|
+
const __dirname = path.dirname(__filename);
|
|
6
|
+
const frontendDir = path.resolve(__dirname, '..', '..', '..', 'frontend');
|
|
7
|
+
export async function addComponent(req, res) {
|
|
8
|
+
try {
|
|
9
|
+
const { path: filePath, component } = req.body;
|
|
10
|
+
await fsp.mkdir(path.join(frontendDir, 'static', 'components'), { recursive: true });
|
|
11
|
+
await fsp.writeFile(path.join(frontendDir, `${filePath}`), component, "utf8");
|
|
12
|
+
res.json({ success: true });
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
const error = err;
|
|
16
|
+
res.json({ success: false, msg: error.message });
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
//# sourceMappingURL=addComponent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addComponent.js","sourceRoot":"","sources":["../../../../src/backend/controllers/gardener/addComponent.ts"],"names":[],"mappings":"AACA,OAAO,GAAG,MAAM,aAAa,CAAC;AAC9B,OAAO,IAAI,MAAM,MAAM,CAAC;AAOxB,OAAO,EAAE,aAAa,EAAE,MAAM,KAAK,CAAC;AACpC,MAAM,UAAU,GAAG,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;AAClD,MAAM,SAAS,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;AAE3C,MAAM,WAAW,GAAG,IAAI,CAAC,OAAO,CAAC,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,CAAC,CAAC;AAE1E,MAAM,CAAC,KAAK,UAAU,YAAY,CAAC,GAAsC,EAAE,GAAa;IACtF,IAAI,CAAC;QACH,MAAM,EAAE,IAAI,EAAE,QAAQ,EAAE,SAAS,EAAE,GAAG,GAAG,CAAC,IAAI,CAAC;QAC/C,MAAM,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,QAAQ,EAAE,YAAY,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACrF,MAAM,GAAG,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,GAAG,QAAQ,EAAE,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,CAAC;QAC9E,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,CAAC;IAE9B,CAAC;IAAC,OAAO,GAAG,EAAE,CAAC;QACb,MAAM,KAAK,GAAG,GAAY,CAAC;QAC3B,GAAG,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,KAAK,EAAE,GAAG,EAAE,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC;IACnD,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addPage.d.ts","sourceRoot":"","sources":["../../../../src/backend/controllers/gardener/addPage.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AA2CjD,wBAAsB,OAAO,CAAC,GAAG,EAAE,OAAO,EAAE,GAAG,EAAE,QAAQ,iBAkCxD"}
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import fsp from "fs/promises";
|
|
2
|
+
import { access } from "fs/promises";
|
|
3
|
+
import path from 'path';
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
6
|
+
const __dirname = path.dirname(__filename);
|
|
7
|
+
const frontendDir = path.resolve(__dirname, '..', '..', '..', 'frontend');
|
|
8
|
+
const templateDir = path.join(frontendDir, 'template');
|
|
9
|
+
async function findTemplate(fileName) {
|
|
10
|
+
while (fileName.length !== 0) {
|
|
11
|
+
console.log(fileName);
|
|
12
|
+
const searchFile = `template.${fileName}.ejs`;
|
|
13
|
+
console.log(searchFile);
|
|
14
|
+
const searchPath = path.join(templateDir, searchFile);
|
|
15
|
+
console.log(searchPath);
|
|
16
|
+
try {
|
|
17
|
+
await access(searchPath); // ✅ checks if file exists
|
|
18
|
+
return searchPath; // return full path immediately
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
// file does not exist → continue
|
|
22
|
+
}
|
|
23
|
+
let lastUnderscore = fileName.lastIndexOf('_');
|
|
24
|
+
if (lastUnderscore === -1)
|
|
25
|
+
break;
|
|
26
|
+
if (lastUnderscore === 0)
|
|
27
|
+
lastUnderscore += 1;
|
|
28
|
+
fileName = fileName.substring(0, lastUnderscore);
|
|
29
|
+
console.log(fileName);
|
|
30
|
+
}
|
|
31
|
+
// ❗ explicit failure instead of silent bug
|
|
32
|
+
throw new Error("Template not found");
|
|
33
|
+
}
|
|
34
|
+
export async function addPage(req, res) {
|
|
35
|
+
try {
|
|
36
|
+
const pagename = req.body.page;
|
|
37
|
+
const name = pagename.replaceAll('/', '_');
|
|
38
|
+
const templatePath = await findTemplate(name); //path.join(frontendDir, findTemplate(name)); //path.join(frontendDir, 'frontendtemplate.ejs');
|
|
39
|
+
const viewPath = path.join(frontendDir, `views`, `${name}.ejs`);
|
|
40
|
+
const routePath = path.resolve(__dirname, '..', '..', 'routes', 'gardener.route.ts');
|
|
41
|
+
const jsDir = path.join(frontendDir, 'static/pages');
|
|
42
|
+
const jsFilePath = path.join(jsDir, `pages.${name}.js`);
|
|
43
|
+
const templateContent = await fsp.readFile(templatePath, 'utf8');
|
|
44
|
+
await fsp.writeFile(viewPath, templateContent, "utf8");
|
|
45
|
+
await replaceLastOccurrence(viewPath, '<script', `<script src="/static/pages/pages.${name}.js" type='module'></script>`);
|
|
46
|
+
const routeEntry = `router.route("${pagename}").get((req: Request, res: Response) => res.render("${name}",{fileName:"${name}"}));\n`;
|
|
47
|
+
await fsp.appendFile(routePath, routeEntry, "utf8");
|
|
48
|
+
await fsp.mkdir(jsDir, { recursive: true });
|
|
49
|
+
const jsContent = 'import { gardener, fetchElement, replaceElement, appendElement } from "/static/gardener.js";\n import {log, parser, addEl, State} from "/static/gardenerDev.js"';
|
|
50
|
+
await fsp.writeFile(jsFilePath, jsContent, "utf8");
|
|
51
|
+
res.json({ success: true });
|
|
52
|
+
}
|
|
53
|
+
catch (err) {
|
|
54
|
+
const error = err;
|
|
55
|
+
res.json({ success: false, msg: error.message });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
async function replaceLastOccurrence(filePath, searchPattern, replacementLine) {
|
|
59
|
+
const content = await fsp.readFile(filePath, 'utf8');
|
|
60
|
+
const lines = content.split('\n');
|
|
61
|
+
let found = false;
|
|
62
|
+
for (let i = lines.length - 1; i >= 0; i--) {
|
|
63
|
+
if (lines[i].includes(searchPattern)) {
|
|
64
|
+
lines[i] = `${replacementLine}\n${lines[i]}`;
|
|
65
|
+
found = true;
|
|
66
|
+
break;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (found) {
|
|
70
|
+
await fsp.writeFile(filePath, lines.join('\n'), 'utf8');
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
console.warn(`Pattern "${searchPattern}" not found in ${filePath}`);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
//# sourceMappingURL=addPage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"addPage.js","sourceRoot":"","sources":["../../../../src/backend/controllers/gardener/addPage.ts"],"names":[],"mappings":"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"}
|