create-gardener 1.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.
- package/.envrc +1 -0
- package/LICENSE +9 -0
- package/Readme.md +349 -0
- package/flake.lock +61 -0
- package/flake.nix +23 -0
- package/package.json +34 -0
- package/starter.js +73 -0
- package/template/Readme.md +349 -0
- package/template/package.json +34 -0
- package/template/src/backend/cache/w_500x500.webp +0 -0
- package/template/src/backend/controllers/gardener.controller.ts +211 -0
- package/template/src/backend/frontendtemplate.ejs +26 -0
- package/template/src/backend/libs/generateWebp.ts +26 -0
- package/template/src/backend/routes/gardener.route.ts +19 -0
- package/template/src/backend/server.ts +30 -0
- package/template/src/frontend/assets/w.webp +0 -0
- package/template/src/frontend/components/test.js +54 -0
- package/template/src/frontend/gardener.js +404 -0
- package/template/src/frontend/global.js +57 -0
- package/template/src/frontend/style.css +481 -0
- package/template/src/frontend/tailwind.css +1 -0
- package/template/src/frontend/views/_.ejs +26 -0
- package/template/src/frontend/views/partials/loader.ejs +3 -0
- package/template/tailwind.config.js +0 -0
- package/template/tsconfig.json +43 -0
package/.envrc
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
use flake
|
package/LICENSE
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 ritishDas
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
6
|
+
|
|
7
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
8
|
+
|
|
9
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
package/Readme.md
ADDED
|
@@ -0,0 +1,349 @@
|
|
|
1
|
+
# Gardener 🌱
|
|
2
|
+
|
|
3
|
+
**Gardener** is a small development toolkit and micro-framework for building websites with **declarative DOM JSON**, server-rendered templates, and a **custom static site generation pipeline**.
|
|
4
|
+
|
|
5
|
+
It is designed for developers who want:
|
|
6
|
+
|
|
7
|
+
* full control over HTML structure
|
|
8
|
+
* minimal abstractions
|
|
9
|
+
* a fast local dev experience
|
|
10
|
+
* a deterministic static output for production
|
|
11
|
+
|
|
12
|
+
Gardener sits somewhere between a tiny framework and a build system.
|
|
13
|
+
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
## What Gardener Includes
|
|
17
|
+
|
|
18
|
+
### Core
|
|
19
|
+
|
|
20
|
+
* 🌿 **gardener.js** — declarative DOM builder using JSON objects
|
|
21
|
+
* 🔁 **parser** — convert real DOM elements back into gardener-compatible JSON
|
|
22
|
+
* 📄 **EJS** for simple server-rendered views
|
|
23
|
+
* 🎨 **Tailwind CSS** for fast styling
|
|
24
|
+
|
|
25
|
+
### Dev Server
|
|
26
|
+
|
|
27
|
+
* Express-based development server
|
|
28
|
+
* Hot reload toggle (Ctrl + H)
|
|
29
|
+
* Endpoints to create pages and components at runtime (dev convenience)
|
|
30
|
+
|
|
31
|
+
### Images
|
|
32
|
+
|
|
33
|
+
* Deterministic image optimization endpoint
|
|
34
|
+
* Sharp-powered resize + WebP conversion
|
|
35
|
+
* Filesystem cache reused during static builds
|
|
36
|
+
|
|
37
|
+
### Static Site Generation (SSG)
|
|
38
|
+
|
|
39
|
+
* Render EJS views into HTML
|
|
40
|
+
* Convert route-encoded filenames into nested directories
|
|
41
|
+
* Merge frontend assets and image cache
|
|
42
|
+
* Clean temporary build artifacts
|
|
43
|
+
* Produce a deployable static directory
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Project Structure
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
src/
|
|
51
|
+
├── backend/
|
|
52
|
+
│ ├── routes/
|
|
53
|
+
│ ├── controllers/
|
|
54
|
+
│ ├── libs/
|
|
55
|
+
│ ├── cache/ # generated image cache (build artifact)
|
|
56
|
+
│ └── server.ts
|
|
57
|
+
│
|
|
58
|
+
├── frontend/
|
|
59
|
+
│ ├── views/ # EJS templates (source)
|
|
60
|
+
│ ├── assets/ # original images
|
|
61
|
+
│ ├── components/
|
|
62
|
+
│ ├── gardener.js
|
|
63
|
+
│ └── styles/
|
|
64
|
+
│
|
|
65
|
+
├── frontendStatic/ # final static output (generated)
|
|
66
|
+
└── tempfrontend/ # temporary build output (deleted after build)
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
---
|
|
70
|
+
|
|
71
|
+
## Requirements
|
|
72
|
+
|
|
73
|
+
* Node.js v16+ (v18+ recommended)
|
|
74
|
+
* pnpm (recommended) or npm
|
|
75
|
+
* Optional: PostgreSQL
|
|
76
|
+
|
|
77
|
+
---
|
|
78
|
+
|
|
79
|
+
## Quickstart (Development)
|
|
80
|
+
|
|
81
|
+
### 1. Install
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/ritishDas/Gardener.git
|
|
85
|
+
cd Gardener
|
|
86
|
+
pnpm install
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### 2. Run dev server
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
pnpm run dev
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
* Server runs at **[http://localhost:3000](http://localhost:3000)**
|
|
96
|
+
* Tailwind watcher and TypeScript server run together
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## Image Optimization & Caching
|
|
101
|
+
|
|
102
|
+
Gardener provides a **deterministic image optimization endpoint**.
|
|
103
|
+
|
|
104
|
+
### Route
|
|
105
|
+
|
|
106
|
+
```
|
|
107
|
+
GET /cache/:name
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
### Filename format
|
|
111
|
+
|
|
112
|
+
```
|
|
113
|
+
<basename>_<width>x<height>.webp
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
### Example
|
|
117
|
+
|
|
118
|
+
```http
|
|
119
|
+
GET /cache/hero_500x300.webp
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
HTML usage:
|
|
123
|
+
|
|
124
|
+
```html
|
|
125
|
+
<img src="/cache/hero_500x300.webp" alt="hero" />
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
---
|
|
129
|
+
|
|
130
|
+
### How it works
|
|
131
|
+
|
|
132
|
+
1. Parses filename to extract:
|
|
133
|
+
|
|
134
|
+
* base name
|
|
135
|
+
* width
|
|
136
|
+
* height
|
|
137
|
+
2. Checks cache:
|
|
138
|
+
|
|
139
|
+
```
|
|
140
|
+
src/backend/cache/
|
|
141
|
+
```
|
|
142
|
+
3. If cached → return immediately
|
|
143
|
+
4. If not cached:
|
|
144
|
+
|
|
145
|
+
* Finds source image in:
|
|
146
|
+
|
|
147
|
+
```
|
|
148
|
+
src/frontend/assets/
|
|
149
|
+
```
|
|
150
|
+
* Resizes and converts to WebP (Sharp)
|
|
151
|
+
* Stores result in cache
|
|
152
|
+
5. Serves the optimized image
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Static Build Integration
|
|
157
|
+
|
|
158
|
+
During static generation:
|
|
159
|
+
|
|
160
|
+
* All cached images under:
|
|
161
|
+
|
|
162
|
+
```
|
|
163
|
+
src/backend/cache/
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
are copied into:
|
|
167
|
+
|
|
168
|
+
```
|
|
169
|
+
src/frontendStatic/
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
Static HTML can safely reference:
|
|
173
|
+
|
|
174
|
+
```html
|
|
175
|
+
<img src="/cache/hero_500x300.webp" />
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
No runtime image processing is required in production.
|
|
179
|
+
|
|
180
|
+
---
|
|
181
|
+
|
|
182
|
+
## Static Site Generation
|
|
183
|
+
|
|
184
|
+
Gardener includes a custom static build pipeline.
|
|
185
|
+
|
|
186
|
+
### What it does
|
|
187
|
+
|
|
188
|
+
1. Renders EJS views into HTML
|
|
189
|
+
2. Writes temporary files using route-encoded filenames
|
|
190
|
+
(example: `_blog_posts_hello.html`)
|
|
191
|
+
3. Converts them into directory-based routes:
|
|
192
|
+
|
|
193
|
+
```
|
|
194
|
+
blog/posts/hello/index.html
|
|
195
|
+
```
|
|
196
|
+
4. Copies frontend assets
|
|
197
|
+
5. Copies image cache
|
|
198
|
+
6. Deletes temporary build directory
|
|
199
|
+
|
|
200
|
+
### Output
|
|
201
|
+
|
|
202
|
+
```
|
|
203
|
+
src/frontendStatic/
|
|
204
|
+
├── index.html
|
|
205
|
+
├── blog/
|
|
206
|
+
│ └── posts/
|
|
207
|
+
│ └── hello/
|
|
208
|
+
│ └── index.html
|
|
209
|
+
├── assets/
|
|
210
|
+
└── cache/
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
This directory is ready for:
|
|
214
|
+
|
|
215
|
+
* static hosting
|
|
216
|
+
* CDN deployment
|
|
217
|
+
* Nginx / Caddy / Netlify / Vercel
|
|
218
|
+
|
|
219
|
+
---
|
|
220
|
+
|
|
221
|
+
## Frontend API — `gardener.js`
|
|
222
|
+
|
|
223
|
+
File: `src/frontend/gardener.js`
|
|
224
|
+
|
|
225
|
+
### `gardener(obj)`
|
|
226
|
+
|
|
227
|
+
Create DOM elements from JSON.
|
|
228
|
+
|
|
229
|
+
```js
|
|
230
|
+
const el = gardener({
|
|
231
|
+
t: 'div',
|
|
232
|
+
cn: ['card', 'p-4'],
|
|
233
|
+
children: [
|
|
234
|
+
{ t: 'h2', txt: 'Title' },
|
|
235
|
+
{ t: 'p', txt: 'Content' }
|
|
236
|
+
]
|
|
237
|
+
});
|
|
238
|
+
|
|
239
|
+
document.body.appendChild(el);
|
|
240
|
+
```
|
|
241
|
+
|
|
242
|
+
---
|
|
243
|
+
|
|
244
|
+
### `parser(elementOrHtml, isParent = true)`
|
|
245
|
+
|
|
246
|
+
Convert DOM into gardener JSON.
|
|
247
|
+
|
|
248
|
+
```js
|
|
249
|
+
const json = parser(document.querySelector('.hero'));
|
|
250
|
+
console.log(json);
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
---
|
|
254
|
+
|
|
255
|
+
### `parserWindow(text)`
|
|
256
|
+
|
|
257
|
+
Dev-only UI:
|
|
258
|
+
|
|
259
|
+
* Preview parsed JSON
|
|
260
|
+
* Press **Y** to create a component file
|
|
261
|
+
|
|
262
|
+
---
|
|
263
|
+
|
|
264
|
+
### Utilities
|
|
265
|
+
|
|
266
|
+
* `imagePreloader(images)`
|
|
267
|
+
* `fetchElement(selector)`
|
|
268
|
+
* `appendElement(parent, child)`
|
|
269
|
+
* `replaceElement(original, newElem)`
|
|
270
|
+
* `createElement(type, classname)`
|
|
271
|
+
|
|
272
|
+
---
|
|
273
|
+
|
|
274
|
+
## Dev-Only Endpoints ⚠️
|
|
275
|
+
|
|
276
|
+
### `POST /addcomponent`
|
|
277
|
+
|
|
278
|
+
Creates a frontend component file.
|
|
279
|
+
|
|
280
|
+
```json
|
|
281
|
+
{
|
|
282
|
+
"path": "components/MyComp.js",
|
|
283
|
+
"component": "{ t: 'div', txt: 'Hello' }"
|
|
284
|
+
}
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
Writes directly to the filesystem.
|
|
288
|
+
|
|
289
|
+
---
|
|
290
|
+
|
|
291
|
+
### `POST /addpage`
|
|
292
|
+
|
|
293
|
+
Creates an EJS page and registers a route.
|
|
294
|
+
|
|
295
|
+
```json
|
|
296
|
+
{ "page": "/my-page" }
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
* Generates a new EJS file
|
|
300
|
+
* Appends a route to the backend router
|
|
301
|
+
|
|
302
|
+
---
|
|
303
|
+
|
|
304
|
+
## Security Notes
|
|
305
|
+
|
|
306
|
+
⚠️ **Important**
|
|
307
|
+
|
|
308
|
+
* `/addcomponent` and `/addpage` mutate files and routes
|
|
309
|
+
* Intended for **local development only**
|
|
310
|
+
* Do NOT expose publicly without authentication and sanitization
|
|
311
|
+
|
|
312
|
+
---
|
|
313
|
+
|
|
314
|
+
## Troubleshooting
|
|
315
|
+
|
|
316
|
+
* **Server not starting**
|
|
317
|
+
|
|
318
|
+
* Check `.env`
|
|
319
|
+
* Ensure PostgreSQL is running (if enabled)
|
|
320
|
+
* Inspect logs from `pnpm run dev`
|
|
321
|
+
|
|
322
|
+
* **Images not loading**
|
|
323
|
+
|
|
324
|
+
* Ensure source image exists in `src/frontend/assets`
|
|
325
|
+
* Filename must match `<name>_<width>x<height>.webp`
|
|
326
|
+
|
|
327
|
+
* **Tailwind not updating**
|
|
328
|
+
|
|
329
|
+
* Ensure `pnpm install` completed successfully
|
|
330
|
+
|
|
331
|
+
---
|
|
332
|
+
|
|
333
|
+
## Contributing
|
|
334
|
+
|
|
335
|
+
This is a small personal/dev-focused toolkit.
|
|
336
|
+
|
|
337
|
+
Contributions are welcome:
|
|
338
|
+
|
|
339
|
+
* bug fixes
|
|
340
|
+
* documentation improvements
|
|
341
|
+
* build pipeline enhancements
|
|
342
|
+
* security hardening
|
|
343
|
+
|
|
344
|
+
---
|
|
345
|
+
|
|
346
|
+
## License
|
|
347
|
+
|
|
348
|
+
MIT
|
|
349
|
+
|
package/flake.lock
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
{
|
|
2
|
+
"nodes": {
|
|
3
|
+
"flake-utils": {
|
|
4
|
+
"inputs": {
|
|
5
|
+
"systems": "systems"
|
|
6
|
+
},
|
|
7
|
+
"locked": {
|
|
8
|
+
"lastModified": 1731533236,
|
|
9
|
+
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
|
10
|
+
"owner": "numtide",
|
|
11
|
+
"repo": "flake-utils",
|
|
12
|
+
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
|
13
|
+
"type": "github"
|
|
14
|
+
},
|
|
15
|
+
"original": {
|
|
16
|
+
"owner": "numtide",
|
|
17
|
+
"repo": "flake-utils",
|
|
18
|
+
"type": "github"
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
"nixpkgs": {
|
|
22
|
+
"locked": {
|
|
23
|
+
"lastModified": 1769018530,
|
|
24
|
+
"narHash": "sha256-MJ27Cy2NtBEV5tsK+YraYr2g851f3Fl1LpNHDzDX15c=",
|
|
25
|
+
"owner": "NixOS",
|
|
26
|
+
"repo": "nixpkgs",
|
|
27
|
+
"rev": "88d3861acdd3d2f0e361767018218e51810df8a1",
|
|
28
|
+
"type": "github"
|
|
29
|
+
},
|
|
30
|
+
"original": {
|
|
31
|
+
"owner": "NixOS",
|
|
32
|
+
"ref": "nixos-unstable",
|
|
33
|
+
"repo": "nixpkgs",
|
|
34
|
+
"type": "github"
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"root": {
|
|
38
|
+
"inputs": {
|
|
39
|
+
"flake-utils": "flake-utils",
|
|
40
|
+
"nixpkgs": "nixpkgs"
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
"systems": {
|
|
44
|
+
"locked": {
|
|
45
|
+
"lastModified": 1681028828,
|
|
46
|
+
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
|
47
|
+
"owner": "nix-systems",
|
|
48
|
+
"repo": "default",
|
|
49
|
+
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
|
50
|
+
"type": "github"
|
|
51
|
+
},
|
|
52
|
+
"original": {
|
|
53
|
+
"owner": "nix-systems",
|
|
54
|
+
"repo": "default",
|
|
55
|
+
"type": "github"
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
},
|
|
59
|
+
"root": "root",
|
|
60
|
+
"version": 7
|
|
61
|
+
}
|
package/flake.nix
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
description="A flake template";
|
|
3
|
+
inputs={
|
|
4
|
+
nixpkgs.url="github:NixOS/nixpkgs/nixos-unstable";
|
|
5
|
+
flake-utils.url="github:numtide/flake-utils";
|
|
6
|
+
};
|
|
7
|
+
outputs={self,nixpkgs,flake-utils}:
|
|
8
|
+
flake-utils.lib.eachDefaultSystem(system:
|
|
9
|
+
let
|
|
10
|
+
pkgs=import nixpkgs{
|
|
11
|
+
inherit system;
|
|
12
|
+
};
|
|
13
|
+
in {
|
|
14
|
+
devShells.default=pkgs.mkShell{
|
|
15
|
+
name="Flake template";
|
|
16
|
+
shellHook=''
|
|
17
|
+
export PNPM_HOME="$HOME/.local/share/pnpm"
|
|
18
|
+
export PATH="$PNPM_HOME:$PATH"
|
|
19
|
+
'';
|
|
20
|
+
};
|
|
21
|
+
}
|
|
22
|
+
);
|
|
23
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "create-gardener",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "A dom gardener converting dom elements into json and vice versa",
|
|
5
|
+
"main": "index.js",
|
|
6
|
+
"bin":{
|
|
7
|
+
"create-gardener":"./starter.js"
|
|
8
|
+
},
|
|
9
|
+
"type": "module",
|
|
10
|
+
"scripts": {
|
|
11
|
+
"test": "echo \"Error: no test specified\" && exit 1",
|
|
12
|
+
"dev": "concurrently \"tsx watch src/backend/server.ts\" \"tailwindcss -w -i src/frontend/tailwind.css -o src/frontend/style.css\""
|
|
13
|
+
},
|
|
14
|
+
"keywords": [],
|
|
15
|
+
"author": "ritishDas",
|
|
16
|
+
"license": "MIT",
|
|
17
|
+
"packageManager": "pnpm@10.20.0",
|
|
18
|
+
"dependencies": {
|
|
19
|
+
"@types/ejs": "^3.1.5",
|
|
20
|
+
"dotenv": "^17.2.3",
|
|
21
|
+
"ejs": "^3.1.10",
|
|
22
|
+
"express": "^5.2.1",
|
|
23
|
+
"sharp": "^0.34.5",
|
|
24
|
+
"tailwindcss": "^4.1.18",
|
|
25
|
+
"types": "^0.1.1"
|
|
26
|
+
},
|
|
27
|
+
"devDependencies": {
|
|
28
|
+
"@types/express": "^5.0.6",
|
|
29
|
+
"@types/node": "^25.0.2",
|
|
30
|
+
"concurrently": "^9.2.1",
|
|
31
|
+
"tsx": "^4.21.0",
|
|
32
|
+
"typescript": "^5.9.3"
|
|
33
|
+
}
|
|
34
|
+
}
|
package/starter.js
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from "fs";
|
|
4
|
+
import path from "path";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
|
|
7
|
+
// --- ESM __dirname fix ---
|
|
8
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname = path.dirname(__filename);
|
|
10
|
+
|
|
11
|
+
// --- CLI args ---
|
|
12
|
+
const projectName = process.argv[2];
|
|
13
|
+
|
|
14
|
+
if (!projectName) {
|
|
15
|
+
console.log(`
|
|
16
|
+
🌱 create-gardener
|
|
17
|
+
|
|
18
|
+
Usage:
|
|
19
|
+
create-gardener <project-name>
|
|
20
|
+
|
|
21
|
+
Example:
|
|
22
|
+
create-gardener my-app
|
|
23
|
+
`);
|
|
24
|
+
process.exit(1);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// --- Target dir ---
|
|
28
|
+
const targetDir = path.resolve(process.cwd(), projectName);
|
|
29
|
+
|
|
30
|
+
if (fs.existsSync(targetDir)) {
|
|
31
|
+
console.error(`❌ Directory "${projectName}" already exists`);
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
36
|
+
console.log(`✅ Created ${projectName}`);
|
|
37
|
+
|
|
38
|
+
// --- Copy template ---
|
|
39
|
+
const templateDir = path.join(__dirname, "template");
|
|
40
|
+
|
|
41
|
+
if (!fs.existsSync(templateDir)) {
|
|
42
|
+
console.error("❌ Template folder not found");
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
copyDir(templateDir, targetDir);
|
|
47
|
+
|
|
48
|
+
console.log(`
|
|
49
|
+
🌿 Project ready!
|
|
50
|
+
|
|
51
|
+
Next steps:
|
|
52
|
+
cd ${projectName}
|
|
53
|
+
pnpm install
|
|
54
|
+
pnpm dev
|
|
55
|
+
`);
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// ---------- helpers ----------
|
|
59
|
+
function copyDir(src, dest) {
|
|
60
|
+
fs.mkdirSync(dest, { recursive: true });
|
|
61
|
+
|
|
62
|
+
for (const entry of fs.readdirSync(src, { withFileTypes: true })) {
|
|
63
|
+
const srcPath = path.join(src, entry.name);
|
|
64
|
+
const destPath = path.join(dest, entry.name);
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
copyDir(srcPath, destPath);
|
|
68
|
+
} else {
|
|
69
|
+
fs.copyFileSync(srcPath, destPath);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|