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
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import fs from 'fs';
|
|
3
|
+
|
|
4
|
+
export default function write (pages, outputDir, keys = Object.keys(pages), accumulator = {}) {
|
|
5
|
+
const key = keys.shift();
|
|
6
|
+
const value = pages[key];
|
|
7
|
+
|
|
8
|
+
if (accumulator[key] != value) {
|
|
9
|
+
const dest = path.join(outputDir, key);
|
|
10
|
+
const dir = path.dirname(dest);
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(dir)) {
|
|
13
|
+
fs.mkdirSync(dir, { recursive: true })
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
fs.writeFileSync(dest, value, err => {
|
|
17
|
+
if (err) {
|
|
18
|
+
console.log(err)
|
|
19
|
+
}
|
|
20
|
+
});
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
return keys.length ? write(pages, outputDir, keys) : 'success';
|
|
24
|
+
}
|
package/lib/main.js
ADDED
|
@@ -0,0 +1,270 @@
|
|
|
1
|
+
import chokidar from 'chokidar';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { dirname } from 'path';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
import bundler from './core/bundler.js';
|
|
10
|
+
import getAssets from './core/getAssets.js';
|
|
11
|
+
import getData from './core/getData.js';
|
|
12
|
+
import getStyles from './core/getStyles.js';
|
|
13
|
+
import getViews from './core/getViews.js';
|
|
14
|
+
import render from './core/render.js';
|
|
15
|
+
import write from './core/write.js';
|
|
16
|
+
import clean from './utils/clean.js';
|
|
17
|
+
import defaultConfig from '../config/default.config.js';
|
|
18
|
+
import { discoverProjectStructure } from './core/discover.js';
|
|
19
|
+
import { SmartWatcher } from './core/watcher.js';
|
|
20
|
+
import { DevServer } from './core/server.js';
|
|
21
|
+
|
|
22
|
+
export default class Clovie {
|
|
23
|
+
constructor (config) {
|
|
24
|
+
// Merge with defaults and auto-discover project structure
|
|
25
|
+
this.config = discoverProjectStructure(Object.assign(defaultConfig, config));
|
|
26
|
+
|
|
27
|
+
// Set derived paths
|
|
28
|
+
if (this.config.styles) {
|
|
29
|
+
this.config.stylesDir = path.resolve(path.dirname(this.config.styles));
|
|
30
|
+
}
|
|
31
|
+
if (this.config.scripts) {
|
|
32
|
+
this.config.scriptsDir = path.resolve(path.dirname(this.config.scripts));
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
this.errorCb = null;
|
|
36
|
+
|
|
37
|
+
// Initialize smart watcher
|
|
38
|
+
this.watcher = new SmartWatcher(this);
|
|
39
|
+
|
|
40
|
+
// Log configuration summary
|
|
41
|
+
console.log('š Clovie Configuration:');
|
|
42
|
+
console.log(` Views: ${this.config.views || 'Not found'}`);
|
|
43
|
+
console.log(` Scripts: ${this.config.scripts || 'Not found'}`);
|
|
44
|
+
console.log(` Styles: ${this.config.styles || 'Not found'}`);
|
|
45
|
+
console.log(` Assets: ${this.config.assets || 'Not found'}`);
|
|
46
|
+
console.log(` Output: ${this.config.outputDir}`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
async startWatch() {
|
|
50
|
+
try {
|
|
51
|
+
console.log('š Starting development server with smart watching...');
|
|
52
|
+
|
|
53
|
+
// Do initial build
|
|
54
|
+
console.log('š¦ Building initial site...');
|
|
55
|
+
await this.build();
|
|
56
|
+
|
|
57
|
+
// Start development server
|
|
58
|
+
console.log('š Starting development server...');
|
|
59
|
+
this.devServer = new DevServer(this, this.config.port || 3000);
|
|
60
|
+
this.devServer.start();
|
|
61
|
+
|
|
62
|
+
// Start smart watcher
|
|
63
|
+
console.log('š Starting smart watcher...');
|
|
64
|
+
this.watcher.start();
|
|
65
|
+
|
|
66
|
+
// Connect watcher to dev server for live reload
|
|
67
|
+
this.watcher.onRebuild = () => {
|
|
68
|
+
if (this.devServer) {
|
|
69
|
+
this.devServer.notifyReload();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
// Keep process alive
|
|
74
|
+
process.on('SIGINT', () => {
|
|
75
|
+
console.log('\nš Shutting down...');
|
|
76
|
+
this.watcher.stop();
|
|
77
|
+
if (this.devServer) {
|
|
78
|
+
this.devServer.stop();
|
|
79
|
+
}
|
|
80
|
+
process.exit(0);
|
|
81
|
+
});
|
|
82
|
+
|
|
83
|
+
console.log('ā
Development server running. Press Ctrl+C to stop.');
|
|
84
|
+
} catch (err) {
|
|
85
|
+
console.error('ā Failed to start watch mode:', err);
|
|
86
|
+
throw err;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
async build () {
|
|
91
|
+
const startTime = Date.now();
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
console.log('š Starting build...');
|
|
95
|
+
|
|
96
|
+
// Clean output directory
|
|
97
|
+
console.log('š§¹ Cleaning output directory...');
|
|
98
|
+
clean(this.config.outputDir);
|
|
99
|
+
|
|
100
|
+
// Load data
|
|
101
|
+
console.log('š Loading data...');
|
|
102
|
+
this.data = this.config.data ? await getData(this.config.data) : {};
|
|
103
|
+
console.log(` Loaded ${Object.keys(this.data).length} data sources`);
|
|
104
|
+
|
|
105
|
+
// Process views
|
|
106
|
+
console.log('š Processing views...');
|
|
107
|
+
this.views = getViews(this.config.views, this.config.models, this.data);
|
|
108
|
+
|
|
109
|
+
// Convert to the format expected by render
|
|
110
|
+
this.processedViews = {};
|
|
111
|
+
for (const [key, value] of Object.entries(this.views)) {
|
|
112
|
+
if (value && value.template) {
|
|
113
|
+
// Use filename from model processing, or generate default
|
|
114
|
+
const fileName = value.filename || key.replace(/\.[^/.]+$/, '.html');
|
|
115
|
+
this.processedViews[fileName] = value;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
console.log(` Processed ${Object.keys(this.processedViews).length} views`);
|
|
119
|
+
|
|
120
|
+
// Render templates
|
|
121
|
+
console.log('šØ Rendering templates...');
|
|
122
|
+
this.rendered = await render(this.processedViews, this.config.compiler, Object.keys(this.processedViews));
|
|
123
|
+
console.log(` Rendered ${Object.keys(this.rendered).length} templates`);
|
|
124
|
+
|
|
125
|
+
// Process assets in parallel for speed
|
|
126
|
+
const assetPromises = [];
|
|
127
|
+
|
|
128
|
+
if (this.config.scripts) {
|
|
129
|
+
console.log('ā” Bundling scripts...');
|
|
130
|
+
assetPromises.push(
|
|
131
|
+
bundler(this.config.scripts).then(scripts => {
|
|
132
|
+
this.scripts = scripts;
|
|
133
|
+
console.log(` Bundled ${Object.keys(scripts).length} script files`);
|
|
134
|
+
})
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
if (this.config.styles) {
|
|
139
|
+
console.log('šØ Compiling styles...');
|
|
140
|
+
assetPromises.push(
|
|
141
|
+
Promise.resolve().then(() => {
|
|
142
|
+
this.styles = getStyles(this.config.styles);
|
|
143
|
+
console.log(` Compiled ${Object.keys(this.styles).length} style files`);
|
|
144
|
+
})
|
|
145
|
+
);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
if (this.config.assets) {
|
|
149
|
+
console.log('š¦ Processing assets...');
|
|
150
|
+
assetPromises.push(
|
|
151
|
+
Promise.resolve().then(() => {
|
|
152
|
+
this.assets = getAssets(this.config.assets);
|
|
153
|
+
console.log(` Processed ${Object.keys(this.assets).length} asset files`);
|
|
154
|
+
})
|
|
155
|
+
);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Wait for all assets to complete
|
|
159
|
+
if (assetPromises.length > 0) {
|
|
160
|
+
await Promise.all(assetPromises);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
// Write output
|
|
164
|
+
console.log('š¾ Writing files...');
|
|
165
|
+
this.cache = write(Object.assign(this.rendered, this.scripts, this.styles, this.assets), this.config.outputDir);
|
|
166
|
+
|
|
167
|
+
const buildTime = Date.now() - startTime;
|
|
168
|
+
console.log(`ā
Build completed in ${buildTime}ms`);
|
|
169
|
+
|
|
170
|
+
return this.cache;
|
|
171
|
+
} catch (err) {
|
|
172
|
+
const buildTime = Date.now() - startTime;
|
|
173
|
+
console.error(`ā Build failed after ${buildTime}ms:`, err);
|
|
174
|
+
|
|
175
|
+
// Provide better error context
|
|
176
|
+
if (err.code === 'ENOENT') {
|
|
177
|
+
console.error('š” Tip: Check that all referenced files and directories exist');
|
|
178
|
+
} else if (err.message?.includes('template')) {
|
|
179
|
+
console.error('š” Tip: Verify your template syntax and data structure');
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (this.errorCb) {
|
|
183
|
+
this.errorCb(err);
|
|
184
|
+
} else {
|
|
185
|
+
throw err;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
watch () {
|
|
191
|
+
try {
|
|
192
|
+
bs.init({
|
|
193
|
+
watch: true,
|
|
194
|
+
server: {
|
|
195
|
+
baseDir: this.config.outputDir,
|
|
196
|
+
serveStaticOptions: {
|
|
197
|
+
extensions: ["html", 'htm']
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
let options = {
|
|
203
|
+
ignored: /(^|[\/\\])\../,
|
|
204
|
+
persistent: true,
|
|
205
|
+
ignoreInitial: true
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
chokidar.watch(this.config.views, options).on('all', () => {
|
|
209
|
+
process.nextTick(async () => {
|
|
210
|
+
try {
|
|
211
|
+
console.log('Recompile Templates');
|
|
212
|
+
this.views = getViews(this.config.views, this.config.models, this.data);
|
|
213
|
+
this.urls = Object.keys(this.views);
|
|
214
|
+
this.rendered = await render(this.views, this.config.compiler, this.urls);
|
|
215
|
+
this.cache = write(this.rendered, this.config.outputDir, Object.keys(this.rendered), this.cache);
|
|
216
|
+
console.log('Templates Done');
|
|
217
|
+
} catch (err) {
|
|
218
|
+
console.error('Template recompilation failed:', err);
|
|
219
|
+
}
|
|
220
|
+
})
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
chokidar.watch(this.config.scriptsDir, options).on('all', () => {
|
|
224
|
+
process.nextTick(async () => {
|
|
225
|
+
try {
|
|
226
|
+
console.log('Recompile Scripts');
|
|
227
|
+
this.scripts = await bundler(this.config.scripts);
|
|
228
|
+
this.cache = write(this.scripts, this.config.outputDir, Object.keys(this.scripts), this.cache);
|
|
229
|
+
console.log('Scripts Done');
|
|
230
|
+
} catch (err) {
|
|
231
|
+
console.error('Script recompilation failed:', err);
|
|
232
|
+
}
|
|
233
|
+
})
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
chokidar.watch(this.config.stylesDir, options).on('all', () => {
|
|
237
|
+
process.nextTick(() => {
|
|
238
|
+
try {
|
|
239
|
+
console.log('Updates styles');
|
|
240
|
+
this.styles = getStyles(this.config.styles);
|
|
241
|
+
this.cache = write(this.styles, this.config.outputDir, Object.keys(this.styles), this.cache);
|
|
242
|
+
console.log('Styles Done');
|
|
243
|
+
} catch (err) {
|
|
244
|
+
console.error('Style update failed:', err);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
});
|
|
248
|
+
|
|
249
|
+
chokidar.watch(this.config.assets, options).on('all', () => {
|
|
250
|
+
process.nextTick(() => {
|
|
251
|
+
try {
|
|
252
|
+
console.log('Updating assets');
|
|
253
|
+
this.assets = getAssets(this.config.assets);
|
|
254
|
+
this.cache = write(this.assets, this.config.outputDir, Object.keys(this.assets), this.cache);
|
|
255
|
+
console.log('Assets updated');
|
|
256
|
+
} catch (err) {
|
|
257
|
+
console.error('Asset update failed:', err);
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
});
|
|
261
|
+
} catch (err) {
|
|
262
|
+
this.error(err)
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
error(cb) {
|
|
267
|
+
this.errorCb = cb
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
|
|
3
|
+
export default function rmDir (dirPath) {
|
|
4
|
+
try {
|
|
5
|
+
var files = fs.readdirSync(dirPath)
|
|
6
|
+
}
|
|
7
|
+
catch(e) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
if (files.length > 0)
|
|
12
|
+
for (var i = 0; i < files.length; i++) {
|
|
13
|
+
var filePath = dirPath + '/' + files[i];
|
|
14
|
+
if (fs.statSync(filePath).isFile())
|
|
15
|
+
fs.unlinkSync(filePath);
|
|
16
|
+
else
|
|
17
|
+
rmDir(filePath);
|
|
18
|
+
}
|
|
19
|
+
fs.rmdirSync(dirPath);
|
|
20
|
+
console.log(dirPath + ' cleaned')
|
|
21
|
+
};
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import copydir from 'copy-dir';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import fs from "fs";
|
|
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
|
+
const boilerplate = path.resolve(__dirname, '../boilerplate');
|
|
11
|
+
|
|
12
|
+
export default (dest) => new Promise((res, rej) => {
|
|
13
|
+
try {
|
|
14
|
+
if (fs.existsSync(dest)) {
|
|
15
|
+
throw `Directory '${dest}'exists`
|
|
16
|
+
} else {
|
|
17
|
+
dest = path.resolve(dest)
|
|
18
|
+
copydir.sync(boilerplate, dest, {
|
|
19
|
+
filter: function(stat, _, filename){
|
|
20
|
+
if (stat === 'directory' && filename === 'dist') {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
return true; // remind to return a true value when file check passed.
|
|
24
|
+
}
|
|
25
|
+
});
|
|
26
|
+
res(`New Attics project created at ${dest}`)
|
|
27
|
+
}
|
|
28
|
+
} catch(e) {
|
|
29
|
+
rej(e)
|
|
30
|
+
}
|
|
31
|
+
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import fs from 'fs';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Reads multiple files and returns a map of file paths to file contents
|
|
6
|
+
* @param {string[]} files - Array of file paths
|
|
7
|
+
* @param {string} src - Source directory path
|
|
8
|
+
* @param {Object} accumulator - Accumulator object for results
|
|
9
|
+
* @returns {Object} Map of file paths to file contents
|
|
10
|
+
*/
|
|
11
|
+
export function readFilesToMap(files, src, accumulator = {}) {
|
|
12
|
+
if (files.length === 0) return accumulator;
|
|
13
|
+
|
|
14
|
+
let file = files.shift();
|
|
15
|
+
if (!file) return accumulator;
|
|
16
|
+
|
|
17
|
+
let key = file.substring(path.join(src).length);
|
|
18
|
+
accumulator[key] = fs.readFileSync(file).toString('utf8');
|
|
19
|
+
|
|
20
|
+
return files.length ? readFilesToMap(files, src, accumulator) : accumulator;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
// Keep the old name as an alias for backward compatibility
|
|
24
|
+
export const getTemplates = readFilesToMap;
|
package/package.json
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clovie",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Vintage web dev tooling with modern quality of life",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"static-site-generator",
|
|
7
|
+
"ssg",
|
|
8
|
+
"templating",
|
|
9
|
+
"handlebars",
|
|
10
|
+
"nunjucks",
|
|
11
|
+
"pug",
|
|
12
|
+
"mustache",
|
|
13
|
+
"scss",
|
|
14
|
+
"esbuild",
|
|
15
|
+
"live-reload",
|
|
16
|
+
"development-server",
|
|
17
|
+
"web-development",
|
|
18
|
+
"nodejs"
|
|
19
|
+
],
|
|
20
|
+
"license": "MIT",
|
|
21
|
+
"author": {
|
|
22
|
+
"name": "Adrian Miller",
|
|
23
|
+
"email": "code.mill@fastmail.com"
|
|
24
|
+
},
|
|
25
|
+
"repository": {
|
|
26
|
+
"type": "git",
|
|
27
|
+
"url": "https://github.com/adrianjonmiller/clovie.git"
|
|
28
|
+
},
|
|
29
|
+
"homepage": "https://github.com/adrianjonmiller/clovie#readme",
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/adrianjonmiller/clovie/issues"
|
|
32
|
+
},
|
|
33
|
+
"main": "lib/main.js",
|
|
34
|
+
"type": "module",
|
|
35
|
+
"bin": {
|
|
36
|
+
"clovie": "bin/cli.js"
|
|
37
|
+
},
|
|
38
|
+
"files": [
|
|
39
|
+
"lib/**/*",
|
|
40
|
+
"bin/**/*",
|
|
41
|
+
"config/**/*",
|
|
42
|
+
"templates/**/*",
|
|
43
|
+
"README.md",
|
|
44
|
+
"LICENSE"
|
|
45
|
+
],
|
|
46
|
+
"scripts": {
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
49
|
+
"test:run": "vitest run",
|
|
50
|
+
"docs:build": "cd docs && node ../bin/cli.js build",
|
|
51
|
+
"docs:dev": "cd docs && node ../bin/cli.js watch",
|
|
52
|
+
"prepublishOnly": "npm test"
|
|
53
|
+
},
|
|
54
|
+
"dependencies": {
|
|
55
|
+
"@babel/core": "^7.23.7",
|
|
56
|
+
"@babel/preset-env": "^7.23.7",
|
|
57
|
+
"esbuild": "^0.19.11",
|
|
58
|
+
"chokidar": "^3.5.3",
|
|
59
|
+
"command-line-args": "^5.2.1",
|
|
60
|
+
"express": "^4.18.2",
|
|
61
|
+
"handlebars": "^4.7.8",
|
|
62
|
+
"sass": "^1.69.5",
|
|
63
|
+
"socket.io": "^4.7.4",
|
|
64
|
+
"type-detect": "^4.0.8",
|
|
65
|
+
"babelify": "^10.0.0"
|
|
66
|
+
},
|
|
67
|
+
"devDependencies": {
|
|
68
|
+
"vitest": "^1.0.4"
|
|
69
|
+
},
|
|
70
|
+
"engines": {
|
|
71
|
+
"node": ">=18.0.0"
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# {{projectName}}
|
|
2
|
+
|
|
3
|
+
A static site built with Clovie.
|
|
4
|
+
|
|
5
|
+
## Getting Started
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
# Install dependencies
|
|
9
|
+
npm install
|
|
10
|
+
|
|
11
|
+
# Start development server
|
|
12
|
+
npm run dev
|
|
13
|
+
|
|
14
|
+
# Build for production
|
|
15
|
+
npm run build
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## Project Structure
|
|
19
|
+
|
|
20
|
+
- `views/` - HTML templates
|
|
21
|
+
- `scripts/` - JavaScript files
|
|
22
|
+
- `styles/` - SCSS files
|
|
23
|
+
- `assets/` - Static assets
|
|
24
|
+
- `dist/` - Build output
|
|
25
|
+
|
|
26
|
+
## Learn More
|
|
27
|
+
|
|
28
|
+
- [Clovie Documentation](https://github.com/your-org/clovie)
|
|
29
|
+
- [Examples](https://github.com/your-org/clovie/tree/main/examples)
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export default {
|
|
2
|
+
// Clovie will auto-detect these paths!
|
|
3
|
+
// Just add your data and models below
|
|
4
|
+
|
|
5
|
+
data: {
|
|
6
|
+
title: '{{projectName}}'
|
|
7
|
+
},
|
|
8
|
+
|
|
9
|
+
// Example models (uncomment to use):
|
|
10
|
+
// models: {
|
|
11
|
+
// posts: {
|
|
12
|
+
// template: '_post.html',
|
|
13
|
+
// output: 'post-{slug}.html',
|
|
14
|
+
// transform: (post) => ({
|
|
15
|
+
// ...post,
|
|
16
|
+
// excerpt: post.content.substring(0, 100) + '...'
|
|
17
|
+
// })
|
|
18
|
+
// }
|
|
19
|
+
// }
|
|
20
|
+
|
|
21
|
+
// Custom compiler (optional - Clovie has a good default)
|
|
22
|
+
// compiler: (template, data) => {
|
|
23
|
+
// return template.replace(/\{\{([^}]+)\}\}/g, (match, key) => {
|
|
24
|
+
// return data[key] || match;
|
|
25
|
+
// });
|
|
26
|
+
// }
|
|
27
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
console.log("Hello from Clovie!");
|