indexmap 0.8.2 → 0.9.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/README.md +287 -35
- data/UPGRADE.md +361 -0
- data/docs/architecture.md +55 -0
- data/docs/deployment.md +223 -0
- data/docs/domain-terms.md +27 -0
- data/docs/dynamic-sitemaps.md +302 -0
- data/docs/search-engine-ping.md +368 -0
- data/docs/storage.md +94 -0
- data/lib/generators/indexmap/install_generator.rb +1 -1
- data/lib/generators/indexmap/templates/initializer.rb.tt +18 -18
- data/lib/indexmap/artifact.rb +9 -0
- data/lib/indexmap/configuration.rb +12 -1
- data/lib/indexmap/creator.rb +45 -42
- data/lib/indexmap/index_now_configuration.rb +3 -1
- data/lib/indexmap/output.rb +19 -5
- data/lib/indexmap/parser.rb +112 -52
- data/lib/indexmap/pinger/base.rb +6 -5
- data/lib/indexmap/pinger/google.rb +12 -8
- data/lib/indexmap/pinger/index_now.rb +97 -52
- data/lib/indexmap/publication.rb +89 -0
- data/lib/indexmap/robots.rb +67 -0
- data/lib/indexmap/robots_controller.rb +13 -0
- data/lib/indexmap/sitemaps_controller.rb +57 -0
- data/lib/indexmap/storage/active_storage.rb +132 -1
- data/lib/indexmap/storage/filesystem.rb +182 -11
- data/lib/indexmap/storage/memory.rb +1 -1
- data/lib/indexmap/task_runner.rb +21 -28
- data/lib/indexmap/validator/duplicates.rb +83 -0
- data/lib/indexmap/validator.rb +171 -59
- data/lib/indexmap/version.rb +1 -1
- data/lib/indexmap/writer.rb +89 -34
- data/lib/indexmap.rb +14 -1
- data/lib/tasks/indexmap_tasks.rake +21 -2
- data/test/indexmap/correctness_test.rb +153 -0
- data/test/indexmap/install_generator_test.rb +36 -4
- data/test/indexmap/parser_test.rb +77 -0
- data/test/indexmap/pinger/google_test.rb +22 -0
- data/test/indexmap/pinger/index_now_test.rb +168 -0
- data/test/indexmap/publication_test.rb +280 -0
- data/test/indexmap/robots_test.rb +73 -0
- data/test/indexmap/sitemaps_controller_test.rb +50 -0
- data/test/indexmap/storage_test.rb +27 -0
- data/test/indexmap/task_runner_test.rb +24 -0
- data/test/indexmap/validator_test.rb +14 -0
- data/test/indexmap/writer_test.rb +42 -0
- data/test/integration/queue_checks.rb +244 -0
- data/test/release_task_test.rb +153 -0
- metadata +22 -5
- data/lib/indexmap/storage/file.rb +0 -11
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 1b4cefe268685e0e7558e8265a10c195369ae24eb117a31f9f880104db68a593
|
|
4
|
+
data.tar.gz: c10e968aff2cd046d825723656b29c1f05bde39ce956c55a34e2df49d6293b70
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81179a3e30e5c4717e431aa6b4a27c7844bd4c3d57a9676d28e26603b9872a7972a889fc1474c2a4c627f4869b838c8d102b1de3069a061e321e7b6c90489c01
|
|
7
|
+
data.tar.gz: 0720dc59bb0adeee8529266aff3c01f69f766a6602eec4253e51918897e9c2123784a2ff8a357f87d1f4e5ade869f2da7f2c36cd6037857aa41e945d7879d2db
|
data/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,71 @@ All notable changes to this project will be documented in this file.
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [0.9.0] - 2026-11-04
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- validate and expose public crawler output
|
|
14
|
+
|
|
15
|
+
- validate and expose public crawler output ([#21](https://github.com/ethos-link/indexmap/pull/21))
|
|
16
|
+
|
|
17
|
+
- stream and atomically publish dynamic inventories
|
|
18
|
+
|
|
19
|
+
- stream and atomically publish dynamic inventories ([#23](https://github.com/ethos-link/indexmap/pull/23))
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- reconcile writer branch with merged sitemap fixes
|
|
27
|
+
|
|
28
|
+
- synchronize publication branch with resolved writer base
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
### Documentation
|
|
34
|
+
|
|
35
|
+
- explain background sitemap generation ([#19](https://github.com/ethos-link/indexmap/pull/19))
|
|
36
|
+
|
|
37
|
+
- explain public key setup in Rails ([#20](https://github.com/ethos-link/indexmap/pull/20))
|
|
38
|
+
|
|
39
|
+
- clarify usage limits and implementation status
|
|
40
|
+
|
|
41
|
+
- clarify retries are disabled by default
|
|
42
|
+
|
|
43
|
+
- document unreleased APIs and sitemap deployment choices
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
### Fixed
|
|
49
|
+
|
|
50
|
+
- consume sitemap section entries once
|
|
51
|
+
|
|
52
|
+
- consume sitemap section entries once ([#22](https://github.com/ethos-link/indexmap/pull/22))
|
|
53
|
+
|
|
54
|
+
- separate review preparation from release tagging
|
|
55
|
+
|
|
56
|
+
- prepare and push releases from main
|
|
57
|
+
|
|
58
|
+
- install git-cliff before running release tests
|
|
59
|
+
|
|
60
|
+
- prepare and push releases from main ([#25](https://github.com/ethos-link/indexmap/pull/25))
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
### fix
|
|
66
|
+
|
|
67
|
+
- harden publication and Rails task loading
|
|
68
|
+
|
|
69
|
+
- harden sitemap publication and Rails integration ([#24](https://github.com/ethos-link/indexmap/pull/24))
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
|
|
8
73
|
## [0.8.2] - 2026-07-22
|
|
9
74
|
|
|
10
75
|
|
data/README.md
CHANGED
|
@@ -14,6 +14,16 @@ It is designed for Rails apps that want:
|
|
|
14
14
|
|
|
15
15
|
By default, `indexmap` writes a sitemap index plus one or more child sitemap files. For simpler sites, it also supports `:single_file` mode, which writes a single `urlset` directly to `sitemap.xml`.
|
|
16
16
|
|
|
17
|
+
## Release status
|
|
18
|
+
|
|
19
|
+
This branch documents **unreleased main**, including partitioned generation,
|
|
20
|
+
atomic publication, sitemap and robots controllers, and IndexNow reports/retries.
|
|
21
|
+
The latest version recorded in this repository is **0.8.2**. For that release,
|
|
22
|
+
use the [0.8.2 README](https://github.com/ethos-link/indexmap/blob/v0.8.2/README.md)
|
|
23
|
+
and [changelog](CHANGELOG.md). Installing from RubyGems does not provide the
|
|
24
|
+
unreleased features below. Use a source checkout for evaluation until the next
|
|
25
|
+
release, and read [UPGRADE.md](UPGRADE.md) before adopting it.
|
|
26
|
+
|
|
17
27
|
## Installation
|
|
18
28
|
|
|
19
29
|
Add this line to your application's Gemfile:
|
|
@@ -44,6 +54,14 @@ directory prefix such as `sitemaps/`.
|
|
|
44
54
|
|
|
45
55
|
## Documentation
|
|
46
56
|
|
|
57
|
+
- [How Indexmap works](docs/architecture.md): pipeline diagram and entrypoints.
|
|
58
|
+
- [Domain terms](docs/domain-terms.md): sections, partitions, outputs, publications,
|
|
59
|
+
roots, and entry types.
|
|
60
|
+
- [Deployment guide](docs/deployment.md): development and production, static and
|
|
61
|
+
database-backed sources, static files or Rails delivery, and robots.txt.
|
|
62
|
+
- [Storage contract](docs/storage.md): legacy, streaming, atomic, and pruning APIs.
|
|
63
|
+
- [Dynamic sitemaps](docs/dynamic-sitemaps.md): streaming and partitions, atomic
|
|
64
|
+
publication, Rails delivery, retention, jobs, and host adoption.
|
|
47
65
|
- [Search engine ping](docs/search-engine-ping.md): Google Search Console
|
|
48
66
|
credentials, Google property configuration, and IndexNow key setup.
|
|
49
67
|
- [UPGRADE.md](UPGRADE.md): host-app migration steps for public contract
|
|
@@ -64,10 +82,15 @@ sections = [
|
|
|
64
82
|
)
|
|
65
83
|
]
|
|
66
84
|
|
|
67
|
-
Indexmap
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
85
|
+
Indexmap.configure do |config|
|
|
86
|
+
config.base_url = "https://example.com"
|
|
87
|
+
config.storage = Indexmap::Storage::Filesystem.new(
|
|
88
|
+
path: "public", public_url: config.base_url
|
|
89
|
+
)
|
|
90
|
+
config.sections = sections
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
Indexmap.create
|
|
71
94
|
```
|
|
72
95
|
|
|
73
96
|
## Rails Usage
|
|
@@ -79,7 +102,7 @@ bin/rails generate indexmap:install
|
|
|
79
102
|
```
|
|
80
103
|
|
|
81
104
|
The generator creates `config/initializers/indexmap.rb` with an idempotent
|
|
82
|
-
`
|
|
105
|
+
`Indexmap::HostConfiguration.apply` loader and adds conditional `indexmap/tasks`
|
|
83
106
|
loading to the application's `Rakefile`. This preserves `bin/rails --tasks` and
|
|
84
107
|
the `indexmap:*` tasks without loading Indexmap during normal Rails boot.
|
|
85
108
|
|
|
@@ -88,12 +111,10 @@ Customize the `Indexmap.configure` block inside the generated initializer:
|
|
|
88
111
|
```ruby
|
|
89
112
|
Indexmap.configure do |config|
|
|
90
113
|
config.base_url = -> { "https://example.com" }
|
|
91
|
-
config.storage =
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
)
|
|
96
|
-
end
|
|
114
|
+
config.storage = Indexmap::Storage::Filesystem.new(
|
|
115
|
+
path: Rails.public_path,
|
|
116
|
+
public_url: config.base_url
|
|
117
|
+
)
|
|
97
118
|
config.sections = -> do
|
|
98
119
|
[
|
|
99
120
|
Indexmap::Section.new(
|
|
@@ -111,7 +132,7 @@ Runtime callers and jobs must apply the generated configuration before using
|
|
|
111
132
|
Indexmap:
|
|
112
133
|
|
|
113
134
|
```ruby
|
|
114
|
-
|
|
135
|
+
Indexmap::HostConfiguration.apply
|
|
115
136
|
Indexmap.create
|
|
116
137
|
```
|
|
117
138
|
|
|
@@ -122,14 +143,188 @@ Then run:
|
|
|
122
143
|
|
|
123
144
|
```bash
|
|
124
145
|
bin/rails indexmap:sitemap:create
|
|
125
|
-
bin/rails indexmap:sitemap:format
|
|
126
146
|
bin/rails indexmap:sitemap:validate
|
|
127
147
|
```
|
|
128
148
|
|
|
129
|
-
`indexmap:sitemap:
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
149
|
+
Creation already validates. `indexmap:sitemap:format` optionally pretty-prints
|
|
150
|
+
legacy XML after publication; it rejects atomic outputs.
|
|
151
|
+
|
|
152
|
+
`indexmap:sitemap:create` is the main task. It stages partitioned XML on temporary
|
|
153
|
+
disk, validates the complete result, then publishes to configured storage.
|
|
154
|
+
Generation and validation failures leave published files untouched. Legacy
|
|
155
|
+
publication remains sequential; enable atomic mode for immutable children and
|
|
156
|
+
revision-checked root activation. See [dynamic sitemaps](docs/dynamic-sitemaps.md).
|
|
157
|
+
|
|
158
|
+
### Background Generation
|
|
159
|
+
|
|
160
|
+
Keep the refresh job and its scheduling in the host application. Indexmap does
|
|
161
|
+
not select a queue adapter, run workers, or deduplicate jobs. Apply the generated
|
|
162
|
+
configuration inside the job. Use `Indexmap::TaskRunner.new.create` for the
|
|
163
|
+
rake task's complete lifecycle, including `after_create` callbacks and IndexNow
|
|
164
|
+
key-file handling; `Indexmap.create` only generates the selected output.
|
|
165
|
+
|
|
166
|
+
#### GoodJob
|
|
167
|
+
|
|
168
|
+
With GoodJob 4.22.1 or newer, use its public labelled concurrency rules:
|
|
169
|
+
|
|
170
|
+
```ruby
|
|
171
|
+
class SitemapRefreshJob < ApplicationJob
|
|
172
|
+
include GoodJob::ActiveJobExtensions::Concurrency
|
|
173
|
+
|
|
174
|
+
good_job_concurrency_rule(
|
|
175
|
+
perform_limit: 1,
|
|
176
|
+
label: "sitemap-refresh",
|
|
177
|
+
apply_label: true
|
|
178
|
+
)
|
|
179
|
+
|
|
180
|
+
good_job_concurrency_rule(
|
|
181
|
+
total_limit: 1,
|
|
182
|
+
label: -> { "sitemap-refresh:#{arguments.first}" },
|
|
183
|
+
apply_label: true
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
def perform(_version)
|
|
187
|
+
Indexmap::HostConfiguration.apply
|
|
188
|
+
Indexmap::TaskRunner.new.create
|
|
189
|
+
end
|
|
190
|
+
end
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
Enqueue with a release identifier: `SitemapRefreshJob.perform_later(version)`.
|
|
194
|
+
The version label limits unfinished work for that release; the shared label
|
|
195
|
+
limits generation across releases to one execution at a time. A newer release
|
|
196
|
+
can enqueue while an older refresh is unfinished. A repeat enqueue is allowed
|
|
197
|
+
after completion: these controls do not provide permanent once-per-release
|
|
198
|
+
tracking. Use `perform_later`; direct `perform_now` calls bypass GoodJob's limits.
|
|
199
|
+
See [GoodJob concurrency controls](https://github.com/bensheldon/good_job#concurrency-controls).
|
|
200
|
+
|
|
201
|
+
#### Solid Queue
|
|
202
|
+
|
|
203
|
+
With Solid Queue 1.7 or newer, serialize writes to the shared sitemap output:
|
|
204
|
+
|
|
205
|
+
```ruby
|
|
206
|
+
class SitemapRefreshJob < ApplicationJob
|
|
207
|
+
limits_concurrency to: 1, key: "sitemap-refresh",
|
|
208
|
+
duration: 30.minutes, on_conflict: :block
|
|
209
|
+
|
|
210
|
+
def perform(_version)
|
|
211
|
+
Indexmap::HostConfiguration.apply
|
|
212
|
+
Indexmap::TaskRunner.new.create
|
|
213
|
+
end
|
|
214
|
+
end
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
Choose a duration longer than the expected generation time. `:block` retains
|
|
218
|
+
every refresh and runs them serially; it does not suppress duplicate enqueues.
|
|
219
|
+
Using `on_conflict: :discard` coalesces conflicting requests, including requests
|
|
220
|
+
for newer releases when the key is shared. A version-based key such as
|
|
221
|
+
`->(version) { "sitemap-refresh:#{version}" }` scopes conflicts to that release,
|
|
222
|
+
but permits different releases to write concurrently. Select that tradeoff
|
|
223
|
+
explicitly; it is not equivalent to GoodJob's two rules above. Direct
|
|
224
|
+
`perform_now` calls bypass queue coordination.
|
|
225
|
+
See [Solid Queue concurrency controls](https://github.com/rails/solid_queue#concurrency-controls).
|
|
226
|
+
|
|
227
|
+
#### Triggering Refreshes
|
|
228
|
+
|
|
229
|
+
Enqueue from one release task, a recurring schedule, or application startup.
|
|
230
|
+
Rails' `after_initialize` hook can enqueue from a worker application without
|
|
231
|
+
depending on a backend's worker callbacks:
|
|
232
|
+
|
|
233
|
+
```ruby
|
|
234
|
+
Rails.application.config.after_initialize do
|
|
235
|
+
if Rails.env.production? && ENV["PROCESS_ROLE"] == "worker"
|
|
236
|
+
SitemapRefreshJob.perform_later(ENV.fetch("RELEASE_VERSION")) do |job|
|
|
237
|
+
raise job.enqueue_error if job.enqueue_error
|
|
238
|
+
end
|
|
239
|
+
end
|
|
240
|
+
end
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
`PROCESS_ROLE` and `RELEASE_VERSION` are application-defined environment values.
|
|
244
|
+
Configure them in the deployment, with the worker role flag limited to worker
|
|
245
|
+
processes. `after_initialize` runs for every Rails process, including commands;
|
|
246
|
+
it indicates application initialization, not worker readiness. Complete database
|
|
247
|
+
preparation before starting workers. Suppressed enqueues are expected; report
|
|
248
|
+
actual enqueue errors. Avoid scheduling the same refresh from both startup and
|
|
249
|
+
a release task.
|
|
250
|
+
|
|
251
|
+
Use storage shared by all serving containers, such as object storage through
|
|
252
|
+
the Active Storage adapter. A job container's local filesystem is not shared
|
|
253
|
+
with web containers unless the deployment mounts the same persistent storage.
|
|
254
|
+
|
|
255
|
+
#### Operating Practices And Current Limits
|
|
256
|
+
|
|
257
|
+
Generate database-backed content after database preparation, through one
|
|
258
|
+
host-owned refresh job. Load the existing configuration inside that job and
|
|
259
|
+
query eligible records when it runs. Pass release or output identifiers rather
|
|
260
|
+
than relations, URL inventories, or credentials. Keep queue selection, recurring
|
|
261
|
+
schedules, and any application authorization context in the host.
|
|
262
|
+
|
|
263
|
+
Use a shared concurrency key or label for jobs writing the same destination,
|
|
264
|
+
across releases. Prefer blocking or rescheduling conflicts to discarding a newer
|
|
265
|
+
refresh. Backend controls do not protect manual rake invocations, and Solid
|
|
266
|
+
Queue's concurrency duration can expire before an unusually long job finishes.
|
|
267
|
+
Avoid concurrent writers through all entrypoints in legacy mode: an interrupted
|
|
268
|
+
sequential publication can leave mixed generations. Opt-in atomic mode checks
|
|
269
|
+
root revisions under a storage lock and retains previous generations. See the
|
|
270
|
+
[publication and retention contract](docs/dynamic-sitemaps.md#atomic-publication-and-storage).
|
|
271
|
+
|
|
272
|
+
Keep generation and search-engine submission separate. A refresh retry should
|
|
273
|
+
not automatically resend successful IndexNow batches. Make `after_create`
|
|
274
|
+
callbacks safe to repeat: they run after publication, and a callback failure does
|
|
275
|
+
not roll published files back. Validate the deployed sitemap and preview IndexNow
|
|
276
|
+
selection before submitting. See [selection and attempt reports](docs/search-engine-ping.md#inspect-selection-and-actual-attempts).
|
|
277
|
+
|
|
278
|
+
Batch source queries with native Rails APIs and select the fields needed for
|
|
279
|
+
URLs and dates. Supply meaningful content dates; do not use the refresh time as
|
|
280
|
+
`lastmod`. Each sitemap must stay within 50,000 entries and 52,428,800 uncompressed
|
|
281
|
+
bytes. Index mode partitions automatically; single-file mode rejects an output
|
|
282
|
+
that needs splitting. Measure the complete refresh before adding parallel jobs or
|
|
283
|
+
caches. Supply lazy sources to benefit from incremental generation and validation;
|
|
284
|
+
app-owned arrays and explicit inventory-returning APIs still retain their data.
|
|
285
|
+
|
|
286
|
+
Generation uses the same synchronous entrypoints with GoodJob and Solid Queue;
|
|
287
|
+
the examples above describe their different concurrency behavior. Verify the
|
|
288
|
+
host's actual adapter, workers, shared storage, failure reporting, and intended
|
|
289
|
+
system context in integration tests. Gem tests alone do not establish those
|
|
290
|
+
deployment guarantees.
|
|
291
|
+
|
|
292
|
+
### Dynamic Robots
|
|
293
|
+
|
|
294
|
+
Indexmap can serve `/robots.txt` from configuration without querying content or
|
|
295
|
+
reading sitemap storage. Crawling defaults to disabled. Inside the generated
|
|
296
|
+
`Indexmap.configure` block, configure the site's policy:
|
|
297
|
+
|
|
298
|
+
```ruby
|
|
299
|
+
config.robots.indexable = -> { Rails.env.production? && ENV["PUBLIC_INDEXING"] == "1" }
|
|
300
|
+
config.robots.groups = [
|
|
301
|
+
{user_agent: "*", allow: ["/"], disallow: ["/admin/", "/search"]}
|
|
302
|
+
]
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The sitemap URL defaults to `base_url` plus `index_filename`. Override it with
|
|
306
|
+
`config.robots.sitemaps = ["https://example.com/sitemap.xml"]` when needed.
|
|
307
|
+
Groups and sitemap lists also accept callables. Keep them configuration-only;
|
|
308
|
+
do not query models in these callables.
|
|
309
|
+
|
|
310
|
+
In the application's `config/routes.rb`, load and mount the endpoint explicitly:
|
|
311
|
+
|
|
312
|
+
```ruby
|
|
313
|
+
require "indexmap/robots_controller"
|
|
314
|
+
Indexmap::HostConfiguration.apply
|
|
315
|
+
|
|
316
|
+
Rails.application.routes.draw do
|
|
317
|
+
get "/robots.txt", to: Indexmap::RobotsController.action(:show)
|
|
318
|
+
end
|
|
319
|
+
```
|
|
320
|
+
|
|
321
|
+
If the configuration loader lives outside an initializer, require that file
|
|
322
|
+
before applying it. Remove `public/robots.txt` so it cannot shadow the route.
|
|
323
|
+
The controller supports GET, HEAD, and conditional requests, uses a one-hour
|
|
324
|
+
public cache, and does not inherit application authentication or browser gates.
|
|
325
|
+
Set `PUBLIC_INDEXING` only for the intended public deployment; production-mode
|
|
326
|
+
previews should remain disabled. Robots rules do not replace authorization or
|
|
327
|
+
page-level indexing directives.
|
|
133
328
|
|
|
134
329
|
### Default Index Mode
|
|
135
330
|
|
|
@@ -138,6 +333,24 @@ This is the default behavior. `indexmap` writes:
|
|
|
138
333
|
- `sitemap.xml` as a sitemap index
|
|
139
334
|
- one or more child sitemap files from `config.sections`
|
|
140
335
|
|
|
336
|
+
Section entries can be enumerables. Indexmap consumes each section once and
|
|
337
|
+
calculates its index `lastmod` in that same pass. For database sources, return a
|
|
338
|
+
fresh enumerable from the configured sections callable on every generation:
|
|
339
|
+
|
|
340
|
+
```ruby
|
|
341
|
+
config.sections = -> do
|
|
342
|
+
entries = PublicPage.indexable.find_each.lazy.map do |page|
|
|
343
|
+
Indexmap::Entry.new(loc: page.canonical_url, lastmod: page.updated_at)
|
|
344
|
+
end
|
|
345
|
+
[Indexmap::Section.new(filename: "sitemap-pages.xml", entries: entries)]
|
|
346
|
+
end
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
`PublicPage`, its eligibility scope, and canonical URL method are host-app
|
|
350
|
+
examples. Keep queries and routing decisions in the host. Generation stages
|
|
351
|
+
bounded partitions and validates incrementally, using temporary disk for global
|
|
352
|
+
duplicate checks. See [memory and disk limits](docs/dynamic-sitemaps.md#configure-generation).
|
|
353
|
+
|
|
141
354
|
### Single-File Mode
|
|
142
355
|
|
|
143
356
|
For sites that only want one `sitemap.xml` file:
|
|
@@ -145,7 +358,7 @@ For sites that only want one `sitemap.xml` file:
|
|
|
145
358
|
```ruby
|
|
146
359
|
Indexmap.configure do |config|
|
|
147
360
|
config.base_url = -> { "https://example.com" }
|
|
148
|
-
config.storage =
|
|
361
|
+
config.storage = Indexmap::Storage::Filesystem.new(path: Rails.public_path, public_url: config.base_url)
|
|
149
362
|
config.format = :single_file
|
|
150
363
|
config.entries = -> do
|
|
151
364
|
[
|
|
@@ -168,7 +381,7 @@ outputs write through the same configured storage as the default output.
|
|
|
168
381
|
```ruby
|
|
169
382
|
Indexmap.configure do |config|
|
|
170
383
|
config.base_url = -> { "https://example.com" }
|
|
171
|
-
config.storage =
|
|
384
|
+
config.storage = Indexmap::Storage::Filesystem.new(path: Rails.root.join("storage/sitemaps"), public_url: config.base_url)
|
|
172
385
|
config.sections = -> { Sitemap.sections }
|
|
173
386
|
|
|
174
387
|
config.output :insights_data do |output|
|
|
@@ -194,8 +407,10 @@ Indexmap.create(:insights_data)
|
|
|
194
407
|
Named outputs inherit `base_url` and `format` from the main configuration unless
|
|
195
408
|
you override them. Storage is configured once and shared by every output.
|
|
196
409
|
|
|
197
|
-
`Indexmap.create`
|
|
198
|
-
|
|
410
|
+
`Indexmap.create` stages XML, validates it, then publishes the selected output.
|
|
411
|
+
Only `Indexmap::TaskRunner#create` (also used by the create task) handles the
|
|
412
|
+
IndexNow key file and `after_create` callbacks. Formatting is a separate optional
|
|
413
|
+
operation for legacy outputs; atomic outputs must be regenerated.
|
|
199
414
|
|
|
200
415
|
### Storage
|
|
201
416
|
|
|
@@ -243,17 +458,26 @@ storage.delete(filename)
|
|
|
243
458
|
storage.public_url(filename)
|
|
244
459
|
```
|
|
245
460
|
|
|
461
|
+
Atomic publication, pruning, and streamed HTTP delivery require additional
|
|
462
|
+
capabilities. See the [storage contract](docs/storage.md) for signatures,
|
|
463
|
+
return values, and adapter requirements. Each operation instance resolves a
|
|
464
|
+
storage callable once and shares the adapter with its collaborators. Create a
|
|
465
|
+
fresh runner/pinger after changing destinations or tenant context. Direct reads
|
|
466
|
+
of `config.storage` still resolve the callable; construct an adapter once in the
|
|
467
|
+
host loader when its settings are fixed for the process.
|
|
468
|
+
|
|
246
469
|
### Deferred Dynamic Sections
|
|
247
470
|
|
|
248
471
|
Use `after_create` when `indexmap:sitemap:create` should publish the default
|
|
249
472
|
sitemap first, then schedule slower dynamic sections for the background. The
|
|
250
|
-
callback runs only after the
|
|
251
|
-
|
|
473
|
+
callback runs only after the default output has been validated and published
|
|
474
|
+
and configured IndexNow key-file handling has succeeded. It also runs for
|
|
475
|
+
`Indexmap::TaskRunner#create`; `Indexmap.create` does not run callbacks.
|
|
252
476
|
|
|
253
477
|
```ruby
|
|
254
478
|
Indexmap.configure do |config|
|
|
255
479
|
config.base_url = -> { "https://example.com" }
|
|
256
|
-
config.storage =
|
|
480
|
+
config.storage = Indexmap::Storage::Filesystem.new(path: Rails.root.join("storage/sitemaps"), public_url: config.base_url)
|
|
257
481
|
config.sections = -> { Sitemap.sections }
|
|
258
482
|
|
|
259
483
|
config.output :insights_data do |output|
|
|
@@ -273,13 +497,17 @@ Then the job can stay small:
|
|
|
273
497
|
```ruby
|
|
274
498
|
class Insights::SitemapRefreshJob < ApplicationJob
|
|
275
499
|
def perform
|
|
500
|
+
Indexmap::HostConfiguration.apply
|
|
276
501
|
Indexmap.create(:insights_data)
|
|
277
502
|
end
|
|
278
503
|
end
|
|
279
504
|
```
|
|
280
505
|
|
|
281
|
-
|
|
282
|
-
|
|
506
|
+
The default output and named output are independent roots. List both public
|
|
507
|
+
root URLs in `config.robots.sitemaps` (or the static robots file) and submit each
|
|
508
|
+
separately if needed; named outputs are not automatically linked into the default
|
|
509
|
+
index. Publish the named output before advertising its URL. A deferred job can
|
|
510
|
+
fail after the default root is published, so monitor it separately.
|
|
283
511
|
|
|
284
512
|
## Validation And Parsing
|
|
285
513
|
|
|
@@ -312,6 +540,11 @@ The built-in validator checks for:
|
|
|
312
540
|
See [Search engine ping](docs/search-engine-ping.md) for Google credential
|
|
313
541
|
setup, Search Console property configuration, and IndexNow key provisioning.
|
|
314
542
|
|
|
543
|
+
For Rails apps with sitemaps outside `public/`, see
|
|
544
|
+
[serving an IndexNow key from `public/`](docs/search-engine-ping.md#rails-public-key-file-with-separate-sitemap-storage).
|
|
545
|
+
Key discovery searches the configured sitemap storage, so an existing public
|
|
546
|
+
key file needs explicit configuration in that setup.
|
|
547
|
+
|
|
315
548
|
Available rake tasks:
|
|
316
549
|
|
|
317
550
|
```bash
|
|
@@ -328,6 +561,13 @@ present, `indexmap:index_now:ping` skips IndexNow submission.
|
|
|
328
561
|
|
|
329
562
|
## Development
|
|
330
563
|
|
|
564
|
+
The gem requires Ruby 3.2+ and Active Support/railties 7.1+. CI exercises the
|
|
565
|
+
Rails 7.1 floor on Ruby 3.2 and Rails 8.1.4 integration on Ruby 4.0, alongside the
|
|
566
|
+
normal Ruby matrix. See [integration checks](docs/dynamic-sitemaps.md#integration-checks-from-a-source-checkout).
|
|
567
|
+
|
|
568
|
+
Install [git-cliff](https://git-cliff.org/) before running tests; the release
|
|
569
|
+
tests use it to generate changelogs in temporary repositories.
|
|
570
|
+
|
|
331
571
|
Run tests:
|
|
332
572
|
|
|
333
573
|
```bash
|
|
@@ -374,9 +614,9 @@ Install [git-cliff](https://git-cliff.org/) locally before preparing a
|
|
|
374
614
|
release. The release task prepends the next `CHANGELOG.md` section from
|
|
375
615
|
Conventional Commits.
|
|
376
616
|
|
|
377
|
-
Before preparing a release,
|
|
378
|
-
|
|
379
|
-
|
|
617
|
+
Before preparing a release, switch to updated `main` with a clean worktree
|
|
618
|
+
matching `origin/main`. If the release changes a public contract, merge the
|
|
619
|
+
`UPGRADE.md` migration steps before preparing the release.
|
|
380
620
|
|
|
381
621
|
Then run one of:
|
|
382
622
|
|
|
@@ -384,15 +624,27 @@ Then run one of:
|
|
|
384
624
|
bundle exec rake 'release:prepare[patch]'
|
|
385
625
|
bundle exec rake 'release:prepare[minor]'
|
|
386
626
|
bundle exec rake 'release:prepare[major]'
|
|
387
|
-
bundle exec rake 'release:prepare[0.
|
|
627
|
+
bundle exec rake 'release:prepare[0.9.0]'
|
|
388
628
|
```
|
|
389
629
|
|
|
390
|
-
The task
|
|
630
|
+
The task prepends the changelog and updates the version, preserving existing
|
|
631
|
+
history. It commits those two files on `main` with a Conventional Commit and
|
|
632
|
+
`What:`, `Why:`, and `Where:` in the body, then pushes `main`. Review any
|
|
633
|
+
release-status notices before running it. Hooks remain enabled; the branch
|
|
634
|
+
protection hook permits only the task's version and changelog commit on `main`.
|
|
635
|
+
Ordinary changes still require a topic branch. Remote branch protections must
|
|
636
|
+
permit the maintainer to push this release commit.
|
|
637
|
+
|
|
638
|
+
After preparation succeeds, create and push the annotated release tag:
|
|
639
|
+
|
|
640
|
+
```bash
|
|
641
|
+
bundle exec rake release:tag
|
|
642
|
+
```
|
|
391
643
|
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
644
|
+
Tagging requires a clean `main` matching `origin/main`, an unused version tag,
|
|
645
|
+
and a matching changelog section. Gem metadata links documentation to that
|
|
646
|
+
version's tag. If a push fails, resolve the failure and push the existing release
|
|
647
|
+
commit before tagging; do not rerun preparation with the same version.
|
|
396
648
|
|
|
397
649
|
The `Release` workflow then runs tests, publishes the gem to RubyGems,
|
|
398
650
|
and creates the GitHub release from the changelog entry.
|