@terrymooreii/sia 2.1.13 → 2.3.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/bin/cli.js +7 -0
- package/docs/README.md +57 -0
- package/docs/creating-plugins.md +509 -0
- package/docs/front-matter.md +16 -8
- package/docs/plugins.md +320 -0
- package/lib/assets.js +36 -1
- package/lib/build.js +82 -6
- package/lib/collections.js +9 -3
- package/lib/config.js +7 -0
- package/lib/content.js +85 -24
- package/lib/hooks.js +188 -0
- package/lib/index.js +19 -0
- package/lib/migrate.js +180 -0
- package/lib/new.js +21 -9
- package/lib/plugins.js +308 -0
- package/lib/templates.js +7 -1
- package/package.json +1 -1
- package/readme.md +14 -3
package/bin/cli.js
CHANGED
|
@@ -15,6 +15,7 @@ import { buildCommand } from '../lib/build.js';
|
|
|
15
15
|
import { newCommand } from '../lib/new.js';
|
|
16
16
|
import { initCommand } from '../lib/init.js';
|
|
17
17
|
import { themeCommand } from '../lib/theme.js';
|
|
18
|
+
import { migrateCommand } from '../lib/migrate.js';
|
|
18
19
|
|
|
19
20
|
program
|
|
20
21
|
.name('sia')
|
|
@@ -54,5 +55,11 @@ program
|
|
|
54
55
|
.option('-q, --quick', 'Skip prompts and use defaults')
|
|
55
56
|
.action(themeCommand);
|
|
56
57
|
|
|
58
|
+
program
|
|
59
|
+
.command('migrate')
|
|
60
|
+
.description('Migrate standalone .md files to folder-based structure (folder/index.md)')
|
|
61
|
+
.option('--dry-run', 'Preview changes without applying them', false)
|
|
62
|
+
.action(migrateCommand);
|
|
63
|
+
|
|
57
64
|
program.parse();
|
|
58
65
|
|
package/docs/README.md
CHANGED
|
@@ -10,6 +10,8 @@ Welcome to the Sia documentation. Sia is a simple, powerful static site generato
|
|
|
10
10
|
| [Markdown Guide](markdown-guide.md) | Markdown syntax and all supported plugins |
|
|
11
11
|
| [Front Matter Reference](front-matter.md) | YAML front matter options for posts, pages, and notes |
|
|
12
12
|
| [Creating Themes](creating-themes.md) | How to create and customize themes |
|
|
13
|
+
| [Plugin System](plugins.md) | Extend Sia with plugins - hooks, API, and configuration |
|
|
14
|
+
| [Creating Plugins](creating-plugins.md) | Guide to creating local and npm package plugins |
|
|
13
15
|
|
|
14
16
|
## Quick Links
|
|
15
17
|
|
|
@@ -58,6 +60,7 @@ npm run build
|
|
|
58
60
|
- **Live Reload** - Development server with hot reloading
|
|
59
61
|
- **Multiple Themes** - Built-in themes (main, minimal, developer, magazine) with light/dark mode
|
|
60
62
|
- **Custom Theme Packages** - Create and share themes as npm packages (`sia-theme-*`)
|
|
63
|
+
- **Plugin System** - Extend functionality with local or npm plugins (`sia-plugin-*`)
|
|
61
64
|
- **RSS Feed** - Automatic RSS feed generation
|
|
62
65
|
- **SEO Ready** - Open Graph and Twitter Card meta tags included
|
|
63
66
|
|
|
@@ -68,8 +71,17 @@ my-site/
|
|
|
68
71
|
├── _config.yml # Site configuration
|
|
69
72
|
├── src/
|
|
70
73
|
│ ├── posts/ # Blog posts (markdown)
|
|
74
|
+
│ │ └── 2024-12-17-my-post/
|
|
75
|
+
│ │ ├── index.md
|
|
76
|
+
│ │ └── (assets can go here)
|
|
71
77
|
│ ├── pages/ # Static pages
|
|
78
|
+
│ │ └── about/
|
|
79
|
+
│ │ ├── index.md
|
|
80
|
+
│ │ └── (assets can go here)
|
|
72
81
|
│ ├── notes/ # Short notes/tweets
|
|
82
|
+
│ │ └── 2024-12-17-note-1234567890/
|
|
83
|
+
│ │ ├── index.md
|
|
84
|
+
│ │ └── (assets can go here)
|
|
73
85
|
│ └── images/ # Images
|
|
74
86
|
├── assets/ # Static assets (optional)
|
|
75
87
|
├── static/ # Static assets (optional)
|
|
@@ -77,10 +89,14 @@ my-site/
|
|
|
77
89
|
├── favicon.ico # Site favicon (optional)
|
|
78
90
|
├── _layouts/ # Custom layouts (optional)
|
|
79
91
|
├── _includes/ # Custom includes (optional)
|
|
92
|
+
├── _plugins/ # Local plugins (optional)
|
|
93
|
+
│ └── my-plugin.js
|
|
80
94
|
├── styles/ # Custom CSS (optional)
|
|
81
95
|
└── dist/ # Generated output
|
|
82
96
|
```
|
|
83
97
|
|
|
98
|
+
Each post, page, and note is created as a folder containing an `index.md` file. This allows you to organize assets (images, PDFs, etc.) alongside your content in the same folder.
|
|
99
|
+
|
|
84
100
|
## Configuration
|
|
85
101
|
|
|
86
102
|
Edit `_config.yml` to customize your site:
|
|
@@ -125,6 +141,12 @@ server:
|
|
|
125
141
|
assets:
|
|
126
142
|
css: [] # Custom CSS files (paths relative to root)
|
|
127
143
|
js: [] # Custom JavaScript files (paths relative to root)
|
|
144
|
+
|
|
145
|
+
plugins:
|
|
146
|
+
enabled: true # Master switch for plugins
|
|
147
|
+
strictMode: false # Fail build on plugin errors
|
|
148
|
+
order: [] # Explicit plugin execution order (optional)
|
|
149
|
+
config: {} # Plugin-specific configuration
|
|
128
150
|
```
|
|
129
151
|
|
|
130
152
|
## Custom CSS and JavaScript
|
|
@@ -162,6 +184,40 @@ This will:
|
|
|
162
184
|
|
|
163
185
|
And inject them into all pages automatically.
|
|
164
186
|
|
|
187
|
+
## Plugin System
|
|
188
|
+
|
|
189
|
+
Sia includes a powerful plugin system that allows you to extend functionality at key points in the build lifecycle. Plugins can:
|
|
190
|
+
|
|
191
|
+
- Transform content during parsing
|
|
192
|
+
- Generate additional files (search indexes, sitemaps, etc.)
|
|
193
|
+
- Add custom Marked extensions for markdown processing
|
|
194
|
+
- Register custom Nunjucks template filters and functions
|
|
195
|
+
- Modify site data before rendering
|
|
196
|
+
- Perform post-build tasks
|
|
197
|
+
|
|
198
|
+
### Using Plugins
|
|
199
|
+
|
|
200
|
+
Plugins can be local (in `_plugins/` directory) or npm packages (with `sia-plugin-*` naming):
|
|
201
|
+
|
|
202
|
+
```bash
|
|
203
|
+
# Install an npm plugin
|
|
204
|
+
npm install sia-plugin-search
|
|
205
|
+
|
|
206
|
+
# Or create a local plugin in _plugins/my-plugin.js
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
Configure plugins in `_config.yml`:
|
|
210
|
+
|
|
211
|
+
```yaml
|
|
212
|
+
plugins:
|
|
213
|
+
enabled: true
|
|
214
|
+
config:
|
|
215
|
+
sia-plugin-search:
|
|
216
|
+
outputPath: search-index.json
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
See the [Plugin System documentation](plugins.md) for complete details and the [Creating Plugins guide](creating-plugins.md) for examples.
|
|
220
|
+
|
|
165
221
|
## Static Assets
|
|
166
222
|
|
|
167
223
|
Sia automatically copies static assets during the build process. You can place static files in any of these locations:
|
|
@@ -225,6 +281,7 @@ dist/
|
|
|
225
281
|
| `sia new page "Title"` | Create a new page |
|
|
226
282
|
| `sia new note "Content"` | Create a new note |
|
|
227
283
|
| `sia theme <name>` | Create a new theme package |
|
|
284
|
+
| `sia migrate` | Migrate standalone .md files to folder structure |
|
|
228
285
|
|
|
229
286
|
## License
|
|
230
287
|
|
|
@@ -0,0 +1,509 @@
|
|
|
1
|
+
# Creating Plugins
|
|
2
|
+
|
|
3
|
+
This guide explains how to create plugins for Sia, both as local plugins in your project and as npm packages that can be shared with others.
|
|
4
|
+
|
|
5
|
+
## Plugin Types
|
|
6
|
+
|
|
7
|
+
Sia supports two types of plugins:
|
|
8
|
+
|
|
9
|
+
1. **Local plugins**: JavaScript files in your project's `_plugins/` directory
|
|
10
|
+
2. **NPM package plugins**: Published npm packages with the `sia-plugin-*` naming convention
|
|
11
|
+
|
|
12
|
+
## Local Plugins
|
|
13
|
+
|
|
14
|
+
Local plugins are perfect for project-specific functionality that you don't need to share.
|
|
15
|
+
|
|
16
|
+
### Creating a Local Plugin
|
|
17
|
+
|
|
18
|
+
1. Create a `_plugins/` directory in your project root (if it doesn't exist)
|
|
19
|
+
2. Create a JavaScript file (`.js` or `.mjs`) in that directory
|
|
20
|
+
3. Export a plugin object
|
|
21
|
+
|
|
22
|
+
**Example: `_plugins/search-index.js`**
|
|
23
|
+
|
|
24
|
+
```javascript
|
|
25
|
+
export default {
|
|
26
|
+
name: 'search-index',
|
|
27
|
+
version: '1.0.0',
|
|
28
|
+
hooks: {
|
|
29
|
+
afterBuild: async (siteData, config, api) => {
|
|
30
|
+
// Generate search index
|
|
31
|
+
const searchData = {
|
|
32
|
+
pages: siteData.collections.pages.map(item => ({
|
|
33
|
+
title: item.title,
|
|
34
|
+
url: item.url,
|
|
35
|
+
excerpt: item.excerpt,
|
|
36
|
+
tags: item.tags
|
|
37
|
+
})),
|
|
38
|
+
posts: siteData.collections.posts.map(item => ({
|
|
39
|
+
title: item.title,
|
|
40
|
+
url: item.url,
|
|
41
|
+
date: item.date.toISOString(),
|
|
42
|
+
excerpt: item.excerpt,
|
|
43
|
+
tags: item.tags
|
|
44
|
+
})),
|
|
45
|
+
notes: siteData.collections.notes.map(item => ({
|
|
46
|
+
title: item.title,
|
|
47
|
+
url: item.url,
|
|
48
|
+
date: item.date.toISOString(),
|
|
49
|
+
excerpt: item.excerpt
|
|
50
|
+
}))
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
// Write search index file
|
|
54
|
+
const outputPath = api.joinPath(config.outputDir, 'search-index.json');
|
|
55
|
+
api.writeFile(outputPath, JSON.stringify(searchData, null, 2));
|
|
56
|
+
api.log('Generated search index', 'info');
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
### Local Plugin with Configuration
|
|
63
|
+
|
|
64
|
+
You can access plugin-specific configuration from `config.plugins.config`:
|
|
65
|
+
|
|
66
|
+
```javascript
|
|
67
|
+
export default {
|
|
68
|
+
name: 'search-index',
|
|
69
|
+
version: '1.0.0',
|
|
70
|
+
configSchema: {
|
|
71
|
+
outputPath: { type: 'string', default: 'search-index.json' },
|
|
72
|
+
includeContent: { type: 'boolean', default: false }
|
|
73
|
+
},
|
|
74
|
+
hooks: {
|
|
75
|
+
afterBuild: async (siteData, config, api) => {
|
|
76
|
+
const pluginConfig = config.plugins?.config?.['search-index'] || {};
|
|
77
|
+
const outputPath = pluginConfig.outputPath || 'search-index.json';
|
|
78
|
+
const includeContent = pluginConfig.includeContent || false;
|
|
79
|
+
|
|
80
|
+
const searchData = {
|
|
81
|
+
pages: siteData.collections.pages.map(item => ({
|
|
82
|
+
title: item.title,
|
|
83
|
+
url: item.url,
|
|
84
|
+
excerpt: item.excerpt,
|
|
85
|
+
...(includeContent && { content: item.content })
|
|
86
|
+
})),
|
|
87
|
+
// ... posts, notes
|
|
88
|
+
};
|
|
89
|
+
|
|
90
|
+
api.writeFile(
|
|
91
|
+
api.joinPath(config.outputDir, outputPath),
|
|
92
|
+
JSON.stringify(searchData, null, 2)
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
};
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Configure in `_config.yml`:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
plugins:
|
|
103
|
+
config:
|
|
104
|
+
search-index:
|
|
105
|
+
outputPath: search-index.json
|
|
106
|
+
includeContent: false
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
## NPM Package Plugins
|
|
110
|
+
|
|
111
|
+
NPM package plugins can be shared with the community and installed via npm.
|
|
112
|
+
|
|
113
|
+
### Creating an NPM Package Plugin
|
|
114
|
+
|
|
115
|
+
1. Create a new npm package with name starting with `sia-plugin-`
|
|
116
|
+
2. Set up the package structure
|
|
117
|
+
3. Export the plugin object
|
|
118
|
+
|
|
119
|
+
### Package Structure
|
|
120
|
+
|
|
121
|
+
```
|
|
122
|
+
sia-plugin-example/
|
|
123
|
+
├── package.json
|
|
124
|
+
├── index.js
|
|
125
|
+
├── README.md
|
|
126
|
+
└── LICENSE
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### package.json
|
|
130
|
+
|
|
131
|
+
```json
|
|
132
|
+
{
|
|
133
|
+
"name": "sia-plugin-example",
|
|
134
|
+
"version": "1.0.0",
|
|
135
|
+
"description": "Example Sia plugin",
|
|
136
|
+
"main": "index.js",
|
|
137
|
+
"type": "module",
|
|
138
|
+
"keywords": [
|
|
139
|
+
"sia",
|
|
140
|
+
"sia-plugin",
|
|
141
|
+
"static-site-generator"
|
|
142
|
+
],
|
|
143
|
+
"author": "Your Name",
|
|
144
|
+
"license": "MIT",
|
|
145
|
+
"engines": {
|
|
146
|
+
"node": ">=18.0.0"
|
|
147
|
+
},
|
|
148
|
+
"peerDependencies": {
|
|
149
|
+
"@terrymooreii/sia": "^2.0.0"
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### index.js
|
|
155
|
+
|
|
156
|
+
```javascript
|
|
157
|
+
export default {
|
|
158
|
+
name: 'sia-plugin-example',
|
|
159
|
+
version: '1.0.0',
|
|
160
|
+
configSchema: {
|
|
161
|
+
enabled: { type: 'boolean', default: true },
|
|
162
|
+
outputFile: { type: 'string', default: 'example-output.json' }
|
|
163
|
+
},
|
|
164
|
+
hooks: {
|
|
165
|
+
afterBuild: async (siteData, config, api) => {
|
|
166
|
+
const pluginConfig = config.plugins?.config?.['sia-plugin-example'] || {};
|
|
167
|
+
|
|
168
|
+
if (pluginConfig.enabled === false) {
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const output = {
|
|
173
|
+
buildDate: new Date().toISOString(),
|
|
174
|
+
totalPages: siteData.collections.pages.length,
|
|
175
|
+
totalPosts: siteData.collections.posts.length,
|
|
176
|
+
totalNotes: siteData.collections.notes.length,
|
|
177
|
+
totalTags: siteData.allTags.length
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
api.writeFile(
|
|
181
|
+
api.joinPath(config.outputDir, pluginConfig.outputFile || 'example-output.json'),
|
|
182
|
+
JSON.stringify(output, null, 2)
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
api.log(`Generated ${pluginConfig.outputFile}`, 'info');
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Installing and Using
|
|
192
|
+
|
|
193
|
+
Users install your plugin:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
npm install sia-plugin-example
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
Then configure it in `_config.yml`:
|
|
200
|
+
|
|
201
|
+
```yaml
|
|
202
|
+
plugins:
|
|
203
|
+
config:
|
|
204
|
+
sia-plugin-example:
|
|
205
|
+
enabled: true
|
|
206
|
+
outputFile: stats.json
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
## Advanced Examples
|
|
210
|
+
|
|
211
|
+
### Content Transformation Plugin
|
|
212
|
+
|
|
213
|
+
Transform content during parsing:
|
|
214
|
+
|
|
215
|
+
```javascript
|
|
216
|
+
export default {
|
|
217
|
+
name: 'content-transformer',
|
|
218
|
+
version: '1.0.0',
|
|
219
|
+
hooks: {
|
|
220
|
+
beforeMarkdown: (markdown, context) => {
|
|
221
|
+
// Replace custom syntax
|
|
222
|
+
return markdown.replace(/\[TOC\]/g, '<!-- Table of Contents -->');
|
|
223
|
+
},
|
|
224
|
+
afterMarkdown: (html, context) => {
|
|
225
|
+
// Inject custom HTML
|
|
226
|
+
return html.replace(
|
|
227
|
+
'<!-- Table of Contents -->',
|
|
228
|
+
'<nav class="toc">...</nav>'
|
|
229
|
+
);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
};
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
### Custom Template Filter Plugin
|
|
236
|
+
|
|
237
|
+
Add custom Nunjucks filters:
|
|
238
|
+
|
|
239
|
+
```javascript
|
|
240
|
+
export default {
|
|
241
|
+
name: 'custom-filters',
|
|
242
|
+
version: '1.0.0',
|
|
243
|
+
hooks: {
|
|
244
|
+
addTemplateFilter: (env, config) => {
|
|
245
|
+
// Add a filter to format numbers
|
|
246
|
+
env.addFilter('formatNumber', (num) => {
|
|
247
|
+
return new Intl.NumberFormat().format(num);
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
// Add a filter to truncate text
|
|
251
|
+
env.addFilter('truncate', (str, length = 50) => {
|
|
252
|
+
if (str.length <= length) return str;
|
|
253
|
+
return str.substring(0, length) + '...';
|
|
254
|
+
});
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
};
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
Use in templates:
|
|
261
|
+
|
|
262
|
+
```nunjucks
|
|
263
|
+
{{ post.wordCount | formatNumber }}
|
|
264
|
+
{{ post.excerpt | truncate(100) }}
|
|
265
|
+
```
|
|
266
|
+
|
|
267
|
+
### Custom Marked Extension Plugin
|
|
268
|
+
|
|
269
|
+
Add custom markdown syntax:
|
|
270
|
+
|
|
271
|
+
```javascript
|
|
272
|
+
import { addMarkedExtension } from '@terrymooreii/sia';
|
|
273
|
+
|
|
274
|
+
export default {
|
|
275
|
+
name: 'custom-markdown',
|
|
276
|
+
version: '1.0.0',
|
|
277
|
+
hooks: {
|
|
278
|
+
beforeBuild: (config, api) => {
|
|
279
|
+
addMarkedExtension({
|
|
280
|
+
renderer: {
|
|
281
|
+
// Custom blockquote renderer
|
|
282
|
+
blockquote(quote) {
|
|
283
|
+
return `<blockquote class="custom-quote">${quote}</blockquote>`;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
});
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
};
|
|
290
|
+
```
|
|
291
|
+
|
|
292
|
+
### Sitemap Generator Plugin
|
|
293
|
+
|
|
294
|
+
Generate a sitemap.xml:
|
|
295
|
+
|
|
296
|
+
```javascript
|
|
297
|
+
export default {
|
|
298
|
+
name: 'sitemap-generator',
|
|
299
|
+
version: '1.0.0',
|
|
300
|
+
hooks: {
|
|
301
|
+
afterBuild: async (siteData, config, api) => {
|
|
302
|
+
const siteUrl = config.site.url.replace(/\/$/, '');
|
|
303
|
+
const basePath = config.site.basePath || '';
|
|
304
|
+
|
|
305
|
+
const urls = [];
|
|
306
|
+
|
|
307
|
+
// Add homepage
|
|
308
|
+
urls.push({
|
|
309
|
+
loc: `${siteUrl}${basePath}/`,
|
|
310
|
+
lastmod: new Date().toISOString().split('T')[0],
|
|
311
|
+
changefreq: 'daily',
|
|
312
|
+
priority: '1.0'
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
// Add all content items
|
|
316
|
+
for (const [collectionName, items] of Object.entries(siteData.collections)) {
|
|
317
|
+
for (const item of items) {
|
|
318
|
+
urls.push({
|
|
319
|
+
loc: `${siteUrl}${item.url}`,
|
|
320
|
+
lastmod: item.date.toISOString().split('T')[0],
|
|
321
|
+
changefreq: 'monthly',
|
|
322
|
+
priority: collectionName === 'pages' ? '0.8' : '0.6'
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Generate XML
|
|
328
|
+
const sitemap = `<?xml version="1.0" encoding="UTF-8"?>
|
|
329
|
+
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
|
330
|
+
${urls.map(url => ` <url>
|
|
331
|
+
<loc>${url.loc}</loc>
|
|
332
|
+
<lastmod>${url.lastmod}</lastmod>
|
|
333
|
+
<changefreq>${url.changefreq}</changefreq>
|
|
334
|
+
<priority>${url.priority}</priority>
|
|
335
|
+
</url>`).join('\n')}
|
|
336
|
+
</urlset>`;
|
|
337
|
+
|
|
338
|
+
api.writeFile(
|
|
339
|
+
api.joinPath(config.outputDir, 'sitemap.xml'),
|
|
340
|
+
sitemap
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
api.log('Generated sitemap.xml', 'info');
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
### Plugin with Dependencies
|
|
350
|
+
|
|
351
|
+
Plugins can depend on other plugins:
|
|
352
|
+
|
|
353
|
+
```javascript
|
|
354
|
+
export default {
|
|
355
|
+
name: 'enhanced-search',
|
|
356
|
+
version: '1.0.0',
|
|
357
|
+
dependencies: ['search-index'], // Requires search-index plugin
|
|
358
|
+
hooks: {
|
|
359
|
+
afterBuild: async (siteData, config, api) => {
|
|
360
|
+
// This plugin enhances the search index created by search-index plugin
|
|
361
|
+
// It runs after search-index because of the dependency
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
};
|
|
365
|
+
```
|
|
366
|
+
|
|
367
|
+
## Best Practices
|
|
368
|
+
|
|
369
|
+
### 1. Error Handling
|
|
370
|
+
|
|
371
|
+
Always handle errors gracefully:
|
|
372
|
+
|
|
373
|
+
```javascript
|
|
374
|
+
hooks: {
|
|
375
|
+
afterBuild: async (siteData, config, api) => {
|
|
376
|
+
try {
|
|
377
|
+
// Plugin logic
|
|
378
|
+
} catch (err) {
|
|
379
|
+
api.log(`Plugin error: ${err.message}`, 'error');
|
|
380
|
+
// Don't throw - let the build continue
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
}
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
### 2. Configuration Validation
|
|
387
|
+
|
|
388
|
+
Validate plugin configuration:
|
|
389
|
+
|
|
390
|
+
```javascript
|
|
391
|
+
hooks: {
|
|
392
|
+
beforeBuild: (config, api) => {
|
|
393
|
+
const pluginConfig = config.plugins?.config?.['my-plugin'] || {};
|
|
394
|
+
|
|
395
|
+
if (pluginConfig.requiredOption === undefined) {
|
|
396
|
+
throw new Error('my-plugin: requiredOption is required');
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
```
|
|
401
|
+
|
|
402
|
+
### 3. Async Operations
|
|
403
|
+
|
|
404
|
+
Use async/await for asynchronous operations:
|
|
405
|
+
|
|
406
|
+
```javascript
|
|
407
|
+
hooks: {
|
|
408
|
+
afterBuild: async (siteData, config, api) => {
|
|
409
|
+
const data = await fetchExternalData();
|
|
410
|
+
// Process data
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
```
|
|
414
|
+
|
|
415
|
+
### 4. Logging
|
|
416
|
+
|
|
417
|
+
Use the API's logging function:
|
|
418
|
+
|
|
419
|
+
```javascript
|
|
420
|
+
api.log('Processing complete', 'info');
|
|
421
|
+
api.log('Warning: something unusual', 'warn');
|
|
422
|
+
api.log('Error occurred', 'error');
|
|
423
|
+
```
|
|
424
|
+
|
|
425
|
+
### 5. Documentation
|
|
426
|
+
|
|
427
|
+
Document your plugin:
|
|
428
|
+
|
|
429
|
+
- Explain what it does
|
|
430
|
+
- List configuration options
|
|
431
|
+
- Provide usage examples
|
|
432
|
+
- Include version compatibility
|
|
433
|
+
|
|
434
|
+
### 6. Testing
|
|
435
|
+
|
|
436
|
+
Test your plugin:
|
|
437
|
+
|
|
438
|
+
1. Create a test Sia site
|
|
439
|
+
2. Add your plugin
|
|
440
|
+
3. Run `sia build`
|
|
441
|
+
4. Verify the output
|
|
442
|
+
|
|
443
|
+
### 7. Version Compatibility
|
|
444
|
+
|
|
445
|
+
Specify Sia version requirements in your plugin's README:
|
|
446
|
+
|
|
447
|
+
```markdown
|
|
448
|
+
## Requirements
|
|
449
|
+
|
|
450
|
+
- Sia >= 2.0.0
|
|
451
|
+
- Node.js >= 18.0.0
|
|
452
|
+
```
|
|
453
|
+
|
|
454
|
+
## Publishing NPM Plugins
|
|
455
|
+
|
|
456
|
+
1. **Prepare your package:**
|
|
457
|
+
- Write a clear README
|
|
458
|
+
- Add a LICENSE file
|
|
459
|
+
- Test thoroughly
|
|
460
|
+
|
|
461
|
+
2. **Publish to npm:**
|
|
462
|
+
```bash
|
|
463
|
+
npm login
|
|
464
|
+
npm publish
|
|
465
|
+
```
|
|
466
|
+
|
|
467
|
+
3. **Tag your releases:**
|
|
468
|
+
```bash
|
|
469
|
+
git tag v1.0.0
|
|
470
|
+
git push --tags
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
4. **Update documentation:**
|
|
474
|
+
- Add to Sia's plugin list (if applicable)
|
|
475
|
+
- Share on social media/forums
|
|
476
|
+
|
|
477
|
+
## Troubleshooting
|
|
478
|
+
|
|
479
|
+
### Plugin Not Loading
|
|
480
|
+
|
|
481
|
+
- Check that the plugin file is in `_plugins/` or installed via npm
|
|
482
|
+
- Verify the plugin exports a default object with `name` and `version`
|
|
483
|
+
- Check console output for error messages
|
|
484
|
+
|
|
485
|
+
### Hook Not Executing
|
|
486
|
+
|
|
487
|
+
- Verify the hook name is correct
|
|
488
|
+
- Check that the hook function is properly defined
|
|
489
|
+
- Ensure the plugin is enabled (`plugins.enabled: true`)
|
|
490
|
+
|
|
491
|
+
### Configuration Not Working
|
|
492
|
+
|
|
493
|
+
- Verify configuration is in `config.plugins.config[pluginName]`
|
|
494
|
+
- Check that plugin name matches exactly (case-sensitive)
|
|
495
|
+
- Validate configuration structure matches `configSchema`
|
|
496
|
+
|
|
497
|
+
### Build Failing
|
|
498
|
+
|
|
499
|
+
- Set `plugins.strictMode: false` to see detailed error messages
|
|
500
|
+
- Check plugin dependencies are installed
|
|
501
|
+
- Verify Node.js version compatibility
|
|
502
|
+
|
|
503
|
+
## Resources
|
|
504
|
+
|
|
505
|
+
- [Plugin System Documentation](./plugins.md) - Complete hook reference
|
|
506
|
+
- [Sia GitHub Repository](https://github.com/terrymooreii/sia) - Source code and issues
|
|
507
|
+
- [Marked Documentation](https://marked.js.org/) - For custom markdown extensions
|
|
508
|
+
- [Nunjucks Documentation](https://mozilla.github.io/nunjucks/) - For custom template filters/functions
|
|
509
|
+
|
package/docs/front-matter.md
CHANGED
|
@@ -366,19 +366,27 @@ permalink: /featured/special-post/
|
|
|
366
366
|
|
|
367
367
|
## Date from Filename
|
|
368
368
|
|
|
369
|
-
Sia can extract dates from filenames using this pattern:
|
|
369
|
+
Sia can extract dates from filenames (or folder names when using `index.md`) using this pattern:
|
|
370
370
|
|
|
371
371
|
```
|
|
372
372
|
YYYY-MM-DD-slug.md
|
|
373
373
|
```
|
|
374
374
|
|
|
375
|
+
or for folder-based content:
|
|
376
|
+
|
|
377
|
+
```
|
|
378
|
+
YYYY-MM-DD-slug/index.md
|
|
379
|
+
```
|
|
380
|
+
|
|
375
381
|
### Examples
|
|
376
382
|
|
|
377
|
-
| Filename | Extracted Date | Extracted Slug |
|
|
378
|
-
|
|
379
|
-
| `2024-12-17-my-post.md` | December 17, 2024 | `my-post` |
|
|
380
|
-
| `2024-01-05-new-year.md` | January 5, 2024 | `new-year` |
|
|
381
|
-
| `about.md` | Current date | `about` |
|
|
383
|
+
| Filename/Folder | Extracted Date | Extracted Slug |
|
|
384
|
+
|-----------------|----------------|----------------|
|
|
385
|
+
| `2024-12-17-my-post/index.md` | December 17, 2024 | `my-post` |
|
|
386
|
+
| `2024-01-05-new-year/index.md` | January 5, 2024 | `new-year` |
|
|
387
|
+
| `about/index.md` | Current date | `about` |
|
|
388
|
+
| `2024-12-17-my-post.md` | December 17, 2024 | `my-post` (backward compatible) |
|
|
389
|
+
| `about.md` | Current date | `about` (backward compatible) |
|
|
382
390
|
|
|
383
391
|
### Priority
|
|
384
392
|
|
|
@@ -391,8 +399,8 @@ Date resolution follows this priority:
|
|
|
391
399
|
Slug resolution:
|
|
392
400
|
|
|
393
401
|
1. `slug` in front matter (highest priority)
|
|
394
|
-
2. Slug extracted from filename (after date prefix)
|
|
395
|
-
3. Slugified filename
|
|
402
|
+
2. Slug extracted from filename or folder name (after date prefix if present)
|
|
403
|
+
3. Slugified filename or folder name
|
|
396
404
|
|
|
397
405
|
---
|
|
398
406
|
|