clovie 0.1.2 → 0.1.4
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 +4 -4
- package/bin/cli.js +16 -4
- package/config/{default.config.js → clovie.config.js} +6 -2
- package/lib/core/discover.js +12 -0
- package/lib/core/getPartials.js +59 -0
- package/lib/core/getViews.js +13 -5
- package/lib/core/render.js +12 -1
- package/lib/core/watcher.js +42 -11
- package/lib/main.js +15 -5
- package/package.json +1 -1
- package/templates/default/package.json +1 -1
- /package/templates/default/{app.config.js → clovie.config.js} +0 -0
package/README.md
CHANGED
|
@@ -135,7 +135,7 @@ export default {
|
|
|
135
135
|
Clovie supports asynchronous data loading for dynamic content:
|
|
136
136
|
|
|
137
137
|
```javascript
|
|
138
|
-
//
|
|
138
|
+
// clovie.config.js
|
|
139
139
|
export default {
|
|
140
140
|
// ... other config
|
|
141
141
|
data: async () => {
|
|
@@ -157,7 +157,7 @@ export default {
|
|
|
157
157
|
Create multiple pages from data arrays using the models system:
|
|
158
158
|
|
|
159
159
|
```javascript
|
|
160
|
-
//
|
|
160
|
+
// clovie.config.js
|
|
161
161
|
export default {
|
|
162
162
|
// ... other config
|
|
163
163
|
data: {
|
|
@@ -369,7 +369,7 @@ When you create a new project with `clovie create`, you get this structure:
|
|
|
369
369
|
|
|
370
370
|
```
|
|
371
371
|
my-site/
|
|
372
|
-
├──
|
|
372
|
+
├── clovie.config.js # Configuration
|
|
373
373
|
├── package.json # Dependencies and scripts
|
|
374
374
|
├── README.md # Project documentation
|
|
375
375
|
├── views/ # HTML templates
|
|
@@ -386,7 +386,7 @@ You can also create your own structure:
|
|
|
386
386
|
|
|
387
387
|
```
|
|
388
388
|
my-site/
|
|
389
|
-
├──
|
|
389
|
+
├── clovie.config.js # Configuration
|
|
390
390
|
├── views/ # Templates
|
|
391
391
|
│ ├── _base.html # Base template (partial)
|
|
392
392
|
│ ├── _header.html # Header partial
|
package/bin/cli.js
CHANGED
|
@@ -77,7 +77,7 @@ const argv = mainOptions._unknown || [];
|
|
|
77
77
|
|
|
78
78
|
// Command-specific options
|
|
79
79
|
const optionDefinitions = [
|
|
80
|
-
{ name: 'config', alias: 'c', type: String, defaultValue: '
|
|
80
|
+
{ name: 'config', alias: 'c', type: String, defaultValue: 'clovie.config.js' },
|
|
81
81
|
{ name: 'watch', alias: 'w', type: Boolean },
|
|
82
82
|
{ name: 'template', alias: 't', type: String, defaultValue: 'default' }
|
|
83
83
|
];
|
|
@@ -97,9 +97,21 @@ const configPath = path.resolve(process.cwd(), options.config);
|
|
|
97
97
|
// Main function
|
|
98
98
|
async function main() {
|
|
99
99
|
try {
|
|
100
|
-
// Config file
|
|
101
|
-
|
|
102
|
-
|
|
100
|
+
// Config file - use default if not found
|
|
101
|
+
let config;
|
|
102
|
+
try {
|
|
103
|
+
const configModule = await import(configPath);
|
|
104
|
+
config = configModule.default || configModule;
|
|
105
|
+
} catch (err) {
|
|
106
|
+
if (err.code === 'ERR_MODULE_NOT_FOUND') {
|
|
107
|
+
// Use default config if clovie.config.js not found
|
|
108
|
+
const defaultConfigModule = await import('../config/clovie.config.js');
|
|
109
|
+
config = defaultConfigModule.default;
|
|
110
|
+
console.log('📁 Using default Clovie configuration');
|
|
111
|
+
} else {
|
|
112
|
+
throw err;
|
|
113
|
+
}
|
|
114
|
+
}
|
|
103
115
|
|
|
104
116
|
// New Clovie instance
|
|
105
117
|
const site = new Clovie(config);
|
|
@@ -5,15 +5,19 @@ export default {
|
|
|
5
5
|
// Smart defaults - these paths are automatically detected
|
|
6
6
|
scripts: null, // Will auto-detect if not specified
|
|
7
7
|
styles: null, // Will auto-detect if not specified
|
|
8
|
-
views: null, // Will auto-detect if not specified
|
|
9
8
|
assets: null, // Will auto-detect if not specified
|
|
9
|
+
views: null,
|
|
10
|
+
partials: null,
|
|
10
11
|
outputDir: path.resolve('./dist/'),
|
|
11
12
|
|
|
12
13
|
// Data and models
|
|
13
14
|
data: {},
|
|
14
15
|
models: {},
|
|
15
16
|
|
|
16
|
-
|
|
17
|
+
register: (name, template) => {
|
|
18
|
+
Handlebars.registerPartial(name, template);
|
|
19
|
+
},
|
|
20
|
+
|
|
17
21
|
compiler: (template, data) => {
|
|
18
22
|
try {
|
|
19
23
|
const compiled = Handlebars.compile(template);
|
package/lib/core/discover.js
CHANGED
|
@@ -53,6 +53,18 @@ export function discoverProjectStructure(config) {
|
|
|
53
53
|
}
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
// Auto-detect partials directory
|
|
57
|
+
if (!discovered.partials) {
|
|
58
|
+
const partialDirs = ['partials', 'components', 'src/partials', 'src/components'];
|
|
59
|
+
for (const dir of partialDirs) {
|
|
60
|
+
if (fs.existsSync(path.join(cwd, dir))) {
|
|
61
|
+
discovered.partials = path.join('./', dir);
|
|
62
|
+
console.log(`🔍 Auto-detected partials directory: ${dir}`);
|
|
63
|
+
break;
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
56
68
|
// Auto-detect assets directory
|
|
57
69
|
if (!discovered.assets) {
|
|
58
70
|
const assetDirs = ['assets', 'public', 'static', 'src/assets', 'src/public'];
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { readFilesToMap } from '../utils/readFilesToMap.js';
|
|
4
|
+
|
|
5
|
+
export default function (partialsPath) {
|
|
6
|
+
try {
|
|
7
|
+
if (!partialsPath || !fs.existsSync(partialsPath)) {
|
|
8
|
+
console.warn(`Partials directory does not exist: ${partialsPath}`);
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
// Get all files from the partials directory
|
|
13
|
+
const fileNames = getFileNames(partialsPath);
|
|
14
|
+
const templates = fileNames ? readFilesToMap(fileNames, partialsPath) : {};
|
|
15
|
+
|
|
16
|
+
// Convert templates to partials object with filename as key (without extension)
|
|
17
|
+
const partials = {};
|
|
18
|
+
for (const [filePath, template] of Object.entries(templates)) {
|
|
19
|
+
const fileName = path.parse(filePath).name; // Get filename without extension
|
|
20
|
+
partials[fileName] = template;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return partials;
|
|
24
|
+
} catch (err) {
|
|
25
|
+
console.error(`Error processing partials from ${partialsPath}:`, err);
|
|
26
|
+
return {};
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function getFileNames (PATH, files = fs.readdirSync(PATH, { withFileTypes: true }), accumulator = [], depth = 0) {
|
|
31
|
+
// Prevent infinite recursion and stack overflow
|
|
32
|
+
const MAX_DEPTH = 50;
|
|
33
|
+
if (depth > MAX_DEPTH) {
|
|
34
|
+
console.warn(`Maximum directory depth exceeded at: ${PATH}`);
|
|
35
|
+
return accumulator;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
if (!files || files.length === 0) return accumulator;
|
|
39
|
+
|
|
40
|
+
let value = files.shift();
|
|
41
|
+
if (!value) return accumulator;
|
|
42
|
+
|
|
43
|
+
try {
|
|
44
|
+
if (value && !value.name.startsWith('.')) {
|
|
45
|
+
let name = path.join(PATH, value.name);
|
|
46
|
+
let res = value.isDirectory() ? getFileNames(name, [], [], depth + 1) : name;
|
|
47
|
+
|
|
48
|
+
if (Array.isArray(res)) {
|
|
49
|
+
accumulator = accumulator.concat(res)
|
|
50
|
+
} else {
|
|
51
|
+
accumulator.push(res)
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
} catch (err) {
|
|
55
|
+
console.error(`Error processing file/directory: ${PATH}/${value?.name}:`, err);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return files.length ? getFileNames(PATH, files, accumulator, depth) : accumulator;
|
|
59
|
+
}
|
package/lib/core/getViews.js
CHANGED
|
@@ -1,27 +1,35 @@
|
|
|
1
1
|
import fs from 'fs';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { readFilesToMap } from '../utils/readFilesToMap.js';
|
|
4
|
+
import getPartials from './getPartials.js';
|
|
4
5
|
|
|
5
|
-
export default function (src, models = {}, data) {
|
|
6
|
+
export default function (src, models = {}, data, partialsPath = null) {
|
|
6
7
|
try {
|
|
7
8
|
if (!src || !fs.existsSync(src)) {
|
|
8
9
|
console.warn(`Views directory does not exist: ${src}`);
|
|
9
|
-
return {};
|
|
10
|
+
return { pages: {}, partials: {} };
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
let fileNames = getFileNames(src);
|
|
13
14
|
let templates = fileNames ? readFilesToMap(fileNames, src) : null;
|
|
14
15
|
let pages = templates ? getPages(templates, data) : {};
|
|
16
|
+
let partials = {};
|
|
17
|
+
|
|
18
|
+
// Process partials if path is provided
|
|
19
|
+
if (partialsPath) {
|
|
20
|
+
partials = getPartials(partialsPath);
|
|
21
|
+
}
|
|
15
22
|
|
|
16
23
|
// Process models if any exist
|
|
17
24
|
if (Object.keys(models).length > 0) {
|
|
18
|
-
|
|
25
|
+
const processedPages = processModels(templates, data, models, pages);
|
|
26
|
+
return { pages: processedPages, partials };
|
|
19
27
|
}
|
|
20
28
|
|
|
21
|
-
return pages;
|
|
29
|
+
return { pages, partials };
|
|
22
30
|
} catch (err) {
|
|
23
31
|
console.error(`Error processing views from ${src}:`, err);
|
|
24
|
-
return {};
|
|
32
|
+
return { pages: {}, partials: {} };
|
|
25
33
|
}
|
|
26
34
|
}
|
|
27
35
|
|
package/lib/core/render.js
CHANGED
|
@@ -1,8 +1,19 @@
|
|
|
1
1
|
import type from 'type-detect';
|
|
2
2
|
|
|
3
|
-
const render = async (views, compiler, fileNames = Object.keys(views), isDevMode = false, accumulator = {}) => {
|
|
3
|
+
const render = async (views, compiler, fileNames = Object.keys(views), isDevMode = false, partials = {}, register = null, accumulator = {}) => {
|
|
4
4
|
if (!fileNames || fileNames.length === 0) return accumulator;
|
|
5
5
|
|
|
6
|
+
// Process partials if register function is provided
|
|
7
|
+
if (register && Object.keys(partials).length > 0) {
|
|
8
|
+
try {
|
|
9
|
+
for (const [name, template] of Object.entries(partials)) {
|
|
10
|
+
register(name, template);
|
|
11
|
+
}
|
|
12
|
+
} catch (err) {
|
|
13
|
+
console.warn('⚠️ Could not register partials:', err.message);
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
|
|
6
17
|
// Live reload script to inject in development mode
|
|
7
18
|
const liveReloadScript = `
|
|
8
19
|
<!-- Live Reload Script (Development Mode Only) -->
|
package/lib/core/watcher.js
CHANGED
|
@@ -6,7 +6,7 @@ import { BuildCache } from './cache.js';
|
|
|
6
6
|
export class SmartWatcher {
|
|
7
7
|
constructor(clovieInstance) {
|
|
8
8
|
this.clovie = clovieInstance;
|
|
9
|
-
this.
|
|
9
|
+
this.watchers = [];
|
|
10
10
|
this.cache = new BuildCache(clovieInstance.config.outputDir);
|
|
11
11
|
this.debounceTimer = null;
|
|
12
12
|
this.isWatching = false;
|
|
@@ -20,14 +20,28 @@ export class SmartWatcher {
|
|
|
20
20
|
|
|
21
21
|
// Watch views directory
|
|
22
22
|
if (this.clovie.config.views) {
|
|
23
|
-
|
|
23
|
+
const viewsWatcher = chokidar.watch(this.clovie.config.views, {
|
|
24
24
|
ignored: /(^|[\/\\])\../, // Ignore hidden files
|
|
25
25
|
persistent: true
|
|
26
26
|
});
|
|
27
27
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
viewsWatcher.on('change', (filePath) => this.handleViewChange(filePath));
|
|
29
|
+
viewsWatcher.on('add', (filePath) => this.handleViewChange(filePath));
|
|
30
|
+
viewsWatcher.on('unlink', (filePath) => this.handleViewChange(filePath));
|
|
31
|
+
this.watchers.push(viewsWatcher);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
// Watch partials directory
|
|
35
|
+
if (this.clovie.config.partials) {
|
|
36
|
+
const partialsWatcher = chokidar.watch(this.clovie.config.partials, {
|
|
37
|
+
ignored: /(^|[\/\\])\../, // Ignore hidden files
|
|
38
|
+
persistent: true
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
partialsWatcher.on('change', (filePath) => this.handlePartialChange(filePath));
|
|
42
|
+
partialsWatcher.on('add', (filePath) => this.handlePartialChange(filePath));
|
|
43
|
+
partialsWatcher.on('unlink', (filePath) => this.handlePartialChange(filePath));
|
|
44
|
+
this.watchers.push(partialsWatcher);
|
|
31
45
|
}
|
|
32
46
|
|
|
33
47
|
// Watch scripts directory
|
|
@@ -40,6 +54,7 @@ export class SmartWatcher {
|
|
|
40
54
|
scriptsWatcher.on('change', (filePath) => this.handleScriptChange(filePath));
|
|
41
55
|
scriptsWatcher.on('add', (filePath) => this.handleScriptChange(filePath));
|
|
42
56
|
scriptsWatcher.on('unlink', (filePath) => this.handleScriptChange(filePath));
|
|
57
|
+
this.watchers.push(scriptsWatcher);
|
|
43
58
|
}
|
|
44
59
|
|
|
45
60
|
// Watch styles directory
|
|
@@ -52,6 +67,7 @@ export class SmartWatcher {
|
|
|
52
67
|
stylesWatcher.on('change', (filePath) => this.handleStyleChange(filePath));
|
|
53
68
|
stylesWatcher.on('add', (filePath) => this.handleStyleChange(filePath));
|
|
54
69
|
stylesWatcher.on('unlink', (filePath) => this.handleStyleChange(filePath));
|
|
70
|
+
this.watchers.push(stylesWatcher);
|
|
55
71
|
}
|
|
56
72
|
|
|
57
73
|
// Watch assets directory
|
|
@@ -64,16 +80,17 @@ export class SmartWatcher {
|
|
|
64
80
|
assetsWatcher.on('change', (filePath) => this.handleAssetChange(filePath));
|
|
65
81
|
assetsWatcher.on('add', (filePath) => this.handleAssetChange(filePath));
|
|
66
82
|
assetsWatcher.on('unlink', (filePath) => this.handleAssetChange(filePath));
|
|
83
|
+
this.watchers.push(assetsWatcher);
|
|
67
84
|
}
|
|
68
85
|
|
|
69
86
|
console.log('✅ Smart watcher started');
|
|
70
87
|
}
|
|
71
88
|
|
|
72
89
|
stop() {
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
90
|
+
this.watchers.forEach(watcher => {
|
|
91
|
+
watcher.close();
|
|
92
|
+
});
|
|
93
|
+
this.watchers = [];
|
|
77
94
|
this.isWatching = false;
|
|
78
95
|
console.log('🛑 Smart watcher stopped');
|
|
79
96
|
}
|
|
@@ -94,6 +111,7 @@ export class SmartWatcher {
|
|
|
94
111
|
try {
|
|
95
112
|
// Only rebuild what's necessary based on what changed
|
|
96
113
|
switch (type) {
|
|
114
|
+
case 'partial':
|
|
97
115
|
case 'view':
|
|
98
116
|
await this.rebuildViews();
|
|
99
117
|
break;
|
|
@@ -129,7 +147,9 @@ export class SmartWatcher {
|
|
|
129
147
|
try {
|
|
130
148
|
// Re-process all views
|
|
131
149
|
const getViews = await import('./getViews.js');
|
|
132
|
-
|
|
150
|
+
const viewsResult = getViews.default(this.clovie.config.views, this.clovie.config.models, this.clovie.data, this.clovie.config.partials);
|
|
151
|
+
this.clovie.views = viewsResult.pages;
|
|
152
|
+
this.clovie.partials = viewsResult.partials;
|
|
133
153
|
|
|
134
154
|
// Convert to the format expected by render (same as main build process)
|
|
135
155
|
this.clovie.processedViews = {};
|
|
@@ -143,7 +163,14 @@ export class SmartWatcher {
|
|
|
143
163
|
|
|
144
164
|
// Re-render all views using the render function
|
|
145
165
|
const render = await import('./render.js');
|
|
146
|
-
this.clovie.rendered = await render.default(
|
|
166
|
+
this.clovie.rendered = await render.default(
|
|
167
|
+
this.clovie.processedViews,
|
|
168
|
+
this.clovie.config.compiler,
|
|
169
|
+
Object.keys(this.clovie.processedViews),
|
|
170
|
+
this.clovie.isDevMode,
|
|
171
|
+
this.clovie.partials,
|
|
172
|
+
this.clovie.config.register
|
|
173
|
+
);
|
|
147
174
|
|
|
148
175
|
// Write the updated views
|
|
149
176
|
const write = await import('./write.js');
|
|
@@ -237,6 +264,10 @@ export class SmartWatcher {
|
|
|
237
264
|
handleViewChange(filePath) {
|
|
238
265
|
this.scheduleRebuild('view', filePath);
|
|
239
266
|
}
|
|
267
|
+
|
|
268
|
+
handlePartialChange(filePath) {
|
|
269
|
+
this.scheduleRebuild('partial', filePath);
|
|
270
|
+
}
|
|
240
271
|
|
|
241
272
|
handleScriptChange(filePath) {
|
|
242
273
|
this.scheduleRebuild('script', filePath);
|
package/lib/main.js
CHANGED
|
@@ -14,7 +14,7 @@ import getViews from './core/getViews.js';
|
|
|
14
14
|
import render from './core/render.js';
|
|
15
15
|
import write from './core/write.js';
|
|
16
16
|
import clean from './utils/clean.js';
|
|
17
|
-
import defaultConfig from '../config/
|
|
17
|
+
import defaultConfig from '../config/clovie.config.js';
|
|
18
18
|
import { discoverProjectStructure } from './core/discover.js';
|
|
19
19
|
import { SmartWatcher } from './core/watcher.js';
|
|
20
20
|
import { DevServer } from './core/server.js';
|
|
@@ -108,7 +108,9 @@ export default class Clovie {
|
|
|
108
108
|
|
|
109
109
|
// Process views
|
|
110
110
|
console.log('📝 Processing views...');
|
|
111
|
-
|
|
111
|
+
const viewsResult = getViews(this.config.views, this.config.models, this.data, this.config.partials);
|
|
112
|
+
this.views = viewsResult.pages;
|
|
113
|
+
this.partials = viewsResult.partials;
|
|
112
114
|
|
|
113
115
|
// Convert to the format expected by render
|
|
114
116
|
this.processedViews = {};
|
|
@@ -120,10 +122,18 @@ export default class Clovie {
|
|
|
120
122
|
}
|
|
121
123
|
}
|
|
122
124
|
console.log(` Processed ${Object.keys(this.processedViews).length} views`);
|
|
125
|
+
console.log(` Loaded ${Object.keys(this.partials).length} partials`);
|
|
123
126
|
|
|
124
127
|
// Render templates
|
|
125
128
|
console.log('🎨 Rendering templates...');
|
|
126
|
-
this.rendered = await render(
|
|
129
|
+
this.rendered = await render(
|
|
130
|
+
this.processedViews,
|
|
131
|
+
this.config.compiler,
|
|
132
|
+
Object.keys(this.processedViews),
|
|
133
|
+
this.isDevMode,
|
|
134
|
+
this.partials,
|
|
135
|
+
this.config.register
|
|
136
|
+
);
|
|
127
137
|
console.log(` Rendered ${Object.keys(this.rendered).length} templates`);
|
|
128
138
|
|
|
129
139
|
// Process assets in parallel for speed
|
|
@@ -213,9 +223,9 @@ export default class Clovie {
|
|
|
213
223
|
process.nextTick(async () => {
|
|
214
224
|
try {
|
|
215
225
|
console.log('Recompile Templates');
|
|
216
|
-
this.views = getViews(this.config.views, this.config.models, this.data);
|
|
226
|
+
this.views = getViews(this.config.views.path, this.config.models, this.data);
|
|
217
227
|
this.urls = Object.keys(this.views);
|
|
218
|
-
this.rendered = await render(this.views, this.config.compiler, this.urls);
|
|
228
|
+
this.rendered = await render(this.views, this.config.views.compiler, this.urls);
|
|
219
229
|
this.cache = write(this.rendered, this.config.outputDir, Object.keys(this.rendered), this.cache);
|
|
220
230
|
console.log('Templates Done');
|
|
221
231
|
} catch (err) {
|
package/package.json
CHANGED
|
File without changes
|