clovie 0.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/LICENSE +21 -0
- package/README.md +441 -0
- package/bin/cli.js +126 -0
- package/config/default.config.js +31 -0
- package/lib/core/bundler.js +31 -0
- package/lib/core/cache.js +69 -0
- package/lib/core/discover.js +74 -0
- package/lib/core/getAssets.js +38 -0
- package/lib/core/getData.js +19 -0
- package/lib/core/getStyles.js +16 -0
- package/lib/core/getViews.js +187 -0
- package/lib/core/render.js +17 -0
- package/lib/core/server.js +104 -0
- package/lib/core/watcher.js +242 -0
- package/lib/core/write.js +24 -0
- package/lib/main.js +270 -0
- package/lib/utils/clean.js +21 -0
- package/lib/utils/create.js +31 -0
- package/lib/utils/readFilesToMap.js +24 -0
- package/package.json +73 -0
- package/templates/default/README.md +29 -0
- package/templates/default/app.config.js +27 -0
- package/templates/default/package.json +12 -0
- package/templates/default/scripts/main.js +1 -0
- package/templates/default/styles/main.scss +11 -0
- package/templates/default/views/index.html +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Adrian Miller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
# Clovie - Vintage Web Dev Tooling
|
|
2
|
+
|
|
3
|
+
A Node.js-based static site generator designed to be simple, fast, and highly modular. The "Hollow Knight of Web Dev" - simple but deep, easy to start but room to grow.
|
|
4
|
+
|
|
5
|
+
## Project Structure
|
|
6
|
+
|
|
7
|
+
```
|
|
8
|
+
packages/clovie/
|
|
9
|
+
├── __tests__/ # Test files
|
|
10
|
+
│ └── index.test.js
|
|
11
|
+
├── bin/ # CLI executable
|
|
12
|
+
│ └── cli.js
|
|
13
|
+
├── config/ # Configuration files
|
|
14
|
+
│ └── default.config.js
|
|
15
|
+
├── lib/ # Source code
|
|
16
|
+
│ ├── core/ # Core functionality
|
|
17
|
+
│ │ ├── index.js # Main Clovie class
|
|
18
|
+
│ │ ├── bundler.js # JavaScript bundling
|
|
19
|
+
│ │ ├── render.js # Template rendering
|
|
20
|
+
│ │ ├── write.js # File writing
|
|
21
|
+
│ │ ├── getViews.js # View processing
|
|
22
|
+
│ │ ├── getData.js # Data loading
|
|
23
|
+
│ │ ├── getStyles.js # SCSS compilation
|
|
24
|
+
│ │ └── getAssets.js # Asset processing
|
|
25
|
+
│ └── utils/ # Utility functions
|
|
26
|
+
│ ├── clean.js # Directory cleaning
|
|
27
|
+
│ └── create.js # Project creation
|
|
28
|
+
└── package.json
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Core Features
|
|
32
|
+
|
|
33
|
+
- **Template Engine Agnostic**: Support for Handlebars, Nunjucks, Pug, Mustache, or custom engines
|
|
34
|
+
- **Asset Processing**: JavaScript bundling with esbuild, SCSS compilation, static asset copying
|
|
35
|
+
- **Development Server**: Live reload with Browser-Sync and file watching
|
|
36
|
+
- **Data-Driven Pages**: Model system for dynamic page generation
|
|
37
|
+
- **Pagination Support**: Built-in pagination for data-driven content
|
|
38
|
+
|
|
39
|
+
## Usage
|
|
40
|
+
|
|
41
|
+
### Installation
|
|
42
|
+
|
|
43
|
+
#### Option 1: Local Installation (Recommended)
|
|
44
|
+
```bash
|
|
45
|
+
# Install as dev dependency in your project
|
|
46
|
+
npm install --save-dev clovie
|
|
47
|
+
|
|
48
|
+
# Use via npm scripts
|
|
49
|
+
npm run build
|
|
50
|
+
npm run dev
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
#### Option 2: Global Installation
|
|
54
|
+
```bash
|
|
55
|
+
# Install globally
|
|
56
|
+
npm install -g clovie
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Creating New Projects
|
|
60
|
+
|
|
61
|
+
#### Using Clovie CLI (Recommended)
|
|
62
|
+
```bash
|
|
63
|
+
# Create a new project
|
|
64
|
+
npx clovie create my-site
|
|
65
|
+
|
|
66
|
+
# Or with global install
|
|
67
|
+
clovie create my-site
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
### Building and Development
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
# Build the site
|
|
76
|
+
clovie build
|
|
77
|
+
# or
|
|
78
|
+
npm run build
|
|
79
|
+
|
|
80
|
+
# Start development server with file watching
|
|
81
|
+
clovie watch
|
|
82
|
+
# or
|
|
83
|
+
npm run dev
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
## Configuration
|
|
87
|
+
|
|
88
|
+
### Minimal Configuration (Recommended)
|
|
89
|
+
|
|
90
|
+
Clovie uses smart defaults and auto-detection, so you can start with just:
|
|
91
|
+
|
|
92
|
+
```javascript
|
|
93
|
+
export default {
|
|
94
|
+
data: {
|
|
95
|
+
title: 'My Site'
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Clovie will automatically detect:
|
|
101
|
+
- `views/` directory for HTML templates
|
|
102
|
+
- `scripts/main.js` for JavaScript entry point
|
|
103
|
+
- `styles/main.scss` for SCSS entry point
|
|
104
|
+
- `assets/` directory for static files
|
|
105
|
+
|
|
106
|
+
### Full Configuration
|
|
107
|
+
|
|
108
|
+
If you need custom paths or behavior:
|
|
109
|
+
|
|
110
|
+
```javascript
|
|
111
|
+
export default {
|
|
112
|
+
// Custom paths (optional - Clovie will auto-detect if not specified)
|
|
113
|
+
scripts: './src/js/app.js',
|
|
114
|
+
styles: './src/css/main.scss',
|
|
115
|
+
views: './templates',
|
|
116
|
+
assets: './public',
|
|
117
|
+
outputDir: './build',
|
|
118
|
+
|
|
119
|
+
// Your data
|
|
120
|
+
data: {
|
|
121
|
+
title: 'My Site'
|
|
122
|
+
},
|
|
123
|
+
|
|
124
|
+
// Custom compiler (optional - Clovie has a good default)
|
|
125
|
+
compiler: (template, data) => {
|
|
126
|
+
return yourTemplateEngine(template, data);
|
|
127
|
+
}
|
|
128
|
+
};
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
## Advanced Features
|
|
132
|
+
|
|
133
|
+
### Async Data Loading
|
|
134
|
+
|
|
135
|
+
Clovie supports asynchronous data loading for dynamic content:
|
|
136
|
+
|
|
137
|
+
```javascript
|
|
138
|
+
// app.config.js
|
|
139
|
+
export default {
|
|
140
|
+
// ... other config
|
|
141
|
+
data: async () => {
|
|
142
|
+
// Fetch data from API
|
|
143
|
+
const response = await fetch('https://api.example.com/posts');
|
|
144
|
+
const posts = await response.json();
|
|
145
|
+
|
|
146
|
+
return {
|
|
147
|
+
title: 'My Blog',
|
|
148
|
+
posts: posts,
|
|
149
|
+
timestamp: new Date().toISOString()
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
};
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
### Data Models & Dynamic Pages
|
|
156
|
+
|
|
157
|
+
Create multiple pages from data arrays using the models system:
|
|
158
|
+
|
|
159
|
+
```javascript
|
|
160
|
+
// app.config.js
|
|
161
|
+
export default {
|
|
162
|
+
// ... other config
|
|
163
|
+
data: {
|
|
164
|
+
title: 'My Blog',
|
|
165
|
+
posts: [
|
|
166
|
+
{ id: 1, title: 'First Post', content: 'Hello World' },
|
|
167
|
+
{ id: 2, title: 'Second Post', content: 'Another post' },
|
|
168
|
+
{ id: 3, title: 'Third Post', content: 'Yet another' }
|
|
169
|
+
]
|
|
170
|
+
},
|
|
171
|
+
models: {
|
|
172
|
+
posts: {
|
|
173
|
+
template: '_post.html', // Template to use
|
|
174
|
+
paginate: 2, // Posts per page (optional)
|
|
175
|
+
output: (post, index) => { // Custom output filename
|
|
176
|
+
return `post-${post.id}.html`;
|
|
177
|
+
},
|
|
178
|
+
transform: (post, index) => { // Transform data before rendering
|
|
179
|
+
return {
|
|
180
|
+
...post,
|
|
181
|
+
excerpt: post.content.substring(0, 100) + '...',
|
|
182
|
+
date: new Date().toISOString()
|
|
183
|
+
};
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
};
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Template (`_post.html`):**
|
|
191
|
+
```html
|
|
192
|
+
<!DOCTYPE html>
|
|
193
|
+
<html>
|
|
194
|
+
<head>
|
|
195
|
+
<title>{{local.title}} - {{title}}</title>
|
|
196
|
+
</head>
|
|
197
|
+
<body>
|
|
198
|
+
<article>
|
|
199
|
+
<h1>{{local.title}}</h1>
|
|
200
|
+
<p>{{local.excerpt}}</p>
|
|
201
|
+
<div>{{local.content}}</div>
|
|
202
|
+
</article>
|
|
203
|
+
</body>
|
|
204
|
+
</html>
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
**Output:**
|
|
208
|
+
- `post-1.html` - First post page
|
|
209
|
+
- `post-2.html` - Second post page
|
|
210
|
+
- `post-3.html` - Third post page
|
|
211
|
+
|
|
212
|
+
### Custom Template Engines
|
|
213
|
+
|
|
214
|
+
Clovie is template-engine agnostic. Here are examples for popular engines:
|
|
215
|
+
|
|
216
|
+
#### Handlebars
|
|
217
|
+
```javascript
|
|
218
|
+
import Handlebars from 'handlebars';
|
|
219
|
+
|
|
220
|
+
export default {
|
|
221
|
+
// ... other config
|
|
222
|
+
compiler: (template, data) => {
|
|
223
|
+
const compiled = Handlebars.compile(template);
|
|
224
|
+
return compiled(data);
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
#### Nunjucks
|
|
230
|
+
```javascript
|
|
231
|
+
import nunjucks from 'nunjucks';
|
|
232
|
+
|
|
233
|
+
export default {
|
|
234
|
+
// ... other config
|
|
235
|
+
compiler: (template, data) => {
|
|
236
|
+
return nunjucks.renderString(template, data);
|
|
237
|
+
}
|
|
238
|
+
};
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Pug
|
|
242
|
+
```javascript
|
|
243
|
+
import pug from 'pug';
|
|
244
|
+
|
|
245
|
+
export default {
|
|
246
|
+
// ... other config
|
|
247
|
+
compiler: (template, data) => {
|
|
248
|
+
return pug.render(template, { ...data, pretty: true });
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
#### Custom Engine
|
|
254
|
+
```javascript
|
|
255
|
+
export default {
|
|
256
|
+
// ... other config
|
|
257
|
+
compiler: (template, data) => {
|
|
258
|
+
// Simple variable replacement
|
|
259
|
+
return template.replace(/\{\{(\w+)\}\}/g, (match, key) => {
|
|
260
|
+
return data[key] || match;
|
|
261
|
+
});
|
|
262
|
+
}
|
|
263
|
+
};
|
|
264
|
+
```
|
|
265
|
+
|
|
266
|
+
### Pagination
|
|
267
|
+
|
|
268
|
+
The models system includes built-in pagination:
|
|
269
|
+
|
|
270
|
+
```javascript
|
|
271
|
+
export default {
|
|
272
|
+
// ... other config
|
|
273
|
+
models: {
|
|
274
|
+
blog: {
|
|
275
|
+
template: '_blog.html',
|
|
276
|
+
paginate: 5, // 5 posts per page
|
|
277
|
+
output: (posts, pageNum) => {
|
|
278
|
+
return pageNum === 0 ? 'blog.html' : `blog-${pageNum + 1}.html`;
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
};
|
|
283
|
+
```
|
|
284
|
+
|
|
285
|
+
**Output:**
|
|
286
|
+
- `blog.html` - First 5 posts
|
|
287
|
+
- `blog-2.html` - Next 5 posts
|
|
288
|
+
- `blog-3.html` - Remaining posts
|
|
289
|
+
|
|
290
|
+
### Data Transformation
|
|
291
|
+
|
|
292
|
+
Transform data before rendering with custom functions:
|
|
293
|
+
|
|
294
|
+
```javascript
|
|
295
|
+
export default {
|
|
296
|
+
// ... other config
|
|
297
|
+
models: {
|
|
298
|
+
products: {
|
|
299
|
+
template: '_product.html',
|
|
300
|
+
transform: (product, index) => {
|
|
301
|
+
return {
|
|
302
|
+
...product,
|
|
303
|
+
price: `$${product.price.toFixed(2)}`,
|
|
304
|
+
slug: product.name.toLowerCase().replace(/\s+/g, '-'),
|
|
305
|
+
inStock: product.quantity > 0
|
|
306
|
+
};
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
## Error Handling & Best Practices
|
|
314
|
+
|
|
315
|
+
### Error Handling
|
|
316
|
+
|
|
317
|
+
Clovie includes robust error handling for common issues:
|
|
318
|
+
|
|
319
|
+
- **Missing directories**: Gracefully handles missing views, scripts, or assets folders
|
|
320
|
+
- **File read errors**: Continues processing even if individual files fail
|
|
321
|
+
- **Template errors**: Provides clear error messages for compilation failures
|
|
322
|
+
- **Data validation**: Warns about invalid data structures
|
|
323
|
+
|
|
324
|
+
### Progress Indicators
|
|
325
|
+
|
|
326
|
+
Clovie provides clear feedback during builds:
|
|
327
|
+
|
|
328
|
+
```
|
|
329
|
+
🚀 Starting build...
|
|
330
|
+
🧹 Cleaning output directory...
|
|
331
|
+
📊 Loading data...
|
|
332
|
+
Loaded 2 data sources
|
|
333
|
+
📝 Processing views...
|
|
334
|
+
Processed 5 views
|
|
335
|
+
🎨 Rendering templates...
|
|
336
|
+
Rendered 5 templates
|
|
337
|
+
⚡ Bundling scripts...
|
|
338
|
+
Bundled 1 script files
|
|
339
|
+
🎨 Compiling styles...
|
|
340
|
+
Compiled 1 style files
|
|
341
|
+
📦 Processing assets...
|
|
342
|
+
Processed 3 asset files
|
|
343
|
+
💾 Writing files...
|
|
344
|
+
✅ Build completed in 45ms
|
|
345
|
+
```
|
|
346
|
+
|
|
347
|
+
### Auto-Discovery
|
|
348
|
+
|
|
349
|
+
Clovie automatically detects common project structures:
|
|
350
|
+
|
|
351
|
+
```
|
|
352
|
+
🔍 Auto-detected views directory: views
|
|
353
|
+
🔍 Auto-detected scripts entry: scripts/main.js
|
|
354
|
+
🔍 Auto-detected styles entry: styles/main.scss
|
|
355
|
+
🔍 Auto-detected assets directory: assets
|
|
356
|
+
```
|
|
357
|
+
|
|
358
|
+
### Best Practices
|
|
359
|
+
|
|
360
|
+
1. **Use partial templates** (files starting with `_`) for reusable components
|
|
361
|
+
2. **Validate data structures** before passing to models
|
|
362
|
+
3. **Handle async data** with proper error catching
|
|
363
|
+
4. **Use meaningful output filenames** for SEO and organization
|
|
364
|
+
5. **Transform data** in the model configuration, not in templates
|
|
365
|
+
|
|
366
|
+
### Project Structure
|
|
367
|
+
|
|
368
|
+
When you create a new project with `clovie create`, you get this structure:
|
|
369
|
+
|
|
370
|
+
```
|
|
371
|
+
my-site/
|
|
372
|
+
├── app.config.js # Configuration
|
|
373
|
+
├── package.json # Dependencies and scripts
|
|
374
|
+
├── README.md # Project documentation
|
|
375
|
+
├── views/ # HTML templates
|
|
376
|
+
│ └── index.html # Home page template
|
|
377
|
+
├── scripts/ # JavaScript
|
|
378
|
+
│ └── main.js # Main script file
|
|
379
|
+
├── styles/ # SCSS
|
|
380
|
+
│ └── main.scss # Main stylesheet
|
|
381
|
+
└── assets/ # Static files (images, etc.)
|
|
382
|
+
```
|
|
383
|
+
|
|
384
|
+
#### Custom Project Structure
|
|
385
|
+
You can also create your own structure:
|
|
386
|
+
|
|
387
|
+
```
|
|
388
|
+
my-site/
|
|
389
|
+
├── app.config.js # Configuration
|
|
390
|
+
├── views/ # Templates
|
|
391
|
+
│ ├── _base.html # Base template (partial)
|
|
392
|
+
│ ├── _header.html # Header partial
|
|
393
|
+
│ ├── index.html # Home page
|
|
394
|
+
│ └── _post.html # Post template (partial)
|
|
395
|
+
├── scripts/ # JavaScript
|
|
396
|
+
│ └── main.js
|
|
397
|
+
├── styles/ # SCSS
|
|
398
|
+
│ └── main.scss
|
|
399
|
+
├── assets/ # Static files
|
|
400
|
+
│ └── images/
|
|
401
|
+
└── data/ # Data files (optional)
|
|
402
|
+
└── posts.json
|
|
403
|
+
```
|
|
404
|
+
|
|
405
|
+
## Development
|
|
406
|
+
|
|
407
|
+
```bash
|
|
408
|
+
# Install dependencies
|
|
409
|
+
npm install
|
|
410
|
+
|
|
411
|
+
# Run tests
|
|
412
|
+
npm test
|
|
413
|
+
|
|
414
|
+
# Run tests in watch mode
|
|
415
|
+
npm run test:watch
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
## Troubleshooting
|
|
419
|
+
|
|
420
|
+
### Common Issues
|
|
421
|
+
|
|
422
|
+
**"Views directory does not exist"**
|
|
423
|
+
- Ensure the `views` path in your config is correct
|
|
424
|
+
- Create the views directory if it doesn't exist
|
|
425
|
+
|
|
426
|
+
**"Data for model must be an array"**
|
|
427
|
+
- Check that your data structure matches the model configuration
|
|
428
|
+
- Ensure the referenced data key contains an array
|
|
429
|
+
|
|
430
|
+
**"Maximum directory depth exceeded"**
|
|
431
|
+
- Check for circular symlinks or extremely deep directory structures
|
|
432
|
+
- The limit is 50 levels deep (configurable in code)
|
|
433
|
+
|
|
434
|
+
**Build failures**
|
|
435
|
+
- Check console output for specific error messages
|
|
436
|
+
- Verify all referenced files exist
|
|
437
|
+
- Ensure template syntax matches your compiler
|
|
438
|
+
|
|
439
|
+
## License
|
|
440
|
+
|
|
441
|
+
MIT
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import path from "path";
|
|
3
|
+
import commandLineArgs from "command-line-args";
|
|
4
|
+
import { fileURLToPath } from 'url';
|
|
5
|
+
import { dirname } from 'path';
|
|
6
|
+
|
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
8
|
+
const __dirname = dirname(__filename);
|
|
9
|
+
|
|
10
|
+
// Local
|
|
11
|
+
import Clovie from "../lib/main.js";
|
|
12
|
+
|
|
13
|
+
// Check for create command first (before any argument parsing)
|
|
14
|
+
if (process.argv.includes('create') && process.argv.length > 2) {
|
|
15
|
+
const projectName = process.argv[3]; // The name after 'create'
|
|
16
|
+
|
|
17
|
+
if (!projectName) {
|
|
18
|
+
console.error('Error: Please provide a project name');
|
|
19
|
+
console.error('Usage: clovie create <project-name>');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
const fs = await import('fs');
|
|
24
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
|
25
|
+
|
|
26
|
+
if (fs.existsSync(projectPath)) {
|
|
27
|
+
console.error(`Error: Directory '${projectName}' already exists`);
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Copy template files
|
|
32
|
+
const templateDir = path.join(__dirname, '../templates/default');
|
|
33
|
+
|
|
34
|
+
// Create project directory first
|
|
35
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
|
36
|
+
|
|
37
|
+
const copyDir = async (src, dest) => {
|
|
38
|
+
const entries = fs.readdirSync(src, { withFileTypes: true });
|
|
39
|
+
|
|
40
|
+
for (const entry of entries) {
|
|
41
|
+
const srcPath = path.join(src, entry.name);
|
|
42
|
+
const destPath = path.join(dest, entry.name);
|
|
43
|
+
|
|
44
|
+
if (entry.isDirectory()) {
|
|
45
|
+
fs.mkdirSync(destPath, { recursive: true });
|
|
46
|
+
await copyDir(srcPath, destPath);
|
|
47
|
+
} else {
|
|
48
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
49
|
+
// Replace template variables
|
|
50
|
+
content = content.replace(/\{\{projectName\}\}/g, projectName);
|
|
51
|
+
fs.writeFileSync(destPath, content);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
try {
|
|
57
|
+
await copyDir(templateDir, projectPath);
|
|
58
|
+
console.log(`✅ Clovie project created successfully at ${projectPath}`);
|
|
59
|
+
console.log('\nNext steps:');
|
|
60
|
+
console.log(` cd ${projectName}`);
|
|
61
|
+
console.log(' npm install');
|
|
62
|
+
console.log(' npm run dev');
|
|
63
|
+
process.exit(0);
|
|
64
|
+
} catch (err) {
|
|
65
|
+
console.error('Error creating project:', err);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Commandline options for other commands
|
|
71
|
+
const mainDefinitions = [
|
|
72
|
+
{ name: 'command', defaultOption: true },
|
|
73
|
+
{ name: 'watch', alias: 'w', type: Boolean }
|
|
74
|
+
];
|
|
75
|
+
const mainOptions = commandLineArgs(mainDefinitions, { stopAtFirstUnknown: true });
|
|
76
|
+
const argv = mainOptions._unknown || [];
|
|
77
|
+
|
|
78
|
+
// Command-specific options
|
|
79
|
+
const optionDefinitions = [
|
|
80
|
+
{ name: 'config', alias: 'c', type: String, defaultValue: 'app.config.js' },
|
|
81
|
+
{ name: 'watch', alias: 'w', type: Boolean },
|
|
82
|
+
{ name: 'template', alias: 't', type: String, defaultValue: 'default' }
|
|
83
|
+
];
|
|
84
|
+
|
|
85
|
+
const options = commandLineArgs(optionDefinitions, { argv });
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
// Handle watch command
|
|
90
|
+
if (mainOptions.command === 'watch') {
|
|
91
|
+
options.watch = true;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Config path
|
|
95
|
+
const configPath = path.resolve(process.cwd(), options.config);
|
|
96
|
+
|
|
97
|
+
// Main function
|
|
98
|
+
async function main() {
|
|
99
|
+
try {
|
|
100
|
+
// Config file
|
|
101
|
+
const configModule = await import(configPath);
|
|
102
|
+
const config = configModule.default || configModule;
|
|
103
|
+
|
|
104
|
+
// New Clovie instance
|
|
105
|
+
const site = new Clovie(config);
|
|
106
|
+
|
|
107
|
+
site.error(err => {
|
|
108
|
+
console.error(err);
|
|
109
|
+
process.exit(1);
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (options.watch) {
|
|
113
|
+
await site.startWatch();
|
|
114
|
+
} else {
|
|
115
|
+
await site.build();
|
|
116
|
+
console.log('Build complete');
|
|
117
|
+
process.exit(0);
|
|
118
|
+
}
|
|
119
|
+
} catch (err) {
|
|
120
|
+
console.error(err);
|
|
121
|
+
process.exit(1);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Run main function
|
|
126
|
+
main();
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import Handlebars from 'handlebars';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
export default {
|
|
5
|
+
// Smart defaults - these paths are automatically detected
|
|
6
|
+
scripts: null, // Will auto-detect if not specified
|
|
7
|
+
styles: null, // Will auto-detect if not specified
|
|
8
|
+
views: null, // Will auto-detect if not specified
|
|
9
|
+
assets: null, // Will auto-detect if not specified
|
|
10
|
+
outputDir: path.resolve('./dist/'),
|
|
11
|
+
|
|
12
|
+
// Data and models
|
|
13
|
+
data: {},
|
|
14
|
+
models: {},
|
|
15
|
+
|
|
16
|
+
// Default compiler - Handlebars for powerful templating
|
|
17
|
+
compiler: (template, data) => {
|
|
18
|
+
try {
|
|
19
|
+
const compiled = Handlebars.compile(template);
|
|
20
|
+
return compiled(data);
|
|
21
|
+
} catch (err) {
|
|
22
|
+
console.warn(`⚠️ Template compilation error: ${err.message}`);
|
|
23
|
+
return template; // Fallback to raw template
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
// Development options
|
|
28
|
+
watch: false,
|
|
29
|
+
port: 3000,
|
|
30
|
+
open: false
|
|
31
|
+
}
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import esbuild from 'esbuild';
|
|
3
|
+
|
|
4
|
+
export default function(file) {
|
|
5
|
+
const pathObj = path.parse(file);
|
|
6
|
+
|
|
7
|
+
return new Promise(async (resolve, reject) => {
|
|
8
|
+
try {
|
|
9
|
+
const result = await esbuild.build({
|
|
10
|
+
entryPoints: [file],
|
|
11
|
+
bundle: true,
|
|
12
|
+
write: false,
|
|
13
|
+
format: 'iife',
|
|
14
|
+
globalName: 'app',
|
|
15
|
+
platform: 'browser',
|
|
16
|
+
target: ['es2015'],
|
|
17
|
+
minify: false,
|
|
18
|
+
sourcemap: false,
|
|
19
|
+
// Performance optimizations
|
|
20
|
+
treeShaking: true,
|
|
21
|
+
metafile: false,
|
|
22
|
+
logLevel: 'silent'
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
const { text } = result.outputFiles[0];
|
|
26
|
+
resolve({[`${pathObj.name}.js`]: text});
|
|
27
|
+
} catch (err) {
|
|
28
|
+
reject(err);
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
};
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import crypto from 'crypto';
|
|
4
|
+
|
|
5
|
+
export class BuildCache {
|
|
6
|
+
constructor(cacheDir) {
|
|
7
|
+
this.cacheDir = cacheDir;
|
|
8
|
+
this.cacheFile = path.join(cacheDir, '.clovie-cache.json');
|
|
9
|
+
this.cache = this.loadCache();
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
loadCache() {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(this.cacheFile)) {
|
|
15
|
+
return JSON.parse(fs.readFileSync(this.cacheFile, 'utf8'));
|
|
16
|
+
}
|
|
17
|
+
} catch (err) {
|
|
18
|
+
console.warn('⚠️ Could not load cache, starting fresh');
|
|
19
|
+
}
|
|
20
|
+
return { files: {}, lastBuild: null };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
saveCache() {
|
|
24
|
+
try {
|
|
25
|
+
if (!fs.existsSync(this.cacheDir)) {
|
|
26
|
+
fs.mkdirSync(this.cacheDir, { recursive: true });
|
|
27
|
+
}
|
|
28
|
+
fs.writeFileSync(this.cacheFile, JSON.stringify(this.cache, null, 2));
|
|
29
|
+
} catch (err) {
|
|
30
|
+
console.warn('⚠️ Could not save cache');
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
getFileHash(filePath) {
|
|
35
|
+
try {
|
|
36
|
+
if (!fs.existsSync(filePath)) return null;
|
|
37
|
+
const content = fs.readFileSync(filePath);
|
|
38
|
+
return crypto.createHash('md5').update(content).digest('hex');
|
|
39
|
+
} catch (err) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
hasChanged(filePath) {
|
|
45
|
+
const currentHash = this.getFileHash(filePath);
|
|
46
|
+
const cachedHash = this.cache.files[filePath];
|
|
47
|
+
|
|
48
|
+
if (currentHash !== cachedHash) {
|
|
49
|
+
this.cache.files[filePath] = currentHash;
|
|
50
|
+
return true;
|
|
51
|
+
}
|
|
52
|
+
return false;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
getChangedFiles(files) {
|
|
56
|
+
return files.filter(file => this.hasChanged(file));
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
markBuilt() {
|
|
60
|
+
this.cache.lastBuild = Date.now();
|
|
61
|
+
this.saveCache();
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
getBuildStats() {
|
|
65
|
+
const totalFiles = Object.keys(this.cache.files).length;
|
|
66
|
+
const changedFiles = Object.values(this.cache.files).filter(hash => hash !== null).length;
|
|
67
|
+
return { totalFiles, changedFiles };
|
|
68
|
+
}
|
|
69
|
+
}
|