site_maps 0.1.1 → 0.2.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.
Files changed (40) hide show
  1. checksums.yaml +4 -4
  2. data/.tool-versions +1 -1
  3. data/CHANGELOG.md +18 -0
  4. data/Gemfile.lock +4 -4
  5. data/README.md +23 -1
  6. data/docs/adapters.md +1 -0
  7. data/docs/brand/BRAND.md +65 -0
  8. data/docs/brand/apple-touch-icon.png +0 -0
  9. data/docs/brand/favicon-16.png +0 -0
  10. data/docs/brand/favicon-32.png +0 -0
  11. data/docs/brand/favicon.ico +0 -0
  12. data/docs/brand/favicon.svg +1 -0
  13. data/docs/brand/icon-512.png +0 -0
  14. data/docs/brand/lockup-sitemaps-mono.svg +1 -0
  15. data/docs/brand/lockup-sitemaps-on-dark.svg +1 -0
  16. data/docs/brand/lockup-sitemaps-on-light.svg +1 -0
  17. data/docs/brand/lockup-sitemaps.svg +1 -0
  18. data/docs/brand/mark-mono.svg +1 -0
  19. data/docs/brand/mark-on-dark.svg +1 -0
  20. data/docs/brand/mark-on-light.svg +1 -0
  21. data/docs/brand/mark-small-mono.svg +1 -0
  22. data/docs/brand/mark-small-on-dark.svg +1 -0
  23. data/docs/brand/mark-small-on-light.svg +1 -0
  24. data/docs/brand/mark-small.svg +1 -0
  25. data/docs/brand/mark.svg +1 -0
  26. data/docs/events.md +1 -0
  27. data/docs/processes.md +28 -0
  28. data/lib/site_maps/adapters/adapter.rb +46 -1
  29. data/lib/site_maps/adapters/aws_sdk/storage.rb +6 -6
  30. data/lib/site_maps/adapters/aws_sdk.rb +10 -0
  31. data/lib/site_maps/adapters/file_system.rb +9 -0
  32. data/lib/site_maps/adapters/noop.rb +4 -0
  33. data/lib/site_maps/configuration.rb +1 -0
  34. data/lib/site_maps/incremental_location.rb +16 -0
  35. data/lib/site_maps/middleware.rb +22 -1
  36. data/lib/site_maps/notification.rb +1 -0
  37. data/lib/site_maps/runner.rb +89 -10
  38. data/lib/site_maps/sitemap_builder.rb +6 -0
  39. data/lib/site_maps/version.rb +1 -1
  40. metadata +20 -1
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9246737e11d28f750005fedc117c0c2afb8c5f5ef606218fc505e1042c4da002
4
- data.tar.gz: 80f005cca72244cfe1044c30cf91d1656b22bb7ff3047a2fd159eee2ddd2f3a5
3
+ metadata.gz: 312638d84df202fdc438b1d93c61325a66eb2513be24846b5dce67dfe47b5737
4
+ data.tar.gz: 9e346f74fd71bb0f5f9187407adb934ae577acac5c71f4a2100ac650c5969f40
5
5
  SHA512:
6
- metadata.gz: 6ebb8a69803018a65c995e61cc714385ae965b04fc8df330cb7a5e99fd37539568cc1661e86da70f1e7e90eef108ba9a9c5ba1bc6e4b50d5c17f41856e4df934
7
- data.tar.gz: 9dbf41444903708643f4639ae773470fc2043eeb0deb9061dedccef5ceda30210435db6faec1d1f949baa8200841d7245c40ac97df88f8993b57e5c1caeaa41b
6
+ metadata.gz: 91ee260ef7da511dbaa184a34d05225e9293c6bf220d3b7bd2447fa69e84fe47bec7aa77f0ae96a9b4d4eeb50646df9d50c7137d44077490622b3152a1ed1c2d
7
+ data.tar.gz: a6d1c0ee3b32571b801ddb2f152b92f1684db588b15bc58ca6af08f0afdc66e919286b1973b352bb7012e33538f13e95e81724a4509adf4181b6ff7e7ebdd973
data/.tool-versions CHANGED
@@ -1 +1 @@
1
- ruby 3.4.9
1
+ ruby 3.0.7
data/CHANGELOG.md CHANGED
@@ -4,6 +4,24 @@ All notable changes to this project will be documented in this file.
4
4
 
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## 0.2.0 - 2026-11-11
8
+
9
+ ### Added
10
+ - `index_from_storage` configuration option. The sitemap index is rebuilt from the sitemap files found in storage (filtered by the declared process locations) instead of the in-memory state of the run, so it always lists every shard that exists.
11
+ - The index is still written when a process fails, and the other processes keep running. The first error is re-raised afterwards.
12
+ - Numbered parts a successful process no longer writes (e.g. `sitemap3.xml` after a shard shrank to two files) are deleted. Files of failed processes are never touched.
13
+ - `Runner#rebuild_index!` rewrites the index from storage without running any process.
14
+ - `Adapter#list_sitemaps` for the `:file_system`, `:aws_sdk` and `:noop` adapters.
15
+ - `sitemaps.prune_sitemap` event.
16
+
17
+ ### Fixed
18
+ - Partial runs preloaded the existing index by fetching `config.url` over HTTP, which failed whenever the stored URL is not publicly routable (e.g. behind the middleware `storage_prefix`) and silently dropped every shard not regenerated by the run. The index is now read through the adapter storage.
19
+
20
+ ## 0.1.2 - 2026-07-17
21
+
22
+ ### Added
23
+ - Middleware `aliases:` option: maps an incoming public request path to another public request path (applied before the prefix options), so the same content can be served at multiple locations without a redirect — e.g. `aliases: { "/sitemap.xml" => "/sitemap_index.xml" }`. Accepts a hash or a callable returning a hash.
24
+
7
25
  ## 0.1.1 - 2026-05-12
8
26
 
9
27
  ### Fixed
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- site_maps (0.1.1)
4
+ site_maps (0.2.0)
5
5
  builder (~> 3.0)
6
6
  concurrent-ruby (>= 1.1)
7
7
  rack (>= 2.0)
@@ -37,7 +37,7 @@ GEM
37
37
  bigdecimal (4.1.1)
38
38
  builder (3.3.0)
39
39
  coderay (1.1.3)
40
- concurrent-ruby (1.3.6)
40
+ concurrent-ruby (1.3.8)
41
41
  crack (1.0.1)
42
42
  bigdecimal
43
43
  rexml
@@ -64,7 +64,7 @@ GEM
64
64
  reline (>= 0.6.0)
65
65
  public_suffix (7.0.5)
66
66
  racc (1.8.1)
67
- rack (3.2.6)
67
+ rack (3.2.8)
68
68
  rainbow (3.1.1)
69
69
  regexp_parser (2.12.0)
70
70
  reline (0.6.3)
@@ -126,7 +126,7 @@ GEM
126
126
  addressable (>= 2.8.0)
127
127
  crack (>= 0.3.2)
128
128
  hashdiff (>= 0.4.0, < 2.0.0)
129
- zeitwerk (2.7.5)
129
+ zeitwerk (2.8.3)
130
130
 
131
131
  PLATFORMS
132
132
  x86_64-linux
data/README.md CHANGED
@@ -1,4 +1,9 @@
1
- # SiteMaps
1
+ <h1 align="center">
2
+ <picture>
3
+ <source media="(prefers-color-scheme: dark)" srcset="docs/brand/lockup-sitemaps-on-dark.svg">
4
+ <img alt="SiteMaps" src="docs/brand/lockup-sitemaps-on-light.svg" width="310">
5
+ </picture>
6
+ </h1>
2
7
 
3
8
  A concurrent, incremental sitemap generator for Ruby. Framework-agnostic with built-in Rails support.
4
9
 
@@ -109,6 +114,7 @@ SiteMaps.use(:file_system, url: "https://example.com/sitemap.xml.gz", directory:
109
114
  | `xsl_index_stylesheet_url` | `nil` | URL of the XSL stylesheet for the sitemap index. |
110
115
  | `ping_search_engines` | `false` | Ping search engines after sitemap generation. |
111
116
  | `ping_engines` | `nil` | Custom engines hash. Defaults to Bing when `nil`. |
117
+ | `index_from_storage` | `false` | Build the sitemap index from the files found in storage. The index survives failed processes and lists every stored shard. See [Index generation](docs/processes.md#index-generation). |
112
118
 
113
119
  ### Gzip Compression
114
120
 
@@ -269,6 +275,7 @@ Use these when the public URL path and the storage path differ:
269
275
  |---|---|---|
270
276
  | `public_prefix:` | Public URL has an extra prefix → strip it to find the file | Stored at `/sitemap.xml`, served at `/sitemaps/tenant/sitemap.xml` |
271
277
  | `storage_prefix:` | Storage has an extra prefix → prepend it to the public path | Stored at `/sitemaps/tenant/sitemap.xml`, served at `/sitemap.xml` |
278
+ | `aliases:` | Serve one public path's content at another public path (no redirect) | `/sitemap.xml` serves the content of `/sitemap_index.xml` |
272
279
 
273
280
  ```ruby
274
281
  # Sitemaps stored at /sitemaps/{slug}/sitemap.xml, served at /sitemap.xml
@@ -283,6 +290,20 @@ Rails.application.middleware.insert_after MultitenancyMiddleware, SiteMaps::Midd
283
290
  adapter: -> { ... }
284
291
  ```
285
292
 
293
+ `aliases:` maps an incoming public request path to another public request path,
294
+ applied before the prefix options. Use it to serve the same content at multiple
295
+ locations — for example, to answer the conventional `/sitemap.xml` with the
296
+ sitemap index that is stored/served as `/sitemap_index.xml`:
297
+
298
+ ```ruby
299
+ Rails.application.middleware.insert_after MultitenancyMiddleware, SiteMaps::Middleware,
300
+ aliases: { "/sitemap.xml" => "/sitemap_index.xml" },
301
+ storage_prefix: -> { site = Current.site; "/sitemaps/#{site.slug}" if site },
302
+ adapter: -> { ... }
303
+ ```
304
+
305
+ It accepts a hash or a callable (0-arg or 1-arg receiving `env`) returning a hash.
306
+
286
307
  XSL stylesheet requests (`/_sitemap-stylesheet.xsl`, `/_sitemap-index-stylesheet.xsl`) are served directly without resolving the adapter or prefix.
287
308
 
288
309
  ### Thread safety
@@ -536,6 +557,7 @@ use SiteMaps::Middleware,
536
557
  adapter: SiteMaps.current_adapter, # defaults to SiteMaps.current_adapter
537
558
  public_prefix: nil, # strip this prefix from the public URL before lookup
538
559
  storage_prefix: nil, # prepend this prefix to the public URL for storage lookup
560
+ aliases: nil, # map a public path to another public path, e.g. { "/sitemap.xml" => "/sitemap_index.xml" }
539
561
  x_robots_tag: "noindex, follow", # default
540
562
  cache_control: "public, max-age=3600" # default
541
563
  ```
data/docs/adapters.md CHANGED
@@ -138,6 +138,7 @@ end
138
138
  | `#write(url, raw_data, **kwargs)` | Persist `raw_data` at the location implied by `url`. |
139
139
  | `#read(url)` | Return `[raw_data, { content_type: '…' }]` for the given URL. |
140
140
  | `#delete(url)` | Remove the file at the URL. |
141
+ | `#list_sitemaps` | (optional) Return a `SiteMaps::Builder::SitemapIndex::Item` for every stored sitemap file. Required by `index_from_storage`. |
141
142
  | `.config_class` | (optional) Return a `Configuration` subclass to expose adapter-specific settings. |
142
143
 
143
144
  The adapter base class handles everything else: URL filters, the process registry, and thread-safe URL tracking.
@@ -0,0 +1,65 @@
1
+ # SiteMaps brand kit
2
+
3
+ ## The mark
4
+
5
+ A map pin drawn as one route that is also a pair of angle brackets. The `<` and `>` sides meet at the pin's point, and the gap at the top is where the tag opens. The green dot is the URL it holds. From a distance it reads as a pin; up close, as XML.
6
+
7
+ ## Colours
8
+
9
+ | Role | Light background | Dark background |
10
+ |-------|------------------|-----------------|
11
+ | Ink | `#1a2233` | `#e3e9f3` |
12
+ | Green | `#1f9d6b` | `#1f9d6b` |
13
+ | Tile (app icons) | `#1a2233` | `#1a2233` |
14
+
15
+ Green is reserved for the dot. The ink and line weight match the Lepus kit, so the libraries read as one family.
16
+
17
+ ## Clear space and minimum size
18
+
19
+ - Clear space: keep at least a quarter of the mark's height empty on every side.
20
+ - `mark*.svg`: 48 px and up.
21
+ - `mark-small*.svg` / `favicon.svg`: 16 to 48 px. Its line is heavier so it stays sharp at favicon sizes. Do not use it large.
22
+ - `lockup-sitemaps*`: 120 px wide minimum.
23
+
24
+ ## Which file where
25
+
26
+ | Use | File |
27
+ |-----|------|
28
+ | README header | `lockup-sitemaps-on-light.svg` + `lockup-sitemaps-on-dark.svg` in a `<picture>` (below) |
29
+ | Web page header that follows the OS theme | `lockup-sitemaps.svg`, `mark.svg` (ink switches with `prefers-color-scheme`) |
30
+ | Inline in an app that sets its own text colour | `*-mono.svg` (everything is `currentColor`) |
31
+ | Fixed background you control | `*-on-light.svg` / `*-on-dark.svg` |
32
+ | Browser tab | `favicon.svg` (adaptive), with `favicon.ico` (16/32/48) as fallback |
33
+ | iOS home screen | `apple-touch-icon.png` (180, solid tile) |
34
+ | Web manifest / PWA | `icon-512.png` (solid tile, mark inside the maskable safe zone) |
35
+ | Legacy PNG favicons | `favicon-16.png`, `favicon-32.png` (transparent, light-background ink) |
36
+
37
+ All wordmark text is outlined Overpass (SIL Open Font License), so no font is needed to render it.
38
+
39
+ ### README
40
+
41
+ GitHub follows the viewer's GitHub theme only through `<picture>`, so use the light/dark pair rather than the adaptive file:
42
+
43
+ ```html
44
+ <h1 align="center">
45
+ <picture>
46
+ <source media="(prefers-color-scheme: dark)" srcset="docs/brand/lockup-sitemaps-on-dark.svg">
47
+ <img alt="SiteMaps" src="docs/brand/lockup-sitemaps-on-light.svg" width="310">
48
+ </picture>
49
+ </h1>
50
+ ```
51
+
52
+ ### HTML head
53
+
54
+ ```html
55
+ <link rel="icon" href="/favicon.ico" sizes="48x48">
56
+ <link rel="icon" href="/favicon.svg" type="image/svg+xml">
57
+ <link rel="apple-touch-icon" href="/apple-touch-icon.png">
58
+ ```
59
+
60
+ ## Don'ts
61
+
62
+ - Don't redraw the wordmark in live text or another typeface.
63
+ - Don't stretch, rotate, outline or add effects to the mark.
64
+ - Don't put the ink mark on mid-tone or busy backgrounds; use the tile or the mono file instead.
65
+ - Don't recolour the line in the accent or put the dot in ink (except in the mono files).
Binary file
Binary file
Binary file
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
Binary file
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="currentColor" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="currentColor"/></g><path fill="currentColor" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="#e3e9f3" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path fill="#e3e9f3" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><g transform="translate(-11.79 -61.50) scale(1.2414)"><path stroke="#1a2233" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path fill="#1a2233" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="-6.00 -64.40 361.08 84.00" width="387" height="90" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><g transform="translate(-11.79 -61.50) scale(1.2414)"><path class="i" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></g><path class="f" d="M95.16 0.77Q90.87 0.77 87.35 -0.77Q83.84 -2.31 81.32 -5.16Q78.79 -8 77.49 -11.93L85.76 -15.03Q87.23 -11.9 89.75 -9.9Q92.27 -7.91 95.39 -7.91Q98.57 -7.91 100.5 -9.18Q102.43 -10.45 102.43 -12.9Q102.43 -14.55 101.31 -15.64Q100.19 -16.74 98.23 -17.55Q96.27 -18.37 93.75 -19.2Q91.26 -20.04 88.69 -21.04Q86.13 -22.05 83.99 -23.56Q81.86 -25.06 80.55 -27.38Q79.25 -29.69 79.25 -33.11Q79.25 -36.51 81.06 -39.34Q82.87 -42.17 86.31 -43.87Q89.75 -45.57 94.64 -45.57Q98.68 -45.57 101.81 -44.14Q104.94 -42.72 106.99 -40.28Q109.04 -37.85 109.86 -34.8L101.67 -32.1Q100.74 -34.14 99 -35.52Q97.26 -36.9 94.16 -36.9Q91.42 -36.9 89.93 -35.88Q88.44 -34.87 88.44 -32.96Q88.44 -31.72 89.45 -30.84Q90.46 -29.96 92.24 -29.25Q94.01 -28.53 96.31 -27.75Q99.06 -26.83 101.78 -25.71Q104.49 -24.58 106.72 -22.95Q108.95 -21.32 110.29 -18.88Q111.62 -16.44 111.62 -12.88Q111.62 -8.54 109.43 -5.5Q107.24 -2.45 103.51 -0.84Q99.79 0.77 95.16 0.77ZM119.58 0V-32.7H128.37V0ZM123.97 -36.2Q121.89 -36.2 120.38 -37.71Q118.87 -39.21 118.87 -41.29Q118.87 -43.36 120.36 -44.84Q121.84 -46.31 123.97 -46.31Q126.16 -46.31 127.62 -44.86Q129.08 -43.41 129.08 -41.29Q129.08 -39.16 127.59 -37.68Q126.11 -36.2 123.97 -36.2ZM147.89 0.77Q143.81 0.77 141.8 -1.43Q139.8 -3.62 139.8 -8.01V-24.97H135.2V-32.7H139.8V-42.04L148.54 -46.21V-32.7H155.73V-24.97H148.54V-9.31Q148.54 -8.07 149.1 -7.45Q149.66 -6.82 150.83 -6.82Q153.25 -6.82 156.13 -8.19L155.13 -0.7Q153.61 0.01 151.83 0.39Q150.05 0.77 147.89 0.77ZM177.38 0.77Q172.59 0.77 169.13 -1.31Q165.68 -3.39 163.83 -7.24Q161.98 -11.08 161.98 -16.38Q161.98 -21.64 163.87 -25.48Q165.75 -29.31 169.21 -31.39Q172.67 -33.47 177.38 -33.47Q181.44 -33.47 184.73 -31.8Q188.02 -30.12 189.96 -26.68Q191.9 -23.24 191.9 -17.97Q191.9 -17.1 191.87 -15.84Q191.83 -14.57 191.66 -13.31H170.59Q170.66 -11.31 171.5 -9.82Q172.35 -8.34 173.85 -7.52Q175.35 -6.71 177.4 -6.71Q179.82 -6.71 181.51 -7.49Q183.21 -8.28 184.78 -10.07L189.83 -4.94Q187.62 -2.35 184.55 -0.79Q181.47 0.77 177.38 0.77ZM170.68 -20.02H183.46Q183.25 -21.93 182.48 -23.27Q181.71 -24.6 180.38 -25.3Q179.06 -26 177.17 -26Q174.59 -26 172.8 -24.46Q171.02 -22.92 170.68 -20.02ZM220.79 0.77 209.55 -23.07Q209.43 -23.37 209.27 -23.77Q209.11 -24.18 208.96 -24.65Q208.98 -24.25 208.99 -23.79Q209 -23.32 209 -23.01V0H199.8V-44.8H209.09L219.89 -20.95Q220.23 -20.25 220.49 -19.6Q220.76 -18.94 220.99 -18.2Q221.25 -18.94 221.5 -19.63Q221.75 -20.31 222.04 -20.95L233.14 -44.8H241.73V0H232.53V-23.07Q232.53 -23.45 232.54 -23.92Q232.54 -24.39 232.55 -24.67Q232.4 -24.29 232.22 -23.87Q232.05 -23.45 231.9 -23.14ZM261.34 0.77Q255.95 0.77 252.82 -2.11Q249.69 -4.99 249.69 -9.88Q249.69 -13.08 251.34 -15.48Q252.98 -17.89 255.92 -19.22Q258.87 -20.54 262.78 -20.54Q264.85 -20.54 266.82 -20.2Q268.79 -19.85 270.16 -19.2V-21.15Q270.16 -23.57 268.67 -24.84Q267.19 -26.1 264.38 -26.1Q261.97 -26.1 259.43 -25.24Q256.9 -24.37 254.18 -22.63L251.51 -29.45Q254.43 -31.39 257.99 -32.43Q261.56 -33.47 265.24 -33.47Q272.06 -33.47 275.45 -30.31Q278.84 -27.14 278.84 -20.83V0H270.16V-2.5Q268.6 -0.9 266.34 -0.07Q264.08 0.77 261.34 0.77ZM263.5 -6.6Q265.39 -6.6 267.1 -7.28Q268.81 -7.95 270.16 -9.2V-12.48Q268.88 -13.04 267.34 -13.34Q265.8 -13.63 264.22 -13.63Q261.43 -13.63 259.85 -12.66Q258.27 -11.68 258.27 -9.92Q258.27 -8.36 259.64 -7.48Q261.01 -6.6 263.5 -6.6ZM287.93 12.99V-32.7H296.72V-30.41Q299.14 -33.47 303.52 -33.47Q307.73 -33.47 310.77 -31.4Q313.81 -29.34 315.44 -25.5Q317.08 -21.66 317.08 -16.33Q317.08 -11.21 315.36 -7.37Q313.65 -3.53 310.57 -1.38Q307.5 0.77 303.42 0.77Q301.26 0.77 299.58 0Q297.9 -0.76 296.72 -2.25V9.04ZM302.05 -7.69Q305.02 -7.69 306.64 -9.96Q308.26 -12.24 308.26 -16.33Q308.26 -20.51 306.64 -22.77Q305.03 -25.02 302.05 -25.02Q300.3 -25.02 299.02 -24.46Q297.74 -23.9 296.72 -22.71V-10.11Q297.29 -9.03 298.77 -8.36Q300.24 -7.69 302.05 -7.69ZM336.21 0.77Q332.26 0.77 328.66 -0.91Q325.05 -2.59 322.98 -5.4L328.64 -10.18Q330.27 -8.5 332.4 -7.49Q334.54 -6.48 336.49 -6.48Q338.75 -6.48 339.97 -7.2Q341.18 -7.92 341.18 -9.25Q341.18 -10.07 340.61 -10.74Q340.03 -11.4 338.48 -12.14Q336.94 -12.88 334.06 -13.93Q328.49 -15.94 326.22 -18.28Q323.94 -20.63 323.94 -24.27Q323.94 -28.39 327.28 -30.93Q330.62 -33.47 335.98 -33.47Q339.62 -33.47 342.71 -32.09Q345.81 -30.71 348.06 -28.08L342.38 -23.4Q339.51 -26.23 335.81 -26.23Q334.05 -26.23 332.95 -25.58Q331.85 -24.94 331.85 -23.91Q331.85 -22.87 333.17 -22.03Q334.48 -21.2 338.14 -20.06Q341.97 -18.91 344.39 -17.4Q346.81 -15.9 347.95 -13.9Q349.08 -11.9 349.08 -9.28Q349.08 -4.58 345.65 -1.9Q342.21 0.77 336.21 0.77Z"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="currentColor" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="currentColor"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#e3e9f3" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#1a2233" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="currentColor" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="currentColor"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#e3e9f3" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><path stroke="#1a2233" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M23 9 L11 28 L32 58 L53 28 L41 9" fill="none" stroke-width="8" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="7" fill="#1f9d6b"/></svg>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 1 64 64" role="img" aria-label="SiteMaps"><title>SiteMaps</title><style>.i{stroke:#1a2233}.f{fill:#1a2233}@media (prefers-color-scheme:dark){.i{stroke:#e3e9f3}.f{fill:#e3e9f3}}</style><path class="i" d="M24 10 L12 28 L32 57 L52 28 L40 10" fill="none" stroke-width="7" stroke-linecap="round" stroke-linejoin="round"/><circle cx="32" cy="28" r="6" fill="#1f9d6b"/></svg>
data/docs/events.md CHANGED
@@ -57,6 +57,7 @@ This is subscribed automatically by the CLI.
57
57
  | `sitemaps.process_execution` | `process`, `kwargs`, `runtime` |
58
58
  | `sitemaps.finalize_urlset` | `url`, `links_count`, `news_count`, `last_modified`, `runtime`, `process` |
59
59
  | `sitemaps.ping` | `results` |
60
+ | `sitemaps.prune_sitemap` | `url`, `runtime` |
60
61
 
61
62
  `process` is a `SiteMaps::Process` struct (`name`, `location_template`, `kwargs_template`, `block`).
62
63
 
data/docs/processes.md CHANGED
@@ -63,6 +63,8 @@ When you call `runner.run`:
63
63
  3. Each process builds a `URLSet`. When the set fills up (50,000 links, 1,000 news items, or 50 MB uncompressed), it's finalized and written, and a new URLSet starts — automatically.
64
64
  4. After every process finishes, the sitemap index is aggregated and written to `config.url`.
65
65
 
66
+ By default the first failing process stops the run and the index is left untouched. With `index_from_storage` the remaining processes still run, the index is written, and the first error is re-raised afterwards.
67
+
66
68
  ## Splitting rules
67
69
 
68
70
  A URL set is finalized and rolled over when **any** of these apply:
@@ -83,6 +85,32 @@ A sitemap index is produced when:
83
85
 
84
86
  Otherwise a single `urlset` is written directly at `config.url` (the "inline" optimization).
85
87
 
88
+ On a partial run the existing index is read back through the adapter, and the entries of shards that were not regenerated are kept.
89
+
90
+ ### Building the index from storage
91
+
92
+ Set `index_from_storage` when the index must always reflect what is stored, no matter which processes ran or failed:
93
+
94
+ ```ruby
95
+ SiteMaps.use(:aws_sdk) do
96
+ configure do |config|
97
+ config.url = "https://example.com/sitemap.xml"
98
+ config.index_from_storage = true
99
+ end
100
+ # ...
101
+ end
102
+ ```
103
+
104
+ The index then lists every stored file that matches a declared process location, plus the external sitemaps. A successful process deletes the numbered parts it no longer writes. Files of failed processes are left as they were.
105
+
106
+ To rebuild a lost or stale index without querying any data source:
107
+
108
+ ```ruby
109
+ SiteMaps.generate(config_file: "config/sitemap.rb").rebuild_index!
110
+ ```
111
+
112
+ Adapters need `#list_sitemaps` for this option (built in for `:file_system`, `:aws_sdk` and `:noop`).
113
+
86
114
  ## Adding external sitemaps
87
115
 
88
116
  Reference third-party or pre-existing sitemaps in the index:
@@ -12,7 +12,11 @@ module SiteMaps::Adapters
12
12
  end
13
13
  end
14
14
 
15
- def_delegators :config, :fetch_sitemap_index_links
15
+ SITEMAP_ENTRY_RE = %r{<sitemap>(.*?)</sitemap>}m
16
+ LOC_RE = %r{<loc>\s*(.*?)\s*</loc>}m
17
+ LASTMOD_RE = %r{<lastmod>\s*(.*?)\s*</lastmod>}m
18
+ XML_ENTITIES = {"&amp;" => "&", "&lt;" => "<", "&gt;" => ">", "&quot;" => '"', "&apos;" => "'"}.freeze
19
+
16
20
  attr_reader :sitemap_index, :processes, :process_mixins
17
21
 
18
22
  def initialize(**options, &block)
@@ -48,6 +52,28 @@ module SiteMaps::Adapters
48
52
  raise NotImplementedError
49
53
  end
50
54
 
55
+ # @abstract
56
+ # @return [Array<SiteMaps::Builder::SitemapIndex::Item>] every sitemap file stored under the sitemap directory
57
+ # @raise [NotImplementedError] if the adapter can not list its storage
58
+ def list_sitemaps
59
+ raise NotImplementedError, "#{self.class} does not support listing stored sitemaps"
60
+ end
61
+
62
+ # Reads the current sitemap index straight from the adapter storage.
63
+ # Adapters without a #read implementation fall back to fetching the index URL.
64
+ #
65
+ # @return [Array<SiteMaps::Builder::SitemapIndex::Item>]
66
+ def fetch_sitemap_index_links
67
+ raw_data, metadata = read(config.url)
68
+ return [] if raw_data.nil?
69
+
70
+ parse_sitemap_index(decompress(raw_data, metadata))
71
+ rescue SiteMaps::FileNotFoundError
72
+ []
73
+ rescue NotImplementedError
74
+ config.fetch_sitemap_index_links
75
+ end
76
+
51
77
  def config
52
78
  yield(@config) if block_given?
53
79
  @config
@@ -106,5 +132,24 @@ module SiteMaps::Adapters
106
132
  @sitemap_index = SiteMaps::Builder::SitemapIndex.new(xsl_url: xsl_url)
107
133
  @repo = nil
108
134
  end
135
+
136
+ private
137
+
138
+ def parse_sitemap_index(xml)
139
+ xml.scan(SITEMAP_ENTRY_RE).filter_map do |(entry)|
140
+ loc = entry[LOC_RE, 1]
141
+ next if loc.nil? || loc.empty?
142
+
143
+ SiteMaps::Builder::SitemapIndex::Item.new(loc.gsub(/&(?:amp|lt|gt|quot|apos);/, XML_ENTITIES), entry[LASTMOD_RE, 1])
144
+ end
145
+ end
146
+
147
+ def decompress(raw_data, metadata)
148
+ return raw_data unless metadata.is_a?(Hash) && metadata[:content_type] == "application/gzip"
149
+
150
+ Zlib::GzipReader.new(StringIO.new(raw_data)).read
151
+ rescue Zlib::GzipFile::Error
152
+ raw_data
153
+ end
109
154
  end
110
155
  end
@@ -37,14 +37,14 @@ class SiteMaps::Adapters::AwsSdk::Storage
37
37
  raise SiteMaps::FileNotFoundError, "File not found: #{location.remote_path}"
38
38
  end
39
39
 
40
- private
41
-
42
- def list_objects(prefix:)
43
- config.s3_bucket.objects(
44
- prefix: prefix
45
- )
40
+ # @param [String] prefix
41
+ # @return [Array<Array(String, Time)>] the key and last modified time of every object under the prefix
42
+ def list(prefix)
43
+ config.s3_bucket.objects(prefix: prefix).map { |object| [object.key, object.last_modified] }
46
44
  end
47
45
 
46
+ private
47
+
48
48
  def object(remote_path)
49
49
  config.s3_bucket.object(remote_path)
50
50
  end
@@ -18,6 +18,16 @@ module SiteMaps::Adapters
18
18
  s3_storage.delete(location)
19
19
  end
20
20
 
21
+ def list_sitemaps
22
+ prefix = config.remote_sitemap_directory
23
+ prefix = "#{prefix}/" unless prefix.empty?
24
+ s3_storage.list(prefix).filter_map do |key, last_modified|
25
+ next unless key.end_with?(".xml", ".xml.gz")
26
+
27
+ SiteMaps::Builder::SitemapIndex::Item.new("#{config.base_uri}/#{key}", last_modified)
28
+ end
29
+ end
30
+
21
31
  private
22
32
 
23
33
  def local_storage
@@ -17,6 +17,15 @@ module SiteMaps::Adapters
17
17
  storage.delete(location)
18
18
  end
19
19
 
20
+ def list_sitemaps
21
+ root = Pathname.new(config.directory)
22
+ pattern = root.join(config.remote_sitemap_directory, "**", "*.{xml,xml.gz}").to_s
23
+ Dir.glob(pattern).map do |path|
24
+ relative = Pathname.new(path).relative_path_from(root).to_s
25
+ SiteMaps::Builder::SitemapIndex::Item.new("#{config.base_uri}/#{relative}", File.mtime(path))
26
+ end
27
+ end
28
+
20
29
  private
21
30
 
22
31
  def storage
@@ -14,5 +14,9 @@ module SiteMaps::Adapters
14
14
  def fetch_sitemap_index_links
15
15
  []
16
16
  end
17
+
18
+ def list_sitemaps
19
+ []
20
+ end
17
21
  end
18
22
  end
@@ -44,6 +44,7 @@ module SiteMaps
44
44
  attribute :xsl_index_stylesheet_url
45
45
  attribute :ping_search_engines, default: false
46
46
  attribute :ping_engines
47
+ attribute :index_from_storage, default: false
47
48
 
48
49
  def initialize(**options)
49
50
  default_attributes.merge(options).each do |key, value|
@@ -4,6 +4,14 @@ module SiteMaps
4
4
  class IncrementalLocation
5
5
  FILENAME = "sitemap.xml"
6
6
  PLACEHOLDER = "%{index}"
7
+ ANY_SEGMENT = "__site_maps_any_segment__"
8
+ TEMPLATE_KEY_RE = /%\{\w+\}/
9
+
10
+ # Matches every file a process location template may produce, whatever its
11
+ # keyword arguments and part number.
12
+ def self.template_pattern(main_url, location_template)
13
+ new(main_url, location_template&.gsub(TEMPLATE_KEY_RE, ANY_SEGMENT)).pattern
14
+ end
7
15
 
8
16
  def initialize(main_url, process_location)
9
17
  @main_uri = URI(main_url)
@@ -24,6 +32,14 @@ module SiteMaps
24
32
  main_uri.to_s
25
33
  end
26
34
 
35
+ # @return [Regexp] matches every numbered part of this location
36
+ def pattern
37
+ source = Regexp.escape(placeholder_url)
38
+ .sub(Regexp.escape(PLACEHOLDER)) { "\\d+" }
39
+ .gsub(ANY_SEGMENT) { "[^/]+" }
40
+ Regexp.new("\\A#{source}\\z")
41
+ end
42
+
27
43
  def relative_directory
28
44
  File.dirname(@uri.path).sub(%r{^/}, "")
29
45
  end
@@ -28,11 +28,21 @@ module SiteMaps
28
28
  # Both options accept a callable (0-arg or 1-arg receiving env), which is useful
29
29
  # in multi-tenant setups where the prefix depends on the current request/site.
30
30
  #
31
+ # @param aliases [Hash{String=>String}, #call, nil] Maps an incoming **public
32
+ # request path** to another public request path, applied before prefix
33
+ # handling. Serves the target's content at the alias path (no redirect).
34
+ #
35
+ # Example: serve the sitemap index at both locations →
36
+ # `aliases: { "/sitemap.xml" => "/sitemap_index.xml" }`
37
+ #
38
+ # Accepts a callable (0-arg or 1-arg receiving env) returning such a hash.
39
+ #
31
40
  def initialize(
32
41
  app,
33
42
  adapter: nil,
34
43
  public_prefix: nil,
35
44
  storage_prefix: nil,
45
+ aliases: nil,
36
46
  x_robots_tag: DEFAULT_X_ROBOTS_TAG,
37
47
  cache_control: DEFAULT_CACHE_CONTROL
38
48
  )
@@ -40,12 +50,13 @@ module SiteMaps
40
50
  @adapter = adapter
41
51
  @public_prefix = public_prefix
42
52
  @storage_prefix = storage_prefix
53
+ @aliases = aliases
43
54
  @x_robots_tag = x_robots_tag
44
55
  @cache_control = cache_control
45
56
  end
46
57
 
47
58
  def call(env)
48
- path = env["PATH_INFO"]
59
+ path = resolve_alias(env["PATH_INFO"], env)
49
60
 
50
61
  if xsl_request?(path)
51
62
  serve_xsl(path)
@@ -74,6 +85,16 @@ module SiteMaps
74
85
 
75
86
  private
76
87
 
88
+ # Rewrites the incoming public path through the alias map (if configured)
89
+ # before any prefix handling, so the aliased path takes the exact same code
90
+ # path as a real request for the target.
91
+ def resolve_alias(path, env)
92
+ aliases = @aliases.respond_to?(:call) ? call_with_env(@aliases, env) : @aliases
93
+ return path unless aliases
94
+
95
+ aliases[path] || path
96
+ end
97
+
77
98
  def resolve_adapter(env)
78
99
  if @adapter.respond_to?(:call)
79
100
  call_with_env(@adapter, env)
@@ -33,5 +33,6 @@ module SiteMaps
33
33
  register_event "sitemaps.enqueue_process"
34
34
  register_event "sitemaps.process_execution"
35
35
  register_event "sitemaps.ping"
36
+ register_event "sitemaps.prune_sitemap"
36
37
  end
37
38
  end
@@ -11,6 +11,7 @@ module SiteMaps
11
11
  @execution = Concurrent::Hash.new
12
12
  @failed = Concurrent::AtomicBoolean.new(false)
13
13
  @errors = Concurrent::Array.new
14
+ @written = Concurrent::Array.new
14
15
  end
15
16
 
16
17
  def enqueue(process_name = :default, **kwargs)
@@ -68,6 +69,7 @@ module SiteMaps
68
69
  adapter.process_mixins.each { |mixin| builder.extend(mixin) }
69
70
  process.call(builder, **kwargs)
70
71
  builder.finalize!
72
+ written << [process.location(**kwargs), builder.written_urls]
71
73
  end
72
74
  end
73
75
  end
@@ -75,30 +77,104 @@ module SiteMaps
75
77
  end
76
78
 
77
79
  futures.each(&:wait)
78
- failed.false? ? finalize! : fail_with_errors!
80
+ if failed.false?
81
+ finalize!
82
+ else
83
+ write_index! if index_from_storage?
84
+ fail_with_errors!
85
+ end
79
86
  ensure
80
87
  pool.shutdown
81
88
  pool.wait_for_termination
82
89
  @execution.clear
90
+ @written.clear
91
+ end
92
+
93
+ # Rewrites the sitemap index from the sitemap files found in storage,
94
+ # without running any process.
95
+ def rebuild_index!
96
+ add_stored_index_links(adapter.list_sitemaps)
97
+ add_external_index_links
98
+ write_sitemap_index
99
+ self
83
100
  end
84
101
 
85
102
  private
86
103
 
87
- attr_reader :pool, :failed, :errors
104
+ attr_reader :pool, :failed, :errors, :written
88
105
 
89
106
  def finalize!
90
- adapter.repo.remaining_index_links.each do |item|
91
- adapter.sitemap_index.add(item)
107
+ write_index!
108
+ should_ping = @ping.nil? ? adapter.config.respond_to?(:ping_search_engines?) && adapter.config.ping_search_engines? : @ping
109
+ ping_search_engines if should_ping
110
+ end
111
+
112
+ def write_index!
113
+ if index_from_storage?
114
+ add_stored_index_links(prune_stale_sitemaps(adapter.list_sitemaps))
115
+ else
116
+ adapter.repo.remaining_index_links.each do |item|
117
+ adapter.sitemap_index.add(item)
118
+ end
92
119
  end
120
+ add_external_index_links
121
+ write_sitemap_index
122
+ end
123
+
124
+ def write_sitemap_index
125
+ return if adapter.sitemap_index.empty?
126
+
127
+ raw_data = adapter.sitemap_index.to_xml
128
+ adapter.write(adapter.config.url, raw_data, last_modified: adapter.sitemap_index.last_modified)
129
+ end
130
+
131
+ def add_external_index_links
93
132
  adapter.external_sitemaps.each do |item|
94
133
  adapter.sitemap_index.add(item)
95
134
  end
96
- unless adapter.sitemap_index.empty?
97
- raw_data = adapter.sitemap_index.to_xml
98
- adapter.write(adapter.config.url, raw_data, last_modified: adapter.sitemap_index.last_modified)
135
+ end
136
+
137
+ # Adds the stored files that belong to a declared process. Files written by
138
+ # this run are already in the index, and the index keeps the first entry
139
+ # added for each location.
140
+ def add_stored_index_links(stored)
141
+ patterns = adapter.processes.values.map do |process|
142
+ SiteMaps::IncrementalLocation.template_pattern(adapter.config.url, process.location_template)
99
143
  end
100
- should_ping = @ping.nil? ? adapter.config.respond_to?(:ping_search_engines?) && adapter.config.ping_search_engines? : @ping
101
- ping_search_engines if should_ping
144
+
145
+ stored.each do |item|
146
+ next if item.loc == adapter.config.url
147
+ next unless patterns.any? { |pattern| pattern.match?(item.loc) }
148
+
149
+ adapter.sitemap_index.add(item)
150
+ end
151
+ end
152
+
153
+ # Deletes the numbered parts a successful process no longer writes, such as
154
+ # sitemap3.xml after a month shrank to two files. Returns the stored files
155
+ # that are left.
156
+ def prune_stale_sitemaps(stored)
157
+ stale = written.flat_map do |location, urls|
158
+ next [] if urls.empty?
159
+
160
+ pattern = SiteMaps::IncrementalLocation.new(adapter.config.url, location).pattern
161
+ stored.select { |item| pattern.match?(item.loc) && !urls.include?(item.loc) && item.loc != adapter.config.url }
162
+ end
163
+
164
+ stale.each do |item|
165
+ SiteMaps::Notification.instrument("sitemaps.prune_sitemap") do |payload|
166
+ payload[:url] = item.loc
167
+ adapter.delete(item.loc)
168
+ rescue SiteMaps::FileNotFoundError
169
+ nil
170
+ end
171
+ end
172
+
173
+ stored - stale
174
+ end
175
+
176
+ def index_from_storage?
177
+ adapter.config.respond_to?(:index_from_storage?) && adapter.config.index_from_storage?
102
178
  end
103
179
 
104
180
  def ping_search_engines
@@ -119,6 +195,7 @@ module SiteMaps
119
195
  end
120
196
 
121
197
  def may_preload_sitemap_index
198
+ return if index_from_storage?
122
199
  return unless preload_sitemap_index_links?
123
200
 
124
201
  adapter.fetch_sitemap_index_links.each do |item|
@@ -126,8 +203,10 @@ module SiteMaps
126
203
  end
127
204
  end
128
205
 
206
+ # A failure stops the remaining processes, unless the index is rebuilt from
207
+ # storage: then every independent process still gets its chance to publish.
129
208
  def wrap_process_execution(process)
130
- return if failed.true?
209
+ return if failed.true? && !index_from_storage?
131
210
 
132
211
  yield
133
212
  rescue => e
@@ -4,8 +4,11 @@ module SiteMaps
4
4
  class SitemapBuilder
5
5
  extend Forwardable
6
6
 
7
+ attr_reader :written_urls
8
+
7
9
  def initialize(adapter:, location: nil, notification_payload: {})
8
10
  @adapter = adapter
11
+ @written_urls = []
9
12
  @url_set = build_url_set
10
13
  @location = location
11
14
  @mutex = Mutex.new
@@ -43,10 +46,12 @@ module SiteMaps
43
46
  if adapter.maybe_inline_urlset? && sitemap_index.empty?
44
47
  payload[:url] = repo.main_url
45
48
  adapter.write(repo.main_url, raw_data, last_modified: url_set.last_modified)
49
+ written_urls << repo.main_url
46
50
  else
47
51
  sitemap_url = repo.generate_url(location)
48
52
  payload[:url] = sitemap_url
49
53
  adapter.write(sitemap_url, raw_data, last_modified: url_set.last_modified)
54
+ written_urls << sitemap_url
50
55
  add_sitemap_index(sitemap_url, lastmod: url_set.last_modified)
51
56
  end
52
57
  end
@@ -67,6 +72,7 @@ module SiteMaps
67
72
  payload[:news_count] = url_set.news_count
68
73
  payload[:last_modified] = url_set.last_modified
69
74
  adapter.write(sitemap_url, raw_data, last_modified: url_set.last_modified)
75
+ written_urls << sitemap_url
70
76
  add_sitemap_index(sitemap_url, lastmod: url_set.last_modified)
71
77
  end
72
78
  @url_set = build_url_set
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SiteMaps
4
- VERSION = "0.1.1"
4
+ VERSION = "0.2.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: site_maps
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.1
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Marcos G. Zimmermann
@@ -108,6 +108,25 @@ files:
108
108
  - docs/README.md
109
109
  - docs/adapters.md
110
110
  - docs/api.md
111
+ - docs/brand/BRAND.md
112
+ - docs/brand/apple-touch-icon.png
113
+ - docs/brand/favicon-16.png
114
+ - docs/brand/favicon-32.png
115
+ - docs/brand/favicon.ico
116
+ - docs/brand/favicon.svg
117
+ - docs/brand/icon-512.png
118
+ - docs/brand/lockup-sitemaps-mono.svg
119
+ - docs/brand/lockup-sitemaps-on-dark.svg
120
+ - docs/brand/lockup-sitemaps-on-light.svg
121
+ - docs/brand/lockup-sitemaps.svg
122
+ - docs/brand/mark-mono.svg
123
+ - docs/brand/mark-on-dark.svg
124
+ - docs/brand/mark-on-light.svg
125
+ - docs/brand/mark-small-mono.svg
126
+ - docs/brand/mark-small-on-dark.svg
127
+ - docs/brand/mark-small-on-light.svg
128
+ - docs/brand/mark-small.svg
129
+ - docs/brand/mark.svg
111
130
  - docs/cli.md
112
131
  - docs/events.md
113
132
  - docs/extensions.md