vowel 0.7.1-beta.0 → 0.7.1
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 +41 -239
- package/bin.js +73 -31
- package/cli.js +32 -0
- package/config.js +12 -0
- package/extractDate.js +1 -1
- package/package.json +8 -33
- package/pathDate.js +69 -0
- package/plugins/assets/index.js +1 -1
- package/plugins/atproto/index.js +5 -45
- package/plugins/atproto/publish.js +34 -9
- package/plugins/atproto/record.js +47 -4
- package/plugins/deploy-cloudflare/index.js +4 -1
- package/plugins/export/index.js +131 -0
- package/plugins/html/bundle/index.js +57 -23
- package/plugins/html/editor/Editor.svelte +9 -5
- package/plugins/html/editor/create-session.js +3 -1
- package/plugins/html/editor/directives.js +12 -0
- package/plugins/html/editor/frontmatter.js +42 -11
- package/plugins/html/editor/ingest.js +19 -8
- package/plugins/html/editor/serialize.js +8 -4
- package/plugins/html/image.js +11 -5
- package/plugins/html/index.js +211 -100
- package/plugins/html/partials.js +14 -8
- package/plugins/html/socialLinks.js +5 -8
- package/plugins/html/writeRules.js +193 -35
- package/plugins/images/favicon.png +0 -0
- package/plugins/images/index.js +41 -3
- package/plugins/markdown/colorScheme.js +70 -14
- package/plugins/markdown/frontmatter.js +18 -8
- package/plugins/markdown/index.js +90 -23
- package/plugins/markdown/metadata.js +74 -22
- package/plugins/markdown/typography.js +11 -11
- package/plugins/styles/index.js +4 -4
- package/plugins/xml/index.js +17 -10
- package/stylesheets/DefaultStyles.css +158 -105
- package/stylesheets/TypographyStyles.css +143 -76
- package/utils.js +74 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sam Littlefair
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -2,245 +2,47 @@
|
|
|
2
2
|
|
|
3
3
|
*Markdown websites in milliseconds*
|
|
4
4
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
## Roadmap
|
|
11
|
-
|
|
12
|
-
### Priorities
|
|
13
|
-
|
|
14
|
-
- [ ] Create CONTRIBUTING.md
|
|
15
|
-
- [ ]
|
|
16
|
-
|
|
17
|
-
### Launch checklist
|
|
18
|
-
|
|
19
|
-
- [x] CLI
|
|
20
|
-
- [x] Docs
|
|
21
|
-
- [x] Classes
|
|
22
|
-
- [x] Route patterns
|
|
23
|
-
- [x] Post list metadata
|
|
24
|
-
- [.] Transaction management
|
|
25
|
-
- [ ] Default 404 pages
|
|
26
|
-
|
|
27
|
-
- [.] Fixes
|
|
28
|
-
- [x] Global nav sublinks
|
|
29
|
-
- [x] Hot reload
|
|
30
|
-
- [x] Explicit asset handling
|
|
31
|
-
- [.] Change "job" to "resources"
|
|
32
|
-
- [.] ReadResource types
|
|
33
|
-
- [x] Processor utilities param
|
|
34
|
-
- [.] Delete old SQLite logic
|
|
35
|
-
- [.] Make metadata explicit in SQLiteTarget type
|
|
36
|
-
- [x] Add SQLite begin/commit logic
|
|
37
|
-
- [.] Remove "loadDatabase"
|
|
38
|
-
- [.] Check all types
|
|
39
|
-
- [.] File handling
|
|
40
|
-
- [x] CSS prefixing
|
|
41
|
-
- [x] Homepage in global nav
|
|
42
|
-
- [x] Metadata in header
|
|
43
|
-
- [x] Page list sort
|
|
44
|
-
- [x] Limit in sort
|
|
45
|
-
- [x] Metafiles
|
|
46
|
-
- [x] Robots.txt
|
|
47
|
-
- [x] Sitemap.xml
|
|
48
|
-
- [x] RSS
|
|
49
|
-
- [x] 404.html (just make 404.md)
|
|
50
|
-
- [ ] Markup
|
|
51
|
-
- [x] Image as figure
|
|
52
|
-
- [x] Admonitions
|
|
53
|
-
- [x] Heading anchors
|
|
54
|
-
- [ ] GFM emoji
|
|
55
|
-
- [ ] Mermaid
|
|
56
|
-
- [x] Slogan
|
|
57
|
-
- [x] TOC
|
|
58
|
-
- [x] ==mark==
|
|
59
|
-
- [ ] Non-urgent fixes / improvements
|
|
60
|
-
- [ ] Compare metadata on file update
|
|
61
|
-
- [x] Prune/cleanup step
|
|
62
|
-
- [x] Dependency type (file, settings, folder)
|
|
63
|
-
- [ ] New systems
|
|
64
|
-
- [x] Tags
|
|
65
|
-
- [ ] External links
|
|
66
|
-
- [ ] Syntax highlighting
|
|
67
|
-
- [ ] Social links
|
|
68
|
-
|
|
69
|
-
### High priority
|
|
70
|
-
|
|
71
|
-
- [x] Global nav
|
|
72
|
-
- [x] Breadcrumbs
|
|
73
|
-
- [x] Page classes
|
|
74
|
-
- [ ] GetMany "path exists" filter
|
|
75
|
-
- [ ] GetMany "extension" filter
|
|
76
|
-
- [ ] Delete settings logic
|
|
77
|
-
- [ ] Update settings logic
|
|
78
|
-
- [x] Logical operators for filters
|
|
79
|
-
- [ ] Customize menus
|
|
80
|
-
- [ ] More file handling plugins
|
|
81
|
-
- [ ] Fonts
|
|
82
|
-
- [x] .woff
|
|
83
|
-
- [x] .woff2
|
|
84
|
-
- [x] .ttf
|
|
85
|
-
- [x] .otf
|
|
86
|
-
- [ ] Handling
|
|
87
|
-
- [ ] Images
|
|
88
|
-
- [x] .png
|
|
89
|
-
- [x] .jpg
|
|
90
|
-
- [x] .jpeg
|
|
91
|
-
- [x] .webp
|
|
92
|
-
- [x] .gif
|
|
93
|
-
- [ ] Formatting
|
|
94
|
-
- [ ] Icons
|
|
95
|
-
- [x] .ico
|
|
96
|
-
- [ ] Vectors
|
|
97
|
-
- [x] .svg
|
|
98
|
-
- [ ] Styles
|
|
99
|
-
- [x] .css
|
|
100
|
-
- [x] Bundling
|
|
101
|
-
- [x] PDFs
|
|
102
|
-
- [x] Videos
|
|
103
|
-
- [x] Arbitrary data
|
|
104
|
-
- [x] Add homepage to global nav
|
|
105
|
-
- [x] Page lists
|
|
106
|
-
- [x] Dates
|
|
107
|
-
- [x] robots.txt
|
|
108
|
-
- [x] sitemap.xml
|
|
109
|
-
- [x] 404.html
|
|
110
|
-
- [ ] Better URL normalization (see notes)
|
|
111
|
-
- [x] Name casing
|
|
112
|
-
- [x] TUI
|
|
113
|
-
- [x] Create settings.md
|
|
114
|
-
- [x] Site title
|
|
115
|
-
- [ ] Webmentions
|
|
116
|
-
- [x] Logo
|
|
117
|
-
- [x] Wordmark
|
|
118
|
-
- [ ] Identity (rel=me)
|
|
119
|
-
- [x] Filename breadcrumbs
|
|
120
|
-
- [x] RSS
|
|
121
|
-
- [x] Sitemap
|
|
122
|
-
- [x] Create home.md
|
|
123
|
-
- [ ] Create folder settings files
|
|
124
|
-
- [ ] Title
|
|
125
|
-
- [ ] Breadcrumb
|
|
126
|
-
- [ ] Remove index file rules
|
|
127
|
-
- [x] Custom CSS
|
|
128
|
-
- [x] styles.css
|
|
129
|
-
- [ ] Any CSS file
|
|
130
|
-
- [x] Tags
|
|
131
|
-
- [ ] Date format settings
|
|
132
|
-
- [ ] ::mark::
|
|
133
|
-
- [x] Infer images
|
|
134
|
-
- [ ] Favicon
|
|
135
|
-
- [-] Webmentions
|
|
136
|
-
- [x] HTML boilerplate
|
|
137
|
-
- [ ] View transitions
|
|
138
|
-
- [x] Logo
|
|
139
|
-
- [x] Wordmark
|
|
140
|
-
- [ ] Sort nav items
|
|
141
|
-
- [ ] Canonical URL
|
|
142
|
-
- [ ] Handle external links
|
|
143
|
-
- [x] Admonitions
|
|
144
|
-
- [ ] Use hgroup for site title, page title, etc
|
|
145
|
-
- [x] Images as `<figure>`
|
|
146
|
-
- [ ] Hidden routes
|
|
147
|
-
- [ ] Frontmatter settings
|
|
148
|
-
- [ ] HTML
|
|
149
|
-
- [ ] RSS
|
|
150
|
-
- [ ] Sitemap
|
|
151
|
-
- [x] Heading anchors
|
|
152
|
-
- [ ] Taxonomy pages and smart frontmatter
|
|
153
|
-
- [x] CSS cache busting
|
|
154
|
-
- [x] Slogan in homepage title
|
|
155
|
-
- [ ] GFM emojis (:smile:)
|
|
156
|
-
|
|
157
|
-
### Medium priority
|
|
158
|
-
|
|
159
|
-
- [ ] Tests
|
|
160
|
-
- [ ] Internal backlinks
|
|
161
|
-
- [ ] Break code into multiple files
|
|
162
|
-
- [ ] Image optimization (unpic)
|
|
163
|
-
- [ ] [SVG by mask](https://pqina.nl/blog/set-svg-background-image-fill-color/) and [CSS icons](https://antfu.me/posts/icons-in-pure-css)
|
|
164
|
-
- [ ] WYSIWYG editor
|
|
165
|
-
- [x] Better signals
|
|
166
|
-
- [x] File-written callback
|
|
167
|
-
- [ ] Themes
|
|
168
|
-
- [ ] Deploy
|
|
169
|
-
- [ ] Cloudflare pages
|
|
170
|
-
- [ ] GitHub pages
|
|
171
|
-
- [-] Post-publish work (ping webmentions)
|
|
172
|
-
- [ ] [Desktop app](https://blackboard.sh/electrobun/docs/)
|
|
173
|
-
- [ ] Mermaid
|
|
174
|
-
- [ ] Codeblock syntax highlighting
|
|
175
|
-
- [ ] Extraction utilities (regex in archive)
|
|
176
|
-
- [ ] Smarter frontmatter
|
|
177
|
-
- [ ] Object dl
|
|
178
|
-
- [ ] Array ul
|
|
179
|
-
- [ ] Image
|
|
180
|
-
- [ ] URL
|
|
181
|
-
- [ ] Date
|
|
182
|
-
- [x] TOC
|
|
183
|
-
- [ ] Versioning/publishing script
|
|
184
|
-
- [ ] Verify all element types from Obsidian
|
|
185
|
-
- [ ] Order/position/kanban
|
|
186
|
-
- [ ] Next/prev links
|
|
187
|
-
- [ ] Banner
|
|
188
|
-
- [ ] CTA
|
|
189
|
-
- [ ] Menu toggle
|
|
190
|
-
- [ ] Icons sets
|
|
191
|
-
- [ ] Auto-icons (:fa-rocket:)
|
|
192
|
-
- [ ] Frontmatter icons
|
|
193
|
-
|
|
194
|
-
### Low priority
|
|
195
|
-
|
|
196
|
-
- [ ] Footnotes
|
|
197
|
-
- [ ] Frontmatter taxonomies
|
|
198
|
-
- [ ] Recursive frontmatter
|
|
199
|
-
- [ ] Browser search
|
|
200
|
-
- [ ] Pagination
|
|
201
|
-
- [ ] Dialogue/spoiler alert
|
|
202
|
-
- [ ] Copy code button
|
|
203
|
-
- [ ] Tabbed element
|
|
204
|
-
- [ ] Multi-lingual
|
|
205
|
-
- [ ] Routing rules (see notes)
|
|
206
|
-
- [ ] ATProto
|
|
207
|
-
- [ ] Liking
|
|
208
|
-
- [ ] Commenting
|
|
209
|
-
- [ ] More inferred properties
|
|
210
|
-
- [ ] Email
|
|
211
|
-
- [ ] Phone number
|
|
212
|
-
- [ ] Location
|
|
213
|
-
- [ ] Color
|
|
214
|
-
- [ ] Price
|
|
215
|
-
- [ ] ISBN
|
|
216
|
-
- Contact form
|
|
217
|
-
- Subscription form
|
|
218
|
-
- Faceted search
|
|
219
|
-
|
|
220
|
-
## Notes and questions
|
|
221
|
-
|
|
222
|
-
rel=author? rel=bookmark? rel=external?
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
Annotations (see notes)
|
|
226
|
-
|
|
227
|
-
Interactivity (see notes)
|
|
228
|
-
|
|
229
|
-
Calculated properties?
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
OG tags to consider:
|
|
5
|
+
Vowel turns a folder of markdown files into a website. Write pages as
|
|
6
|
+
plain `.md` files, in Obsidian or any editor; Vowel works out titles,
|
|
7
|
+
dates, breadcrumbs, menus, tags, a feed and a sitemap from the files
|
|
8
|
+
and how they're arranged, so there's almost nothing to configure.
|
|
233
9
|
|
|
10
|
+
**Documentation: [vowel.cc](https://vowel.cc)**
|
|
11
|
+
|
|
12
|
+
## Start
|
|
13
|
+
|
|
14
|
+
Vowel needs [Node.js](https://nodejs.org) 26 or later. In the folder
|
|
15
|
+
that holds your markdown:
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
npx vowel@latest
|
|
234
19
|
```
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
*/
|
|
20
|
+
|
|
21
|
+
The first time, a short setup offers to write a `settings.md` and a
|
|
22
|
+
`home.md`. Vowel then builds the site and serves a preview at
|
|
23
|
+
`http://localhost:8000`, updating it as you edit.
|
|
24
|
+
|
|
25
|
+
## Publish
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
vowel export ../my-site # build into a folder, to upload anywhere
|
|
29
|
+
vowel --command deploy # build and deploy to Cloudflare Pages
|
|
246
30
|
```
|
|
31
|
+
|
|
32
|
+
See [the docs](https://vowel.cc) for deploying, AT Protocol publishing,
|
|
33
|
+
themes, and every setting.
|
|
34
|
+
|
|
35
|
+
## How it's built
|
|
36
|
+
|
|
37
|
+
Vowel is a set of plugins for [Votive](https://github.com/samlfair/votive),
|
|
38
|
+
a file processor that turns sources into targets and rebuilds only what
|
|
39
|
+
an edit changed.
|
|
40
|
+
|
|
41
|
+
## Contributing
|
|
42
|
+
|
|
43
|
+
Run the tests with `node --test tests/*.js`. Plans and open questions
|
|
44
|
+
are in [ROADMAP.md](./ROADMAP.md).
|
|
45
|
+
|
|
46
|
+
## License
|
|
47
|
+
|
|
48
|
+
[MIT](./LICENSE)
|
package/bin.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import fs from "node:fs/promises"
|
|
4
|
-
import
|
|
4
|
+
import os from "node:os"
|
|
5
|
+
import { intro, note, text, select, confirm, spinner, outro, box, isCancel, cancel } from "@clack/prompts"
|
|
5
6
|
import { parse } from "@bomb.sh/args"
|
|
6
7
|
import t from "@bomb.sh/tab"
|
|
7
8
|
import votive from "votive"
|
|
@@ -19,7 +20,23 @@ function wait(ms) {
|
|
|
19
20
|
}
|
|
20
21
|
|
|
21
22
|
import init from "./index.js"
|
|
22
|
-
import { shouldClear } from "./cli.js"
|
|
23
|
+
import { shouldClear, normalizeDomain, wrongFolderWarning } from "./cli.js"
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* A prompt's answer - or, when the author pressed Ctrl-C or Escape, a
|
|
27
|
+
* clean exit. clack returns a cancel symbol instead of an answer, and
|
|
28
|
+
* the wizard used to carry on with it until a template string threw
|
|
29
|
+
* "Cannot convert a Symbol value to a string". Nothing has been written
|
|
30
|
+
* at that point: the wizard writes its files last.
|
|
31
|
+
* @template T
|
|
32
|
+
* @param {T | symbol} value
|
|
33
|
+
* @returns {T}
|
|
34
|
+
*/
|
|
35
|
+
function answer(value) {
|
|
36
|
+
if (!isCancel(value)) return /** @type {T} */ (value)
|
|
37
|
+
cancel("Setup cancelled. Nothing was written.")
|
|
38
|
+
process.exit(0)
|
|
39
|
+
}
|
|
23
40
|
|
|
24
41
|
/**
|
|
25
42
|
* Discards the build database. Only under --clear (alias --reset): an
|
|
@@ -64,14 +81,14 @@ async function wizard() {
|
|
|
64
81
|
{
|
|
65
82
|
box(`Welcome!\n\nVowel is a static blog generator. It bundles markdown into HTML.`)
|
|
66
83
|
|
|
67
|
-
await select({
|
|
84
|
+
answer(await select({
|
|
68
85
|
message: "Proceed",
|
|
69
86
|
options: [
|
|
70
87
|
{
|
|
71
88
|
value: true,
|
|
72
89
|
label: "OK"
|
|
73
90
|
}]
|
|
74
|
-
})
|
|
91
|
+
}))
|
|
75
92
|
|
|
76
93
|
box(`Vowel infers most relevant information from common markdown conventions, so it requires very little configuration.
|
|
77
94
|
|
|
@@ -80,25 +97,25 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
80
97
|
|
|
81
98
|
|
|
82
99
|
|
|
83
|
-
const proceed = await confirm({
|
|
100
|
+
const proceed = answer(await confirm({
|
|
84
101
|
message: "Generate settings file"
|
|
85
|
-
})
|
|
102
|
+
}))
|
|
86
103
|
|
|
87
104
|
if (proceed) {
|
|
88
105
|
const frontmatter = [`---`]
|
|
89
106
|
|
|
90
107
|
box(`Change these configurations by editing 'settings.md'.`)
|
|
91
108
|
|
|
92
|
-
const websiteName = await text({
|
|
109
|
+
const websiteName = answer(await text({
|
|
93
110
|
message: "Website name",
|
|
94
111
|
placeholder: "My Cool Website"
|
|
95
|
-
})
|
|
112
|
+
}))
|
|
96
113
|
|
|
97
114
|
frontmatter.push(`name: ${websiteName}`)
|
|
98
115
|
|
|
99
116
|
box(`Vowel comes loaded with themes. You can override these themes by writing styles in a 'styles.css' file at the root of your project.`)
|
|
100
117
|
|
|
101
|
-
const theme = await select({
|
|
118
|
+
const theme = answer(await select({
|
|
102
119
|
message: "Theme",
|
|
103
120
|
options: [
|
|
104
121
|
{
|
|
@@ -122,7 +139,7 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
122
139
|
hint: 'write your own styles in `./styles.css`'
|
|
123
140
|
}
|
|
124
141
|
]
|
|
125
|
-
})
|
|
142
|
+
}))
|
|
126
143
|
|
|
127
144
|
if (theme) {
|
|
128
145
|
frontmatter.push(`theme: ${theme}`)
|
|
@@ -130,21 +147,24 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
130
147
|
|
|
131
148
|
box(`To publish a sitemap and RSS feed, provide the domain that you plan to use.`)
|
|
132
149
|
|
|
133
|
-
const domain = await text({
|
|
150
|
+
const domain = answer(await text({
|
|
134
151
|
message: "Domain",
|
|
135
152
|
placeholder: "thebomb.com"
|
|
136
|
-
})
|
|
153
|
+
}))
|
|
137
154
|
|
|
138
|
-
|
|
139
|
-
|
|
155
|
+
// As pasted from a browser: https://Example.com/ is example.com.
|
|
156
|
+
const siteDomain = normalizeDomain(domain)
|
|
157
|
+
|
|
158
|
+
if (siteDomain) {
|
|
159
|
+
frontmatter.push(`domain: ${siteDomain}`)
|
|
140
160
|
}
|
|
141
161
|
|
|
142
162
|
box(`To credit an author on your RSS feed, provide an author.`)
|
|
143
163
|
|
|
144
|
-
const author = await text({
|
|
164
|
+
const author = answer(await text({
|
|
145
165
|
message: "Author",
|
|
146
166
|
placeholder: "George Constanza"
|
|
147
|
-
})
|
|
167
|
+
}))
|
|
148
168
|
|
|
149
169
|
if (author) {
|
|
150
170
|
frontmatter.push(`author: ${author}`)
|
|
@@ -152,15 +172,16 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
152
172
|
|
|
153
173
|
box(`Markdown files are pages. Filenames are breadcrumbs. Folders are website sections. 'home.md' is your homepage.`)
|
|
154
174
|
|
|
155
|
-
const homepage = await confirm({
|
|
175
|
+
const homepage = answer(await confirm({
|
|
156
176
|
message: "Generate homepage"
|
|
157
|
-
})
|
|
177
|
+
}))
|
|
158
178
|
|
|
159
|
-
// A
|
|
160
|
-
|
|
161
|
-
|
|
179
|
+
// A slogan is a page's; the homepage's is the site's hero. Only
|
|
180
|
+
// asked for the homepage the wizard is about to write.
|
|
181
|
+
const slogan = homepage ? answer(await text({
|
|
182
|
+
message: "Homepage slogan (leave empty to skip)",
|
|
162
183
|
placeholder: "All the news that's fit to blog"
|
|
163
|
-
})
|
|
184
|
+
})) : undefined
|
|
164
185
|
|
|
165
186
|
const spin = spinner()
|
|
166
187
|
|
|
@@ -178,14 +199,14 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
178
199
|
|
|
179
200
|
const homeExists = await exists("home.md")
|
|
180
201
|
|
|
181
|
-
if (!homeExists) {
|
|
182
|
-
const homeFrontmatter =
|
|
202
|
+
if (homepage && !homeExists) {
|
|
203
|
+
const homeFrontmatter = slogan ? `---\nslogan: ${slogan}\n---\n` : ""
|
|
183
204
|
await fs.writeFile("home.md", `${homeFrontmatter}# Home\n\nWelcome home!`, "utf-8")
|
|
184
205
|
}
|
|
185
206
|
|
|
186
207
|
spin.clear()
|
|
187
208
|
|
|
188
|
-
await select({
|
|
209
|
+
answer(await select({
|
|
189
210
|
message: "Ready to go. Vowel will now launch your website.",
|
|
190
211
|
options: [
|
|
191
212
|
{
|
|
@@ -193,7 +214,7 @@ The few configurations that Vowel uses live in a 'settings.md' file at the root
|
|
|
193
214
|
label: "Let's go"
|
|
194
215
|
}
|
|
195
216
|
]
|
|
196
|
-
})
|
|
217
|
+
}))
|
|
197
218
|
|
|
198
219
|
|
|
199
220
|
}
|
|
@@ -243,7 +264,29 @@ async function main() {
|
|
|
243
264
|
return
|
|
244
265
|
}
|
|
245
266
|
|
|
246
|
-
|
|
267
|
+
// `vowel export <folder>`: the export command (plugins/export), spelled
|
|
268
|
+
// as a word rather than --command/--payload.
|
|
269
|
+
if (args._?.[0] === "export") {
|
|
270
|
+
await runCLICommand("export", JSON.stringify({ path: args._[1] }))
|
|
271
|
+
return
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// A site is a folder with a settings.md; the wizard writes one. Not "no
|
|
275
|
+
// database yet", which was also true of every site cloned onto a new
|
|
276
|
+
// machine. And only with a terminal to ask in: under CI or a pipe the
|
|
277
|
+
// prompts waited forever.
|
|
278
|
+
const settingsExists = await exists("settings.md")
|
|
279
|
+
const interactive = Boolean(process.stdin.isTTY && process.stdout.isTTY)
|
|
280
|
+
|
|
281
|
+
if (!settingsExists && interactive) {
|
|
282
|
+
const entries = await fs.readdir(".")
|
|
283
|
+
const folder = process.cwd()
|
|
284
|
+
const warning = wrongFolderWarning(entries, { folder, home: folder === os.homedir() })
|
|
285
|
+
if (warning && !answer(await confirm({ message: warning, initialValue: false }))) {
|
|
286
|
+
cancel("Run vowel inside your site's folder, or in a new, empty one.")
|
|
287
|
+
return
|
|
288
|
+
}
|
|
289
|
+
}
|
|
247
290
|
|
|
248
291
|
if (shouldClear(args)) {
|
|
249
292
|
await removeDatabase(args.logging === "verbose")
|
|
@@ -251,10 +294,9 @@ async function main() {
|
|
|
251
294
|
}
|
|
252
295
|
await fs.mkdir(config.targetFolder, { recursive: true })
|
|
253
296
|
|
|
254
|
-
// The wizard runs
|
|
255
|
-
//
|
|
256
|
-
|
|
257
|
-
if (!args.skip && !dbExists) {
|
|
297
|
+
// The wizard runs for a new site only; --clear doesn't change that - a
|
|
298
|
+
// cleared project is still one that was set up.
|
|
299
|
+
if (!args.skip && !settingsExists && interactive) {
|
|
258
300
|
// const loading = votive({ ...config, verbose: false })
|
|
259
301
|
await wizard()
|
|
260
302
|
// await (loading)
|
package/cli.js
CHANGED
|
@@ -13,3 +13,35 @@
|
|
|
13
13
|
export function shouldClear(args) {
|
|
14
14
|
return Boolean(args?.clear || args?.reset)
|
|
15
15
|
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* A domain as settings.md wants it: `https://Example.com/` → `example.com`.
|
|
19
|
+
* What the wizard's prompt gets is whatever someone pasted from a browser.
|
|
20
|
+
* @param {unknown} value
|
|
21
|
+
* @returns {string}
|
|
22
|
+
*/
|
|
23
|
+
export function normalizeDomain(value) {
|
|
24
|
+
if (typeof value !== "string") return ""
|
|
25
|
+
return value.trim().replace(/^[a-z]+:\/\//i, "").replace(/\/+$/, "").toLowerCase()
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Whether this looks like the wrong folder to make a site in, and why,
|
|
30
|
+
* as the question to ask - or undefined when it looks fine. Vowel makes
|
|
31
|
+
* a site from everything in the folder it runs in, so starting it in a
|
|
32
|
+
* home folder, or a folder of other things, sets up and scans all of
|
|
33
|
+
* it. A new, empty folder is fine; so is one with markdown in it.
|
|
34
|
+
* Only asked when there is no settings.md: a folder that has one is a
|
|
35
|
+
* site already.
|
|
36
|
+
* @param {string[]} entries - the folder's own entries, by name
|
|
37
|
+
* @param {{ folder: string, home: boolean }} where
|
|
38
|
+
* @returns {string | undefined}
|
|
39
|
+
*/
|
|
40
|
+
export function wrongFolderWarning(entries, { folder, home }) {
|
|
41
|
+
const visible = entries.filter(name => !name.startsWith("."))
|
|
42
|
+
const markdown = visible.filter(name => name.toLowerCase().endsWith(".md"))
|
|
43
|
+
if (home) return `${folder} is your home folder. Vowel makes a website from everything in the folder it runs in. Set up a site here anyway?`
|
|
44
|
+
if (markdown.length || !visible.length) return undefined
|
|
45
|
+
const others = visible.length === 1 ? "1 other thing" : `${visible.length} other things`
|
|
46
|
+
return `${folder} has no markdown files, and ${others} in it. Vowel makes a website from everything in the folder it runs in. Set up a site here anyway?`
|
|
47
|
+
}
|
package/config.js
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import path from "node:path"
|
|
2
|
+
import { readFileSync } from "node:fs"
|
|
2
3
|
import { resolveProjectFolder } from "votive"
|
|
3
4
|
import { systemDirectoryFor } from "./systemPaths.js"
|
|
4
5
|
import { secretRouter } from "./secretPaths.js"
|
|
@@ -16,6 +17,7 @@ import vowelAtprotoPlugin from "./plugins/atproto/index.js"
|
|
|
16
17
|
import vowelPublishPlugin from "./plugins/publish/index.js"
|
|
17
18
|
import vowelURLsPlugin from "./plugins/urls/index.js"
|
|
18
19
|
import vowelSecretPagesPlugin from "./plugins/secret-pages/index.js"
|
|
20
|
+
import vowelExportPlugin from "./plugins/export/index.js"
|
|
19
21
|
|
|
20
22
|
/** @import {VotiveConfig} from "votive" */
|
|
21
23
|
|
|
@@ -29,6 +31,14 @@ import vowelSecretPagesPlugin from "./plugins/secret-pages/index.js"
|
|
|
29
31
|
* the values), and a hex digest is lowercase already.
|
|
30
32
|
* @param {string} sourcePath
|
|
31
33
|
*/
|
|
34
|
+
/**
|
|
35
|
+
* Vowel's part of the cache key (votive's cacheKey.js): a warm start
|
|
36
|
+
* reuses the build cache only if this same version of vowel made it, so
|
|
37
|
+
* an upgrade that changes a bundled stylesheet or how pages render is
|
|
38
|
+
* seen without a `--clear`.
|
|
39
|
+
*/
|
|
40
|
+
const VOWEL_VERSION = JSON.parse(readFileSync(new URL("./package.json", import.meta.url), "utf8")).version
|
|
41
|
+
|
|
32
42
|
function vowelRouter(sourcePath) {
|
|
33
43
|
return secretRouter(sourcePath).toLowerCase()
|
|
34
44
|
}
|
|
@@ -43,6 +53,7 @@ const defaultPlugins = [
|
|
|
43
53
|
vowelFontsPlugin,
|
|
44
54
|
vowelAssetsPlugin,
|
|
45
55
|
vowelDeployCloudflarePlugin,
|
|
56
|
+
vowelExportPlugin,
|
|
46
57
|
vowelURLsPlugin,
|
|
47
58
|
vowelAtprotoPlugin,
|
|
48
59
|
vowelPublishPlugin,
|
|
@@ -103,6 +114,7 @@ function createConfig(sourceFolder = ".", overrides = {}) {
|
|
|
103
114
|
databasePath: path.join(systemDirectory, ".votive.db"),
|
|
104
115
|
cacheDirectory: path.join(systemDirectory, ".cache"),
|
|
105
116
|
plugins: [...defaultPlugins, ...(extraPlugins ?? [])],
|
|
117
|
+
cacheKey: `vowel@${VOWEL_VERSION}`,
|
|
106
118
|
...rest
|
|
107
119
|
}
|
|
108
120
|
}
|
package/extractDate.js
CHANGED