@terrymooreii/sia 2.1.6 → 2.1.8
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 +61 -1
- package/docs/creating-themes.md +216 -1
- package/docs/imgs/logo-sia-t.png +0 -0
- package/docs/imgs/logo-sia.png +0 -0
- package/docs/imgs/sia2.png +0 -0
- package/lib/assets.js +14 -7
- package/lib/build.js +8 -4
- package/lib/init.js +3 -3
- package/lib/templates.js +11 -4
- package/lib/theme-resolver.js +175 -0
- package/lib/theme.js +1524 -0
- package/package.json +1 -1
- package/readme.md +110 -1
- package/themes/developer/pages/blog.njk +1 -1
- package/themes/developer/pages/feed.njk +1 -1
- package/themes/developer/pages/index.njk +1 -1
- package/themes/developer/pages/notes.njk +1 -1
- package/themes/magazine/layouts/post.njk +2 -2
- package/themes/magazine/pages/blog.njk +1 -1
- package/themes/magazine/pages/index.njk +3 -3
- package/themes/magazine/pages/notes.njk +1 -1
- package/themes/main/includes/footer.njk +1 -1
- package/themes/main/pages/blog.njk +1 -1
- package/themes/main/pages/index.njk +2 -2
- package/themes/main/pages/tag.njk +3 -3
- package/themes/minimal/includes/footer.njk +1 -1
- package/themes/minimal/pages/blog.njk +1 -1
- package/themes/minimal/pages/index.njk +1 -1
- package/themes/minimal/pages/tag.njk +3 -3
- package/themes/minimal/pages/tags.njk +1 -1
package/bin/cli.js
CHANGED
|
@@ -14,6 +14,7 @@ import { devCommand } from '../lib/server.js';
|
|
|
14
14
|
import { buildCommand } from '../lib/build.js';
|
|
15
15
|
import { newCommand } from '../lib/new.js';
|
|
16
16
|
import { initCommand } from '../lib/init.js';
|
|
17
|
+
import { themeCommand } from '../lib/theme.js';
|
|
17
18
|
|
|
18
19
|
program
|
|
19
20
|
.name('sia')
|
|
@@ -47,5 +48,11 @@ program
|
|
|
47
48
|
.option('-d, --draft', 'Save as draft (posts only)')
|
|
48
49
|
.action(newCommand);
|
|
49
50
|
|
|
51
|
+
program
|
|
52
|
+
.command('theme <name>')
|
|
53
|
+
.description('Create a new Sia theme package')
|
|
54
|
+
.option('-q, --quick', 'Skip prompts and use defaults')
|
|
55
|
+
.action(themeCommand);
|
|
56
|
+
|
|
50
57
|
program.parse();
|
|
51
58
|
|
package/docs/README.md
CHANGED
|
@@ -54,8 +54,10 @@ npm run build
|
|
|
54
54
|
- **Tags & Categories** - Organize content with tags and auto-generated tag pages
|
|
55
55
|
- **Pagination** - Built-in pagination for listing pages
|
|
56
56
|
- **Image Support** - Automatic image copying and organization
|
|
57
|
+
- **Static Assets** - Support for favicons, fonts, and other static files
|
|
57
58
|
- **Live Reload** - Development server with hot reloading
|
|
58
59
|
- **Multiple Themes** - Built-in themes (main, minimal, developer, magazine) with light/dark mode
|
|
60
|
+
- **Custom Theme Packages** - Create and share themes as npm packages (`sia-theme-*`)
|
|
59
61
|
- **RSS Feed** - Automatic RSS feed generation
|
|
60
62
|
- **SEO Ready** - Open Graph and Twitter Card meta tags included
|
|
61
63
|
|
|
@@ -69,6 +71,10 @@ my-site/
|
|
|
69
71
|
│ ├── pages/ # Static pages
|
|
70
72
|
│ ├── notes/ # Short notes/tweets
|
|
71
73
|
│ └── images/ # Images
|
|
74
|
+
├── assets/ # Static assets (optional)
|
|
75
|
+
├── static/ # Static assets (optional)
|
|
76
|
+
├── public/ # Static assets (optional)
|
|
77
|
+
├── favicon.ico # Site favicon (optional)
|
|
72
78
|
├── _layouts/ # Custom layouts (optional)
|
|
73
79
|
├── _includes/ # Custom includes (optional)
|
|
74
80
|
├── styles/ # Custom CSS (optional)
|
|
@@ -87,7 +93,8 @@ site:
|
|
|
87
93
|
author: "Your Name"
|
|
88
94
|
|
|
89
95
|
theme:
|
|
90
|
-
name: main #
|
|
96
|
+
name: main # Built-in: main, minimal, developer, magazine
|
|
97
|
+
# Or use external: my-theme (loads sia-theme-my-theme)
|
|
91
98
|
|
|
92
99
|
input: src
|
|
93
100
|
output: dist
|
|
@@ -116,6 +123,58 @@ server:
|
|
|
116
123
|
showDrafts: false
|
|
117
124
|
```
|
|
118
125
|
|
|
126
|
+
## Static Assets
|
|
127
|
+
|
|
128
|
+
Sia automatically copies static assets during the build process. You can place static files in any of these locations:
|
|
129
|
+
|
|
130
|
+
- **`assets/`** - Place files in `assets/` at the project root
|
|
131
|
+
- **`static/`** - Place files in `static/` at the project root
|
|
132
|
+
- **`public/`** - Place files in `public/` at the project root
|
|
133
|
+
- **Root directory** - Place `favicon.ico` directly in the project root
|
|
134
|
+
|
|
135
|
+
All files from these directories will be copied to the `dist/` folder during build, preserving their directory structure.
|
|
136
|
+
|
|
137
|
+
### Supported File Types
|
|
138
|
+
|
|
139
|
+
Static assets include:
|
|
140
|
+
- **Favicons** - `.ico` files (favicon.ico can be in root or asset directories)
|
|
141
|
+
- **Fonts** - `.woff`, `.woff2`, `.ttf`, `.eot`
|
|
142
|
+
- **Documents** - `.pdf`, `.txt`, `.json`, `.xml`
|
|
143
|
+
- **Scripts** - `.js` files
|
|
144
|
+
- **Stylesheets** - `.css` files (though custom CSS is better placed in `styles/`)
|
|
145
|
+
- **Images** - All image formats (though images are better placed in `src/images/`)
|
|
146
|
+
|
|
147
|
+
### Example Structure
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
my-site/
|
|
151
|
+
├── assets/
|
|
152
|
+
│ ├── favicon.ico
|
|
153
|
+
│ ├── robots.txt
|
|
154
|
+
│ ├── manifest.json
|
|
155
|
+
│ └── fonts/
|
|
156
|
+
│ └── custom-font.woff2
|
|
157
|
+
├── static/
|
|
158
|
+
│ └── documents/
|
|
159
|
+
│ └── resume.pdf
|
|
160
|
+
└── favicon.ico # Also supported in root
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
During build, these will be copied to:
|
|
164
|
+
```
|
|
165
|
+
dist/
|
|
166
|
+
├── assets/
|
|
167
|
+
│ ├── favicon.ico
|
|
168
|
+
│ ├── robots.txt
|
|
169
|
+
│ ├── manifest.json
|
|
170
|
+
│ └── fonts/
|
|
171
|
+
│ └── custom-font.woff2
|
|
172
|
+
├── static/
|
|
173
|
+
│ └── documents/
|
|
174
|
+
│ └── resume.pdf
|
|
175
|
+
└── favicon.ico
|
|
176
|
+
```
|
|
177
|
+
|
|
119
178
|
## CLI Commands
|
|
120
179
|
|
|
121
180
|
| Command | Description |
|
|
@@ -126,6 +185,7 @@ server:
|
|
|
126
185
|
| `sia new post "Title"` | Create a new blog post |
|
|
127
186
|
| `sia new page "Title"` | Create a new page |
|
|
128
187
|
| `sia new note "Content"` | Create a new note |
|
|
188
|
+
| `sia theme <name>` | Create a new theme package |
|
|
129
189
|
|
|
130
190
|
## License
|
|
131
191
|
|
package/docs/creating-themes.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Creating Themes
|
|
2
2
|
|
|
3
|
-
This guide explains how to create custom themes for Sia
|
|
3
|
+
This guide explains how to create custom themes for Sia, distribute them as npm packages, and customize existing themes.
|
|
4
4
|
|
|
5
5
|
## Table of Contents
|
|
6
6
|
|
|
@@ -13,6 +13,9 @@ This guide explains how to create custom themes for Sia and how to customize exi
|
|
|
13
13
|
- [Styles](#styles)
|
|
14
14
|
- [Shared Includes](#shared-includes)
|
|
15
15
|
- [Dark Mode Support](#dark-mode-support)
|
|
16
|
+
- [External Theme Packages](#external-theme-packages)
|
|
17
|
+
- [Creating a Theme Package](#creating-a-theme-package)
|
|
18
|
+
- [Publishing Your Theme](#publishing-your-theme)
|
|
16
19
|
- [Customizing Existing Themes](#customizing-existing-themes)
|
|
17
20
|
- [Built-in Themes](#built-in-themes)
|
|
18
21
|
- [Best Practices](#best-practices)
|
|
@@ -649,6 +652,218 @@ toggle.addEventListener('click', () => {
|
|
|
649
652
|
|
|
650
653
|
---
|
|
651
654
|
|
|
655
|
+
## External Theme Packages
|
|
656
|
+
|
|
657
|
+
Sia supports distributing themes as npm packages, making it easy to share themes with the community.
|
|
658
|
+
|
|
659
|
+
### How Theme Resolution Works
|
|
660
|
+
|
|
661
|
+
When Sia loads a theme, it follows this resolution order:
|
|
662
|
+
|
|
663
|
+
1. **Built-in themes** - First checks the `themes/` folder in the Sia package
|
|
664
|
+
2. **npm packages** - If not found, looks for `sia-theme-{name}` in your `package.json` dependencies
|
|
665
|
+
3. **Fallback** - Falls back to the "main" theme if nothing is found
|
|
666
|
+
|
|
667
|
+
### Using an External Theme
|
|
668
|
+
|
|
669
|
+
To use an external theme in your Sia site:
|
|
670
|
+
|
|
671
|
+
```bash
|
|
672
|
+
# Install the theme package
|
|
673
|
+
npm install sia-theme-awesome
|
|
674
|
+
```
|
|
675
|
+
|
|
676
|
+
Then configure it in your `_config.yml`:
|
|
677
|
+
|
|
678
|
+
```yaml
|
|
679
|
+
theme:
|
|
680
|
+
name: awesome # Sia will look for sia-theme-awesome
|
|
681
|
+
```
|
|
682
|
+
|
|
683
|
+
That's it! Sia automatically detects and uses the theme from `node_modules`.
|
|
684
|
+
|
|
685
|
+
### Theme Package Requirements
|
|
686
|
+
|
|
687
|
+
External theme packages must:
|
|
688
|
+
|
|
689
|
+
1. **Follow naming convention** - Package name must be `sia-theme-{name}`
|
|
690
|
+
2. **Export theme directory** - Include an `index.js` that exports the theme path
|
|
691
|
+
3. **Include required files** - Have `layouts/` and `pages/` directories (minimum)
|
|
692
|
+
4. **Follow theme structure** - Match the same structure as built-in themes
|
|
693
|
+
|
|
694
|
+
---
|
|
695
|
+
|
|
696
|
+
## Creating a Theme Package
|
|
697
|
+
|
|
698
|
+
### Using the Theme Generator
|
|
699
|
+
|
|
700
|
+
The easiest way to create a new theme is with the built-in generator:
|
|
701
|
+
|
|
702
|
+
```bash
|
|
703
|
+
sia theme my-awesome-theme
|
|
704
|
+
```
|
|
705
|
+
|
|
706
|
+
This creates a complete theme package with all necessary files:
|
|
707
|
+
|
|
708
|
+
```
|
|
709
|
+
sia-theme-my-awesome-theme/
|
|
710
|
+
├── package.json # npm package configuration
|
|
711
|
+
├── index.js # Exports theme directory path
|
|
712
|
+
├── README.md # Theme documentation
|
|
713
|
+
├── layouts/
|
|
714
|
+
│ ├── base.njk # Base HTML template
|
|
715
|
+
│ ├── post.njk # Blog post layout
|
|
716
|
+
│ ├── page.njk # Static page layout
|
|
717
|
+
│ └── note.njk # Note layout
|
|
718
|
+
├── includes/
|
|
719
|
+
│ ├── header.njk # Site header/navigation
|
|
720
|
+
│ ├── footer.njk # Site footer
|
|
721
|
+
│ ├── hero.njk # Homepage hero section
|
|
722
|
+
│ ├── pagination.njk # Pagination component
|
|
723
|
+
│ └── tag-list.njk # Tag cloud component
|
|
724
|
+
├── pages/
|
|
725
|
+
│ ├── index.njk # Homepage
|
|
726
|
+
│ ├── blog.njk # Blog listing
|
|
727
|
+
│ ├── notes.njk # Notes listing
|
|
728
|
+
│ ├── tags.njk # All tags page
|
|
729
|
+
│ ├── tag.njk # Single tag page
|
|
730
|
+
│ └── feed.njk # RSS feed
|
|
731
|
+
└── styles/
|
|
732
|
+
└── main.css # Theme styles
|
|
733
|
+
```
|
|
734
|
+
|
|
735
|
+
### Generator Options
|
|
736
|
+
|
|
737
|
+
```bash
|
|
738
|
+
# Interactive mode (prompts for details)
|
|
739
|
+
sia theme my-theme
|
|
740
|
+
|
|
741
|
+
# Quick mode (skip prompts, use defaults)
|
|
742
|
+
sia theme my-theme --quick
|
|
743
|
+
sia theme my-theme -q
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
### package.json Structure
|
|
747
|
+
|
|
748
|
+
The generated `package.json` includes:
|
|
749
|
+
|
|
750
|
+
```json
|
|
751
|
+
{
|
|
752
|
+
"name": "sia-theme-my-theme",
|
|
753
|
+
"version": "1.0.0",
|
|
754
|
+
"description": "My Theme theme for Sia static site generator",
|
|
755
|
+
"main": "index.js",
|
|
756
|
+
"type": "module",
|
|
757
|
+
"keywords": [
|
|
758
|
+
"sia",
|
|
759
|
+
"sia-theme",
|
|
760
|
+
"static-site",
|
|
761
|
+
"theme"
|
|
762
|
+
],
|
|
763
|
+
"author": "Your Name",
|
|
764
|
+
"license": "MIT",
|
|
765
|
+
"peerDependencies": {
|
|
766
|
+
"@terrymooreii/sia": ">=2.0.0"
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
```
|
|
770
|
+
|
|
771
|
+
### index.js Structure
|
|
772
|
+
|
|
773
|
+
The `index.js` exports the theme directory for Sia to locate:
|
|
774
|
+
|
|
775
|
+
```javascript
|
|
776
|
+
import { fileURLToPath } from 'url';
|
|
777
|
+
import { dirname } from 'path';
|
|
778
|
+
|
|
779
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
780
|
+
const __dirname = dirname(__filename);
|
|
781
|
+
|
|
782
|
+
// Export the theme directory path for Sia to use
|
|
783
|
+
export const themeDir = __dirname;
|
|
784
|
+
export default themeDir;
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### Manual Theme Creation
|
|
788
|
+
|
|
789
|
+
If you prefer to create a theme manually:
|
|
790
|
+
|
|
791
|
+
1. Create a new directory: `mkdir sia-theme-my-theme`
|
|
792
|
+
2. Initialize npm: `npm init`
|
|
793
|
+
3. Set the package name to `sia-theme-{name}`
|
|
794
|
+
4. Create the required directory structure
|
|
795
|
+
5. Add an `index.js` that exports the directory path
|
|
796
|
+
6. Add all required template files
|
|
797
|
+
|
|
798
|
+
---
|
|
799
|
+
|
|
800
|
+
## Publishing Your Theme
|
|
801
|
+
|
|
802
|
+
### Preparing for Publication
|
|
803
|
+
|
|
804
|
+
1. **Test locally** - Link your theme and test with a Sia site:
|
|
805
|
+
|
|
806
|
+
```bash
|
|
807
|
+
# In your theme directory
|
|
808
|
+
npm link
|
|
809
|
+
|
|
810
|
+
# In a Sia site directory
|
|
811
|
+
npm link sia-theme-my-theme
|
|
812
|
+
```
|
|
813
|
+
|
|
814
|
+
2. **Update package.json** - Add repository, bugs, and homepage URLs:
|
|
815
|
+
|
|
816
|
+
```json
|
|
817
|
+
{
|
|
818
|
+
"repository": {
|
|
819
|
+
"type": "git",
|
|
820
|
+
"url": "https://github.com/username/sia-theme-my-theme.git"
|
|
821
|
+
},
|
|
822
|
+
"bugs": {
|
|
823
|
+
"url": "https://github.com/username/sia-theme-my-theme/issues"
|
|
824
|
+
},
|
|
825
|
+
"homepage": "https://github.com/username/sia-theme-my-theme#readme"
|
|
826
|
+
}
|
|
827
|
+
```
|
|
828
|
+
|
|
829
|
+
3. **Write documentation** - Update the README with:
|
|
830
|
+
- Screenshots of the theme
|
|
831
|
+
- Installation instructions
|
|
832
|
+
- Configuration options
|
|
833
|
+
- Customization tips
|
|
834
|
+
|
|
835
|
+
### Publishing to npm
|
|
836
|
+
|
|
837
|
+
```bash
|
|
838
|
+
# Login to npm (if not already)
|
|
839
|
+
npm login
|
|
840
|
+
|
|
841
|
+
# Publish the package
|
|
842
|
+
npm publish
|
|
843
|
+
|
|
844
|
+
# Or publish with public access if scoped
|
|
845
|
+
npm publish --access public
|
|
846
|
+
```
|
|
847
|
+
|
|
848
|
+
### Versioning
|
|
849
|
+
|
|
850
|
+
Follow semantic versioning:
|
|
851
|
+
|
|
852
|
+
- **Patch** (1.0.1) - Bug fixes, minor style tweaks
|
|
853
|
+
- **Minor** (1.1.0) - New features, backward-compatible changes
|
|
854
|
+
- **Major** (2.0.0) - Breaking changes to templates or configuration
|
|
855
|
+
|
|
856
|
+
### Theme Discovery
|
|
857
|
+
|
|
858
|
+
To help users find your theme:
|
|
859
|
+
|
|
860
|
+
1. Use `sia-theme` in your npm keywords
|
|
861
|
+
2. Add a clear description
|
|
862
|
+
3. Include screenshots in your README
|
|
863
|
+
4. Consider creating a demo site
|
|
864
|
+
|
|
865
|
+
---
|
|
866
|
+
|
|
652
867
|
## Customizing Existing Themes
|
|
653
868
|
|
|
654
869
|
You don't need to create a full theme to customize your site.
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/lib/assets.js
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
rmdirSync
|
|
11
11
|
} from 'fs';
|
|
12
12
|
import { join, dirname, relative, extname } from 'path';
|
|
13
|
+
import { resolveTheme, getBuiltInThemesDir } from './theme-resolver.js';
|
|
13
14
|
|
|
14
15
|
// Supported asset extensions
|
|
15
16
|
const IMAGE_EXTENSIONS = ['.jpg', '.jpeg', '.png', '.gif', '.svg', '.webp', '.ico', '.avif'];
|
|
@@ -126,8 +127,11 @@ function hasCssFiles(dir) {
|
|
|
126
127
|
|
|
127
128
|
/**
|
|
128
129
|
* Copy default styles to output
|
|
130
|
+
*
|
|
131
|
+
* @param {object} config - Site configuration
|
|
132
|
+
* @param {object} [resolvedTheme] - Pre-resolved theme info from resolveTheme()
|
|
129
133
|
*/
|
|
130
|
-
export function copyDefaultStyles(config,
|
|
134
|
+
export function copyDefaultStyles(config, resolvedTheme = null) {
|
|
131
135
|
const outputStylesDir = join(config.outputDir, 'styles');
|
|
132
136
|
|
|
133
137
|
// Check if user has custom styles (must actually have CSS files)
|
|
@@ -140,20 +144,23 @@ export function copyDefaultStyles(config, themesDir) {
|
|
|
140
144
|
return copied;
|
|
141
145
|
}
|
|
142
146
|
|
|
143
|
-
//
|
|
147
|
+
// Resolve theme if not already resolved
|
|
144
148
|
const themeName = config.theme?.name || 'main';
|
|
145
|
-
const
|
|
149
|
+
const theme = resolvedTheme || resolveTheme(themeName, config.rootDir);
|
|
150
|
+
const themeStylesDir = join(theme.themeDir, 'styles');
|
|
146
151
|
|
|
147
152
|
if (existsSync(themeStylesDir)) {
|
|
148
153
|
const copied = copyAssets(themeStylesDir, outputStylesDir);
|
|
149
|
-
|
|
154
|
+
if (!theme.isExternal) {
|
|
155
|
+
console.log(`🎨 Using "${theme.themeName}" theme`);
|
|
156
|
+
}
|
|
150
157
|
return copied;
|
|
151
158
|
}
|
|
152
159
|
|
|
153
|
-
// Fallback to main theme if
|
|
154
|
-
const fallbackStylesDir = join(
|
|
160
|
+
// Fallback to main theme if theme styles not found
|
|
161
|
+
const fallbackStylesDir = join(getBuiltInThemesDir(), 'main', 'styles');
|
|
155
162
|
if (existsSync(fallbackStylesDir)) {
|
|
156
|
-
console.log(`⚠️ Theme "${themeName}" not found, using "main" theme`);
|
|
163
|
+
console.log(`⚠️ Theme "${themeName}" styles not found, using "main" theme styles`);
|
|
157
164
|
const copied = copyAssets(fallbackStylesDir, outputStylesDir);
|
|
158
165
|
return copied;
|
|
159
166
|
}
|
package/lib/build.js
CHANGED
|
@@ -5,10 +5,10 @@ import { loadConfig } from './config.js';
|
|
|
5
5
|
import { buildSiteData, paginate, getPaginationUrls } from './collections.js';
|
|
6
6
|
import { createTemplateEngine, renderTemplate } from './templates.js';
|
|
7
7
|
import { copyImages, copyDefaultStyles, copyStaticAssets, writeFile, ensureDir } from './assets.js';
|
|
8
|
+
import { resolveTheme } from './theme-resolver.js';
|
|
8
9
|
|
|
9
10
|
const __filename = fileURLToPath(import.meta.url);
|
|
10
11
|
const __dirname = dirname(__filename);
|
|
11
|
-
const themesDir = join(__dirname, '..', 'themes');
|
|
12
12
|
|
|
13
13
|
/**
|
|
14
14
|
* Clean the output directory
|
|
@@ -215,11 +215,15 @@ export async function build(options = {}) {
|
|
|
215
215
|
cleanOutput(config);
|
|
216
216
|
}
|
|
217
217
|
|
|
218
|
+
// Resolve theme once for both templates and assets
|
|
219
|
+
const themeName = config.theme?.name || 'main';
|
|
220
|
+
const resolvedTheme = resolveTheme(themeName, config.rootDir);
|
|
221
|
+
|
|
218
222
|
// Build site data (collections, tags, etc.)
|
|
219
223
|
const siteData = buildSiteData(config);
|
|
220
224
|
|
|
221
|
-
// Create template engine
|
|
222
|
-
const env = createTemplateEngine(config);
|
|
225
|
+
// Create template engine with resolved theme
|
|
226
|
+
const env = createTemplateEngine(config, resolvedTheme);
|
|
223
227
|
|
|
224
228
|
// Render all content items
|
|
225
229
|
let itemCount = 0;
|
|
@@ -244,7 +248,7 @@ export async function build(options = {}) {
|
|
|
244
248
|
|
|
245
249
|
// Copy assets
|
|
246
250
|
copyImages(config);
|
|
247
|
-
copyDefaultStyles(config,
|
|
251
|
+
copyDefaultStyles(config, resolvedTheme);
|
|
248
252
|
copyStaticAssets(config);
|
|
249
253
|
|
|
250
254
|
const duration = ((Date.now() - startTime) / 1000).toFixed(2);
|
package/lib/init.js
CHANGED
|
@@ -87,7 +87,7 @@ Welcome to my new blog! This is my first post.
|
|
|
87
87
|
|
|
88
88
|
## About This Site
|
|
89
89
|
|
|
90
|
-
This site is built with [Sia](https://github.com/
|
|
90
|
+
This site is built with [Sia](https://github.com/terrymooreii/sia), a simple and powerful static site generator.
|
|
91
91
|
|
|
92
92
|
## What's Next?
|
|
93
93
|
|
|
@@ -114,7 +114,7 @@ Hello! I'm ${author}. Welcome to my corner of the internet.
|
|
|
114
114
|
|
|
115
115
|
## About This Site
|
|
116
116
|
|
|
117
|
-
This site is built with [Sia](https://github.com/
|
|
117
|
+
This site is built with [Sia](https://github.com/terrymooreii/sia), a simple static site generator that supports:
|
|
118
118
|
|
|
119
119
|
- Markdown with front matter
|
|
120
120
|
- Blog posts, pages, and notes
|
|
@@ -216,7 +216,7 @@ Then upload the \`dist/\` folder to any static hosting.
|
|
|
216
216
|
|
|
217
217
|
return `# ${title}
|
|
218
218
|
|
|
219
|
-
A static site built with [Sia](https://github.com/
|
|
219
|
+
A static site built with [Sia](https://github.com/terrymooreii/sia).
|
|
220
220
|
|
|
221
221
|
## Getting Started
|
|
222
222
|
|
package/lib/templates.js
CHANGED
|
@@ -2,6 +2,7 @@ import nunjucks from 'nunjucks';
|
|
|
2
2
|
import { join, dirname } from 'path';
|
|
3
3
|
import { existsSync } from 'fs';
|
|
4
4
|
import { fileURLToPath } from 'url';
|
|
5
|
+
import { resolveTheme, getBuiltInThemesDir } from './theme-resolver.js';
|
|
5
6
|
|
|
6
7
|
const __filename = fileURLToPath(import.meta.url);
|
|
7
8
|
const __dirname = dirname(__filename);
|
|
@@ -190,8 +191,11 @@ function createUrlFilter(basePath) {
|
|
|
190
191
|
|
|
191
192
|
/**
|
|
192
193
|
* Create and configure the Nunjucks environment
|
|
194
|
+
*
|
|
195
|
+
* @param {object} config - Site configuration
|
|
196
|
+
* @param {object} [resolvedTheme] - Pre-resolved theme info from resolveTheme()
|
|
193
197
|
*/
|
|
194
|
-
export function createTemplateEngine(config) {
|
|
198
|
+
export function createTemplateEngine(config, resolvedTheme = null) {
|
|
195
199
|
// Set up template paths - user layouts first, then defaults
|
|
196
200
|
const templatePaths = [];
|
|
197
201
|
|
|
@@ -205,15 +209,18 @@ export function createTemplateEngine(config) {
|
|
|
205
209
|
templatePaths.push(config.includesDir);
|
|
206
210
|
}
|
|
207
211
|
|
|
208
|
-
//
|
|
212
|
+
// Resolve theme if not already resolved
|
|
209
213
|
const themeName = config.theme?.name || 'main';
|
|
210
|
-
const
|
|
214
|
+
const theme = resolvedTheme || resolveTheme(themeName, config.rootDir);
|
|
215
|
+
const themeDir = theme.themeDir;
|
|
216
|
+
|
|
217
|
+
// Add theme template paths
|
|
211
218
|
templatePaths.push(join(themeDir, 'layouts'));
|
|
212
219
|
templatePaths.push(join(themeDir, 'includes'));
|
|
213
220
|
templatePaths.push(join(themeDir, 'pages'));
|
|
214
221
|
|
|
215
222
|
// Shared includes available to all themes
|
|
216
|
-
const sharedIncludesDir = join(
|
|
223
|
+
const sharedIncludesDir = join(getBuiltInThemesDir(), '_shared', 'includes');
|
|
217
224
|
templatePaths.push(sharedIncludesDir);
|
|
218
225
|
|
|
219
226
|
// Create the environment
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
import { existsSync, readFileSync, readdirSync, statSync } from 'fs';
|
|
2
|
+
import { join, dirname } from 'path';
|
|
3
|
+
import { fileURLToPath } from 'url';
|
|
4
|
+
import { createRequire } from 'module';
|
|
5
|
+
|
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
7
|
+
const __dirname = dirname(__filename);
|
|
8
|
+
|
|
9
|
+
// Built-in themes directory
|
|
10
|
+
const builtInThemesDir = join(__dirname, '..', 'themes');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Resolve the path to a theme directory
|
|
14
|
+
*
|
|
15
|
+
* Resolution order:
|
|
16
|
+
* 1. Check built-in themes folder (sia's themes/)
|
|
17
|
+
* 2. Check for npm package sia-theme-{name}
|
|
18
|
+
* 3. Fall back to 'main' theme
|
|
19
|
+
*
|
|
20
|
+
* @param {string} themeName - The theme name from config
|
|
21
|
+
* @param {string} rootDir - The user's project root directory
|
|
22
|
+
* @returns {{ themeDir: string, themeName: string, isExternal: boolean }}
|
|
23
|
+
*/
|
|
24
|
+
export function resolveTheme(themeName, rootDir = process.cwd()) {
|
|
25
|
+
// 1. Check built-in themes folder
|
|
26
|
+
const builtInThemeDir = join(builtInThemesDir, themeName);
|
|
27
|
+
if (existsSync(builtInThemeDir)) {
|
|
28
|
+
return {
|
|
29
|
+
themeDir: builtInThemeDir,
|
|
30
|
+
themeName,
|
|
31
|
+
isExternal: false
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// 2. Check for npm package sia-theme-{name}
|
|
36
|
+
const packageName = `sia-theme-${themeName}`;
|
|
37
|
+
const externalThemeDir = resolveExternalTheme(packageName, rootDir);
|
|
38
|
+
|
|
39
|
+
if (externalThemeDir) {
|
|
40
|
+
console.log(`🎨 Using external theme package: ${packageName}`);
|
|
41
|
+
return {
|
|
42
|
+
themeDir: externalThemeDir,
|
|
43
|
+
themeName,
|
|
44
|
+
isExternal: true
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// 3. Fall back to 'main' theme
|
|
49
|
+
if (themeName !== 'main') {
|
|
50
|
+
console.log(`⚠️ Theme "${themeName}" not found, falling back to "main" theme`);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return {
|
|
54
|
+
themeDir: join(builtInThemesDir, 'main'),
|
|
55
|
+
themeName: 'main',
|
|
56
|
+
isExternal: false
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Attempt to resolve an external theme package
|
|
62
|
+
*
|
|
63
|
+
* @param {string} packageName - The npm package name (sia-theme-{name})
|
|
64
|
+
* @param {string} rootDir - The user's project root directory
|
|
65
|
+
* @returns {string|null} The theme directory path or null if not found
|
|
66
|
+
*/
|
|
67
|
+
function resolveExternalTheme(packageName, rootDir) {
|
|
68
|
+
// First, check if the package is in the user's package.json
|
|
69
|
+
const packageJsonPath = join(rootDir, 'package.json');
|
|
70
|
+
|
|
71
|
+
if (!existsSync(packageJsonPath)) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
try {
|
|
76
|
+
const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
|
|
77
|
+
const allDeps = {
|
|
78
|
+
...packageJson.dependencies,
|
|
79
|
+
...packageJson.devDependencies
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
// Check if the theme package is listed as a dependency
|
|
83
|
+
if (!allDeps[packageName]) {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Try to resolve the package path
|
|
88
|
+
// Use createRequire to resolve from the user's project directory
|
|
89
|
+
const require = createRequire(join(rootDir, 'package.json'));
|
|
90
|
+
|
|
91
|
+
try {
|
|
92
|
+
// Try to resolve the package's main entry point
|
|
93
|
+
const packageMainPath = require.resolve(packageName);
|
|
94
|
+
const packageDir = dirname(packageMainPath);
|
|
95
|
+
|
|
96
|
+
// The theme directory is typically the package root
|
|
97
|
+
// Check if it has the expected theme structure
|
|
98
|
+
if (isValidThemeDirectory(packageDir)) {
|
|
99
|
+
return packageDir;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Sometimes the main entry is in a subdirectory, try parent
|
|
103
|
+
const parentDir = dirname(packageDir);
|
|
104
|
+
if (isValidThemeDirectory(parentDir)) {
|
|
105
|
+
return parentDir;
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Try node_modules directly
|
|
109
|
+
const nodeModulesPath = join(rootDir, 'node_modules', packageName);
|
|
110
|
+
if (existsSync(nodeModulesPath) && isValidThemeDirectory(nodeModulesPath)) {
|
|
111
|
+
return nodeModulesPath;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return null;
|
|
115
|
+
} catch (resolveErr) {
|
|
116
|
+
// Package might not be installed yet
|
|
117
|
+
// Try node_modules directly as fallback
|
|
118
|
+
const nodeModulesPath = join(rootDir, 'node_modules', packageName);
|
|
119
|
+
if (existsSync(nodeModulesPath) && isValidThemeDirectory(nodeModulesPath)) {
|
|
120
|
+
return nodeModulesPath;
|
|
121
|
+
}
|
|
122
|
+
return null;
|
|
123
|
+
}
|
|
124
|
+
} catch (err) {
|
|
125
|
+
return null;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Check if a directory has the expected theme structure
|
|
131
|
+
*
|
|
132
|
+
* @param {string} dir - Directory to check
|
|
133
|
+
* @returns {boolean}
|
|
134
|
+
*/
|
|
135
|
+
function isValidThemeDirectory(dir) {
|
|
136
|
+
if (!existsSync(dir)) {
|
|
137
|
+
return false;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// A valid theme must have at least layouts and pages directories
|
|
141
|
+
const hasLayouts = existsSync(join(dir, 'layouts'));
|
|
142
|
+
const hasPages = existsSync(join(dir, 'pages'));
|
|
143
|
+
|
|
144
|
+
return hasLayouts && hasPages;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* Get the built-in themes directory path
|
|
149
|
+
*
|
|
150
|
+
* @returns {string}
|
|
151
|
+
*/
|
|
152
|
+
export function getBuiltInThemesDir() {
|
|
153
|
+
return builtInThemesDir;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Get list of available built-in themes
|
|
158
|
+
*
|
|
159
|
+
* @returns {string[]}
|
|
160
|
+
*/
|
|
161
|
+
export function getBuiltInThemes() {
|
|
162
|
+
return readdirSync(builtInThemesDir)
|
|
163
|
+
.filter(name => {
|
|
164
|
+
if (name.startsWith('_')) return false; // Skip _shared
|
|
165
|
+
const themePath = join(builtInThemesDir, name);
|
|
166
|
+
return statSync(themePath).isDirectory();
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
export default {
|
|
171
|
+
resolveTheme,
|
|
172
|
+
getBuiltInThemesDir,
|
|
173
|
+
getBuiltInThemes
|
|
174
|
+
};
|
|
175
|
+
|