scavold 0.2.0-rc.1 → 0.2.0-rc.10

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.
Files changed (44) hide show
  1. package/CHANGELOG.md +337 -0
  2. package/COMPONENTS.md +413 -62
  3. package/FRONTMATTER.md +123 -3
  4. package/README.md +9 -4
  5. package/SECURITY.md +38 -0
  6. package/components/ScavoldCover.vue +162 -0
  7. package/components/ScavoldImage.vue +5 -1
  8. package/components/ScavoldLocaleMenu.vue +1 -1
  9. package/components/ScavoldLocaleRedirect.vue +1 -1
  10. package/components/ScavoldMenuItems.vue +3 -19
  11. package/components/ScavoldPageList.vue +110 -0
  12. package/components/ScavoldVideo.vue +12 -53
  13. package/composables/hierarchy.ts +75 -24
  14. package/composables/useAutoplay.js +128 -0
  15. package/composables/useContainer.js +1 -1
  16. package/composables/useCover.js +111 -0
  17. package/composables/useI18n.js +32 -6
  18. package/composables/usePageList.js +342 -0
  19. package/composables/useVideo.js +12 -22
  20. package/index.d.ts +42 -0
  21. package/l10n/de.json +12 -1
  22. package/l10n/en.json +12 -1
  23. package/lib/config.d.ts +1 -0
  24. package/lib/config.js +411 -55
  25. package/lib/containers.js +162 -12
  26. package/lib/dateFormat.js +125 -0
  27. package/lib/excerpt.js +144 -0
  28. package/lib/feed.js +109 -0
  29. package/lib/hide.d.ts +13 -0
  30. package/lib/hide.js +37 -0
  31. package/lib/href.d.ts +21 -0
  32. package/lib/href.js +62 -0
  33. package/lib/index.js +2 -3
  34. package/lib/legacyUrls.js +179 -0
  35. package/lib/media.js +125 -14
  36. package/lib/pageKeys.js +111 -0
  37. package/lib/pageList.js +38 -0
  38. package/lib/pages.js +113 -9
  39. package/lib/redirectTarget.js +6 -12
  40. package/lib/sectionManifest.js +144 -16
  41. package/lib/verifyBuild.js +324 -0
  42. package/package.json +17 -9
  43. package/scripts/check-csp.js +7 -1
  44. package/scripts/check-fixture.js +501 -0
package/CHANGELOG.md ADDED
@@ -0,0 +1,337 @@
1
+ # Changelog
2
+
3
+ All notable changes to Scavold are documented here. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), versions follow
5
+ [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
+
7
+ While the version stays below `1.0.0` and carries a pre-release suffix, breaking
8
+ changes may occur in any release.
9
+
10
+ ## [Unreleased]
11
+
12
+ ## [0.2.0-rc.10] — 2026-10-01
13
+
14
+ ### Added
15
+
16
+ - `:::cover` shows an image or a video across the full width and height of the
17
+ window, cropped to fit, with the block's content laid on top. An image comes with
18
+ all its variants and a `sizes` value that accounts for the cropping; a video plays
19
+ muted in a loop while at least half of it is on screen, stays still for visitors
20
+ asking for reduced motion, and always has a button to pause it. The editor offers it
21
+ as a section type.
22
+ - A container argument declared as `media-file` that names an image hands its
23
+ component the variants and dimensions as well, as `data-<arg>-srcset`,
24
+ `data-<arg>-webp-srcset`, `data-<arg>-width` and `data-<arg>-height`.
25
+ - `<ScavoldImage>` takes `loading` and `fetchpriority`, for an image at the top of a
26
+ page.
27
+ - `useAutoplay()` brings the same autoplay behaviour to a site's own video component.
28
+
29
+ ### Changed
30
+
31
+ - A `:::video` with `autoplay` plays only while at least half of it is on screen, and
32
+ pauses when scrolled away — unless the visitor turned the sound up. It stays paused
33
+ for visitors whose system asks for reduced motion, and once the visitor paused it.
34
+ The `autoplay` attribute is no longer part of the markup: it started the video at
35
+ once, wherever it was on the page and before reduced motion could be checked.
36
+
37
+ ### Removed
38
+
39
+ - The `overlay` and `controls` arguments of `:::video`. Use `:::cover` for a video
40
+ with content on top: the background mode had no way to pause a looping video, which
41
+ WCAG 2.2.2 requires, and ignored reduced motion. A `:::video` now always is a player
42
+ with its controls; a block still carrying `overlay` renders as one, with its content
43
+ as fallback text. `useVideo()` no longer returns `overlay` and `controls`, and the
44
+ `.scavold-video--overlay`, `.scavold-video__media` and `.scavold-video__overlay`
45
+ classes are gone.
46
+
47
+ ## [0.2.0-rc.9] — 2026-10-01
48
+
49
+ ### Fixed
50
+
51
+ - An image wider than the largest of `image_widths` named that width twice in its
52
+ `srcset`, and had its widest variant written twice at the same time.
53
+
54
+ ## [0.2.0-rc.8] — 2026-10-01
55
+
56
+ ### Added
57
+
58
+ - `SECURITY.md` says how to report a vulnerability and which versions receive fixes,
59
+ beside the [cratly security policy](https://cratly.io/security) it belongs to.
60
+ Every release's tag pipeline keeps a CycloneDX SBOM of what a site installs along
61
+ with scavold, and checks those packages for known vulnerabilities; `bun run sbom`
62
+ and `bun run audit:runtime` do the same locally.
63
+
64
+ ### Fixed
65
+
66
+ - A photo whose camera stored it turned, saying in its EXIF data how to show it, came
67
+ out upside down or lying on its side on the site, and a portrait got the variant
68
+ widths of a landscape. The variants are now turned the way the photo says, and
69
+ measured by the width it is shown at. Every image gets new variant names once, so
70
+ variants kept between builds are written again rather than served as they were.
71
+
72
+ ### Security
73
+
74
+ - `sharp` 0.35, whose bundled libvips and libheif fix vulnerabilities reported against
75
+ 0.34 (GHSA-f88m-g3jw-g9cj, GHSA-rgj7-g3m4-5g8c). It needs Node 20.9 or newer, and so
76
+ does scavold now.
77
+
78
+ ## [0.2.0-rc.7] — 2026-09-29
79
+
80
+ ### Fixed
81
+
82
+ - A link to a media file whose extension VitePress does not know, such as `.diff`,
83
+ pointed at the file name with `.html` appended and therefore at nothing. It now names
84
+ the file as published.
85
+
86
+ ## [0.2.0-rc.6] — 2026-09-13
87
+
88
+ ### Added
89
+
90
+ - `aliases` in a page's front matter names the former addresses it replaces, and
91
+ `retired_urls` in `.cratly.config.yaml` those that are not coming back. A site taking
92
+ over from an existing website declares them once and the build writes
93
+ `cratly-redirects.json` into its output: an address, where it went, and `301` or `410`.
94
+ The format names no server — only a server-side redirect passes a ranking on, and which
95
+ server that is cratly does not assume, so the deploy step translates the file for its
96
+ target. Keeping an address is still better than redirecting to it, which is what `url`
97
+ is for; the build refuses an alias that two pages claim, or that the site serves itself
98
+ and the redirect would therefore hide.
99
+ - Every build now ends by reading its own result. A page that the generator listed but
100
+ never wrote, or a script a built page names and the output does not contain, ends the
101
+ build with the file named — the two states a bundling fault leaves behind while the log
102
+ says "build complete". Links and images pointing at absent files are reported and left
103
+ to the site, since one may legitimately name a path the web server provides. Turn it
104
+ down with `verify: { content: false }` or off with `verify: false`. A reference is read
105
+ the way a browser reads it: a relative one against the address of the page holding it, so
106
+ a link that forgot its protocol — `[Website](./www.example.com)`, which the build turns
107
+ into a sibling page nobody wrote — is seen as well. Proven against the
108
+ case-collision that used to build green: the page missing from the output is the only
109
+ trace it leaves on a file system that ignores letter case, and that is now enough.
110
+ Everything found also goes to `.cratly/build-report.json`, written before the build is
111
+ ended rather than after, each entry naming the source file an author would open. A build
112
+ log is read by whoever has access to it; a file can be handed to whoever wrote the page.
113
+ - `ignoreDeadLinks` now defaults to `true`. VitePress ends a build on a dead link and does
114
+ so before writing anything, which leaves the reason in a job log that whoever wrote the
115
+ link often cannot read, and lets one typo stand between a finished page and its
116
+ publication. Scavold finds the same mistake in the finished output and reports it there
117
+ instead. Whether it should also stop a pipeline is a question whose answer differs per
118
+ branch, so it is left to CI: the site template fails a merge request over it and lets the
119
+ default branch deploy, since blocking there would answer a broken link with a stale site.
120
+ Ask for VitePress' abort back with `ignoreDeadLinks: false`.
121
+ - A site whose page paths collide in VitePress's eyes now learns that from Scavold, by
122
+ name, before the build starts. VitePress identifies a page by its path flattened into
123
+ a single name — slash becomes underscore, and the page-hash map lower-cases it — and
124
+ uses that name for the page's bundle entry, its server module and its client chunk.
125
+ Two pages meeting there overwrite each other: either the build dies at render time in
126
+ `pageChunk.imports`, naming no file, or it succeeds and ships two pages pointing at a
127
+ script that was never written. Scavold now reproduces that identity when it reads the
128
+ config and aborts with both file names, whether they collide through an underscore in
129
+ a file name (`de_kontakt.md` beside `de/kontakt.md`), through letter case alone, or
130
+ through a `url` alias landing on a path another page already owns as its file — the
131
+ case the previous alias check, which compared aliases only with each other, let pass.
132
+ - `date` and `datetime` join the type vocabulary of `frontmatter_fields` and of a
133
+ container's `props`. Scavold passes such a value through as the ISO 8601 text it is;
134
+ what changes is that the cratly editor now offers a date picker for it instead of a
135
+ plain text box. A `datetime` always carries an offset — a time of day without one is
136
+ a different moment on every machine that builds the site.
137
+
138
+ ### Changed
139
+
140
+ - `@cepharum/vue3-i18n` moves to `2.0.0`, two majors on from the `0.5.4` a site would
141
+ have installed. Scavold uses `useL10n`, `setLocale` and `setLoader`, none of which
142
+ changed; what 2.0 breaks are the locale helpers now returning full BCP-47 tags, and
143
+ Scavold calls none of them. A site inherits the new version with its next install, and
144
+ gains what 2.0 adds along the way: regional locales overlaying a bare language, plural
145
+ and gender selection resolved through `Intl`, and `Intl`-backed formatters in
146
+ placeholders.
147
+
148
+ ### Fixed
149
+
150
+ - `lib/href.js` ships the declarations it had been missing since it was split out, so
151
+ `typecheck` passes again — and the pipeline runs it now, which is why it could go
152
+ unnoticed at all. Nothing about the module changed; TypeScript simply had nothing to
153
+ read about it, and treated every href the hierarchy composes as `any`.
154
+
155
+ ## [0.2.0-rc.5] — 2026-08-17
156
+
157
+ ### Added
158
+
159
+ - `:::pagelist` dates its entries the way the site wants them: `time-style` adds the
160
+ time of day, `timezone` names the zone the timestamps are read in, and both accept
161
+ `iso` alongside the `Intl` styles, writing `2020-10-20 12:16` in every language. The
162
+ default is unchanged — the date alone, read in UTC, which is where a front matter
163
+ value carrying no time of day belongs.
164
+ - `formatTimestamp()` in `scavold/lib/dateFormat.js` renders timestamps the same way
165
+ for a theme's own components — a byline under a post's title, say. It is free of Vue
166
+ and VitePress and depends on its arguments alone, so pre-rendered markup and the
167
+ browser agree.
168
+
169
+ ### Changed
170
+
171
+ - A `:::pagelist` that paginates reaches every page it found: `limit` now defaults to
172
+ `0` when `per-page` is set, rather than capping the selection at five entries and
173
+ leaving a pager to turn the pages of those five. A limit the author sets still wins.
174
+ - Every link a site generates — menus, breadcrumbs, page lists, the feed, redirect targets
175
+ — names the file the page is built into: `/blog/post.html`, `/blog/index.html`,
176
+ `/index.html`. Links used to leave the extension off while VitePress's own markdown
177
+ links carried it, so one site spoke two dialects and the shorter one only worked on
178
+ servers that go looking for the file. Naming it is answered on the first attempt, and
179
+ the sitemap now lists the same URLs. The address bar still shows `/` and `/blog/`:
180
+ VitePress's router shortens them while navigating.
181
+
182
+ ### Fixed
183
+
184
+ - The site's own `index.md` is no longer linked as `/index`, a URL no server has to
185
+ answer. The rule for a page's URL existed in three copies and now lives in
186
+ `lib/href.js` alone.
187
+ - Scavold's built-in translations come from the bundle. They used to be fetched from a
188
+ URL that no build emits, so every page requested a file that was not there and the
189
+ interface silently fell back to English, whatever language the site is in.
190
+
191
+ ## [0.2.0-rc.4] — 2026-08-15
192
+
193
+ ### Added
194
+
195
+ - `metaChunk` is on by default, keeping VitePress's page-hash map out of the inline
196
+ scripts. Its content changes with every build, so under a content security policy that
197
+ names script hashes it has to be updated on the server for every deploy — and when it
198
+ is not, the map is blocked and the client side of the site stops working while the
199
+ pre-rendered HTML still shows. What stays inline are VitePress's own two snippets,
200
+ whose hashes hold still. `metaChunk: false` in the site's own config wins.
201
+ - An RSS feed, written at the end of the build when `augmentConfig` is given a `feed`
202
+ option: the pages of a section, newest first, with their excerpt as the entry text.
203
+ Announced are pages carrying a `date` — a feed is a chronology, and an entry a reader
204
+ cannot place in it is noise. Absolute links need a hostname, taken from
205
+ `sitemap.hostname` unless the feed names its own; without one the build says so and
206
+ writes nothing. Several feeds may be declared, one per language say. The feed is
207
+ compiled from the page hierarchy and is unrelated to `:::pagelist` — a page may appear
208
+ in three lists and in the feed, in the feed alone, or in neither.
209
+ - Front matter `hide` names surfaces: `"menu"`, `"breadcrumb"`, `"list"`, `"feed"`, a
210
+ list of several, or `true` for all of them. A page can now be kept out of the
211
+ navigation and the feed while staying in the lists.
212
+ - `:::pagelist` pages its entries in the browser with `per-page`: only the current page's
213
+ entries are rendered, the page shown is remembered as `?page=2` so returning from an
214
+ entry lands where the reader left off, and the pager is a labelled `<nav>` with a
215
+ status line and buttons.
216
+ - New built-in section `:::pagelist`, rendered by `<ScavoldPageList>`: links a
217
+ configurable number of the site's own pages — the latest posts of a blog, a section's
218
+ sub-pages, a list of downloads. Which pages are listed is addressed as in
219
+ `<ScavoldMenu>` (`from`, `from-root`, `from-path`, plus `deep` for pages filed in
220
+ sub-folders), and `limit`, `offset`, `sort` and `reverse` decide which of them appear
221
+ in what order. Entries are a plain list of links by default; the `teaser`, `images`,
222
+ `dates` and `heading-level` arguments turn them into teaser cards. Anything written
223
+ inside the block is rendered above the list. Each entry is a single link around all
224
+ of its parts, so a screen reader announces it once rather than three times.
225
+ - Pages carry teaser data on their hierarchy node, so a list shows content maintained
226
+ in the target page rather than repeated in the list: `date` (normalised to an ISO
227
+ timestamp and rendered in UTC, so a date-only value never shifts a day), `excerpt` —
228
+ from `excerpt` or `description` front matter, otherwise derived from the page's
229
+ opening prose and trimmed at a word boundary — and `image`, from `image` front matter
230
+ or the first image in the page body. Front matter images now go through the
231
+ responsive image pipeline too, which no pass reached before, as no Markdown syntax
232
+ reveals them.
233
+ - `.cratly.config.yaml` accepts `excerpt_length` (default `200`) to control how long
234
+ derived excerpts get; `0` switches the derivation off for sites that would rather not
235
+ carry it in every page's payload.
236
+ - Front matter `hide` accepts `"list"`, hiding a page from page lists only — a draft
237
+ post stays reachable by URL without being teased anywhere.
238
+ - `useHierarchy()` exposes `collectPages()`, `nodeHref()`, `nodeLabel()` and
239
+ `nodeTitle()`, the last two separating the short navigation text menus want from the
240
+ full title a teaser heading wants. `<ScavoldMenuItems>` uses them instead of its own
241
+ copies.
242
+
243
+ ## [0.2.0-rc.3] — 2026-08-09
244
+
245
+ ### Added
246
+
247
+ - Media that is not an image is published. Files an author uploads into the media folder
248
+ — PDFs, videos, archives — are copied into the static output keeping their path, and
249
+ references to them are rewritten to the URL the built site serves: Markdown links,
250
+ and container arguments declared as `media-file` such as a video's `src` or `poster`.
251
+ Previously only Markdown image syntax was handled, so a linked document or a video
252
+ resolved to a dead URL although the editor offered exactly that reference. Where a
253
+ single URL is needed instead of a srcset — a `poster`, a link to an image — the
254
+ largest generated variant is used.
255
+
256
+ ### Fixed
257
+
258
+ - Flag arguments on a container reach components that declare them as props. Bare words
259
+ used to become the container's `class` only, while `useVideo()` reads its booleans
260
+ from `data-*` props — so the documented `::: video src=… overlay autoplay loop`
261
+ rendered a plain inline player with no background mode, no autoplay and no loop, and
262
+ failed silently. Flags are now emitted as an empty `data-<flag>` attribute in addition
263
+ to the class. A flag a component does not declare as a prop shows up in the markup as
264
+ an empty attribute (`<section class="highlight" data-highlight>`).
265
+
266
+ ### Added
267
+
268
+ - Container blocks work with any name, declared or not: an undeclared name is rendered
269
+ by `ScavoldContainer` instead of leaving its fences in the page as literal text. To
270
+ keep a typo from silently becoming a `<div>`, the build reports each undeclared name
271
+ once; declaring it in `.cratly.config.yaml` silences the report and adds typed
272
+ properties for the editor.
273
+
274
+ ### Fixed
275
+
276
+ - `registerContainers()` and the component reference described `ScavoldContainer` as a
277
+ catch-all fallback, which it was not — no rule was registered for unknown names.
278
+
279
+ ## [0.2.0-rc.2] — 2026-08-06
280
+
281
+ ### Fixed
282
+
283
+ - `media_folder` from `.cratly.config.yaml` is honoured again. `augmentConfig()` read
284
+ every other folder declaration but this one, so image resolution fell back to the
285
+ pages folder: any image stored in the editor-managed media folder was reported as
286
+ missing and failed the build. No site could use an image from `media/`.
287
+
288
+ ### Changed
289
+
290
+ - The folder precedence rules moved into an exported, pure `resolveFolders()`, so an
291
+ explicit `srcDir` / `mediaDir` in the VitePress config beating the YAML declaration
292
+ is covered by unit tests instead of only by a running site.
293
+
294
+ ## [0.2.0-rc.1] — 2026-08-05
295
+
296
+ First published release. Version `0.1.0` existed in-tree only.
297
+
298
+ ### Added
299
+
300
+ - **Section-type manifest.** The build emits `.cratly/sections.json`, describing the
301
+ typed properties each container accepts, so the cratly editor can render matching
302
+ controls. Container declarations in `.cratly.config.yaml` support `props` (with
303
+ `flags` and `kv` as shorthands) and are merged with Scavold's built-in sections.
304
+ - **Background videos.** The `video` container accepts `overlay` to place the video
305
+ behind the block's content, plus `controls` to bring the native controls back in
306
+ that mode.
307
+ - Type declarations for both entry points (`scavold`, `scavold/config`), a `typecheck`
308
+ script, and the MIT licence text the package always claimed to carry.
309
+
310
+ ### Fixed
311
+
312
+ - `Scavold.UserConfig` in the shipped declarations was a self-referential type alias
313
+ (TS2456) and now aliases VitePress's `UserConfig`.
314
+
315
+ ### Known issues
316
+
317
+ - Flag arguments on the `video` container have no effect: bare words become the
318
+ container's `class`, while `useVideo()` reads its booleans from `data-*` props, which
319
+ only `key=value` arguments produce. Use `overlay=1 autoplay=1 loop=1` until this is
320
+ resolved.
321
+ - Container names that are neither built in nor declared in `.cratly.config.yaml` are
322
+ not parsed as containers at all, despite `ScavoldContainer` being documented as a
323
+ catch-all fallback.
324
+ - Only images are processed out of `media_folder`; other file types (video, documents)
325
+ are never copied into the build output and have to live in the static folder.
326
+
327
+ [Unreleased]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.10...main
328
+ [0.2.0-rc.10]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.9...v0.2.0-rc.10
329
+ [0.2.0-rc.9]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.8...v0.2.0-rc.9
330
+ [0.2.0-rc.8]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.7...v0.2.0-rc.8
331
+ [0.2.0-rc.7]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.6...v0.2.0-rc.7
332
+ [0.2.0-rc.6]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.5...v0.2.0-rc.6
333
+ [0.2.0-rc.5]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.4...v0.2.0-rc.5
334
+ [0.2.0-rc.4]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.3...v0.2.0-rc.4
335
+ [0.2.0-rc.3]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.2...v0.2.0-rc.3
336
+ [0.2.0-rc.2]: https://gitlab.com/cratly/scavold/-/compare/v0.2.0-rc.1...v0.2.0-rc.2
337
+ [0.2.0-rc.1]: https://gitlab.com/cratly/scavold/-/tags/v0.2.0-rc.1