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 CHANGED
@@ -135,7 +135,7 @@ export default {
135
135
  Clovie supports asynchronous data loading for dynamic content:
136
136
 
137
137
  ```javascript
138
- // app.config.js
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
- // app.config.js
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
- ├── app.config.js # Configuration
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
- ├── app.config.js # Configuration
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: 'app.config.js' },
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
- const configModule = await import(configPath);
102
- const config = configModule.default || configModule;
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
- // Default compiler - Handlebars for powerful templating
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);
@@ -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
+ }
@@ -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
- return processModels(templates, data, models, pages);
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
 
@@ -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) -->
@@ -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.watcher = null;
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
- this.watcher = chokidar.watch(this.clovie.config.views, {
23
+ const viewsWatcher = chokidar.watch(this.clovie.config.views, {
24
24
  ignored: /(^|[\/\\])\../, // Ignore hidden files
25
25
  persistent: true
26
26
  });
27
27
 
28
- this.watcher.on('change', (filePath) => this.handleViewChange(filePath));
29
- this.watcher.on('add', (filePath) => this.handleViewChange(filePath));
30
- this.watcher.on('unlink', (filePath) => this.handleViewChange(filePath));
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
- if (this.watcher) {
74
- this.watcher.close();
75
- this.watcher = null;
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
- this.clovie.views = getViews.default(this.clovie.config.views, this.clovie.config.models, this.clovie.data);
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(this.clovie.processedViews, this.clovie.config.compiler, Object.keys(this.clovie.processedViews), this.clovie.isDevMode);
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/default.config.js';
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
- this.views = getViews(this.config.views, this.config.models, this.data);
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(this.processedViews, this.config.compiler, Object.keys(this.processedViews), this.isDevMode);
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clovie",
3
- "version": "0.1.2",
3
+ "version": "0.1.4",
4
4
  "description": "Vintage web dev tooling with modern quality of life",
5
5
  "keywords": [
6
6
  "static-site-generator",
@@ -7,6 +7,6 @@
7
7
  "dev": "clovie watch"
8
8
  },
9
9
  "devDependencies": {
10
- "clovie": "^0.1.0"
10
+ "clovie": "^0.1.3"
11
11
  }
12
12
  }