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
data/UPGRADE.md
ADDED
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
# Upgrade Guide
|
|
2
|
+
|
|
3
|
+
Use this guide when upgrading an application that already generates, validates,
|
|
4
|
+
or pings sitemaps with `indexmap`.
|
|
5
|
+
|
|
6
|
+
## Unreleased
|
|
7
|
+
|
|
8
|
+
### Host loader and rendered artifacts
|
|
9
|
+
|
|
10
|
+
The install generator now defines `Indexmap::HostConfiguration.apply` instead of
|
|
11
|
+
`IndexmapConfiguration.apply`. When adopting the new template, move the existing
|
|
12
|
+
loader into `module Indexmap; module HostConfiguration; ...; end; end` and update
|
|
13
|
+
its bottom-of-file apply call, route mounts, jobs, scripts, and tests. Keep the
|
|
14
|
+
existing configure block, mutex, and idempotent `@applied` guard. Inside the new
|
|
15
|
+
namespace, qualify host constants that overlap gem names (for example `::Entry`
|
|
16
|
+
for a host model, versus `Indexmap::Entry` for a sitemap value). Use:
|
|
17
|
+
|
|
18
|
+
```ruby
|
|
19
|
+
Indexmap::HostConfiguration.apply if defined?(Indexmap::Configuration)
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
An existing host-owned loader can continue running while you migrate: the gem
|
|
23
|
+
never calls it by name. Do not mix old and new loader modules, as each has its own
|
|
24
|
+
application guard. Apply the host configuration before creating an operation.
|
|
25
|
+
|
|
26
|
+
The rendered file value is now `Indexmap::Artifact`, replacing
|
|
27
|
+
`Indexmap::Storage::File`. Its `filename`, `body`, `content_type`, and `basename`
|
|
28
|
+
interface is unchanged. Update custom adapters, type checks, and direct
|
|
29
|
+
constructor calls; replace internal `require "indexmap/storage/file"` with
|
|
30
|
+
`require "indexmap"`. The old constant and require path are removed. `::File`
|
|
31
|
+
always means Ruby's filesystem class.
|
|
32
|
+
|
|
33
|
+
### Storage resolution and publication lifecycle
|
|
34
|
+
|
|
35
|
+
`Creator`, `TaskRunner`, `Validator`, and pinger instances now resolve a storage
|
|
36
|
+
callable lazily once and retain that adapter. Nested generation, validation,
|
|
37
|
+
IndexNow key handling, and submission share it. Create a fresh operation instance
|
|
38
|
+
after changing storage configuration or tenant/context; do not reuse a runner or
|
|
39
|
+
pinger across different destinations. `Indexmap.create` already creates a new
|
|
40
|
+
creator each time. The optional `storage:` constructor keyword selects an explicit
|
|
41
|
+
adapter. Direct reads of `config.storage` still resolve the callable each time.
|
|
42
|
+
|
|
43
|
+
Filesystem publication now copies validated files and the previous root into
|
|
44
|
+
private `.indexmap-builds/` staging on the destination filesystem, outside the
|
|
45
|
+
per-root lock. Short locked sections create the staging lease and activate files
|
|
46
|
+
with same-filesystem renames. Keep that private directory writable and exclude
|
|
47
|
+
hidden paths from static HTTP serving. Allow disk space for source staging,
|
|
48
|
+
destination staging, and retained generations. Do not remove active staging
|
|
49
|
+
directories manually. Pruning reclaims abandoned staging after its grace period
|
|
50
|
+
and skips directories whose build lock is held; returned deleted paths can now
|
|
51
|
+
include these private directories. Normal failures remove staging immediately.
|
|
52
|
+
|
|
53
|
+
Filesystem listing now scopes traversal by the literal prefix's directory and
|
|
54
|
+
leading filename fragment. It excludes symlinks and hidden paths, including
|
|
55
|
+
private staging, and requires normalized relative prefixes (no traversal,
|
|
56
|
+
absolute paths, or redundant separators). Replace any reliance on symlink entries with actual keys under the storage root. A listing without a
|
|
57
|
+
prefix still intentionally scans the complete visible tree.
|
|
58
|
+
|
|
59
|
+
Atomic Active Storage checks the expected revision before uploading and again
|
|
60
|
+
before each upload. A failed or superseded build destroys its newly created child
|
|
61
|
+
records and purges its blobs immediately, including a partial upload whose blob
|
|
62
|
+
was saved before the service failed. Uploads and cleanup stay outside the root
|
|
63
|
+
row lock. A committed root is preserved even if a host `after_commit` callback
|
|
64
|
+
raises. Activation also checks that children still exist, so pruning an unusually
|
|
65
|
+
old in-flight build aborts it rather than publishing missing children.
|
|
66
|
+
|
|
67
|
+
Concurrent builds can still perform some speculative uploads before one wins;
|
|
68
|
+
optimistic publication cannot avoid work already in flight. Process death and
|
|
69
|
+
storage outages can also prevent immediate cleanup. Keep scheduled pruning,
|
|
70
|
+
unattached-blob cleanup, and working attachment purge jobs. A
|
|
71
|
+
`Indexmap::PublicationCleanupError` reports cleanup failures through `failures`
|
|
72
|
+
(each contains `artifact` and `error`), with the original publication exception as
|
|
73
|
+
its cause. Retain those private diagnostics; a failed service deletion can require
|
|
74
|
+
cleanup by object key even when Active Storage already removed the blob row.
|
|
75
|
+
|
|
76
|
+
### Maintainer release workflow
|
|
77
|
+
|
|
78
|
+
`release:prepare[...]` now runs from clean, updated `main` matching
|
|
79
|
+
`origin/main`. It updates the version and changelog, creates a Conventional
|
|
80
|
+
Commit with hooks enabled, and pushes `main`. Replace scripts that prepare on a
|
|
81
|
+
topic branch or separately commit those files. The branch hook permits only a
|
|
82
|
+
release-task commit containing the bumped version and matching changelog; other
|
|
83
|
+
changes still require a topic branch. Ensure remote branch protections permit
|
|
84
|
+
the maintainer to push this release commit. Then run `bundle exec rake release:tag`
|
|
85
|
+
to create and push the tag. If pushing preparation fails, push the existing
|
|
86
|
+
release commit before tagging rather than preparing the same version again.
|
|
87
|
+
See the [release instructions](README.md#release).
|
|
88
|
+
|
|
89
|
+
### Task loading, parsing, and documentation
|
|
90
|
+
|
|
91
|
+
For an existing lazy installation, edit the generated Rakefile loader to check
|
|
92
|
+
Rake's collected tasks as well as `ARGV` (Rails removes the command from `ARGV`
|
|
93
|
+
before loading the Rakefile):
|
|
94
|
+
|
|
95
|
+
```ruby
|
|
96
|
+
indexmap_tasks_requested = Rake.application.options.show_tasks ||
|
|
97
|
+
ARGV.any? { |argument| ["-T", "--tasks"].include?(argument) }
|
|
98
|
+
require "indexmap/tasks" if indexmap_tasks_requested ||
|
|
99
|
+
(Rake.application.top_level_tasks + ARGV).any? { |argument| argument.start_with?("indexmap:") }
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
Keep this block before `require_relative "config/application"`. The installer
|
|
103
|
+
preserves existing task loaders, so rerunning it does not replace a stale block.
|
|
104
|
+
Verify `bin/rails indexmap:sitemap:create`, `bin/rails --tasks`, and
|
|
105
|
+
`bundle exec rake indexmap:sitemap:validate`. The generated initializer now checks
|
|
106
|
+
`defined?(Indexmap::Configuration)` before applying settings and constructs the
|
|
107
|
+
filesystem adapter once inside `apply`. Apply those edits to existing initializers
|
|
108
|
+
if needed, together with the host-loader rename above.
|
|
109
|
+
|
|
110
|
+
`Parser` remote children and redirects must now keep the explicit source URL's
|
|
111
|
+
scheme, host, and port; userinfo is rejected. Cross-origin references raise
|
|
112
|
+
`Indexmap::ValidationError` before their target is requested. To inspect a deployed
|
|
113
|
+
sitemap through a local origin, use `rebase_remote_children: true` deliberately;
|
|
114
|
+
this rebases child references, not redirects. Pass only trusted remote root URLs:
|
|
115
|
+
the parser is not an SSRF filter for arbitrary user-supplied roots or DNS results.
|
|
116
|
+
Remote responses and local files are capped at 52,428,800 bytes per document, and
|
|
117
|
+
DTDs are rejected. Streaming XML parsing can raise `Nokogiri::XML::SyntaxError`
|
|
118
|
+
for malformed XML that the older DOM parser recovered silently. Fix malformed or
|
|
119
|
+
oversized/DTD-bearing documents and cross-origin references before upgrading.
|
|
120
|
+
Storage-root parsing resolves children through storage and does not fetch remote
|
|
121
|
+
child URLs.
|
|
122
|
+
|
|
123
|
+
Use `Parser#each_entry` for incremental enumeration without the `entries` cache;
|
|
124
|
+
`entries` and `paths` still collect results. Google URL counting now streams XML
|
|
125
|
+
and uses temporary disk for exact deduplication. Provision temporary space for
|
|
126
|
+
that operation as well as generation/validation.
|
|
127
|
+
|
|
128
|
+
Create stages, validates, and publishes; it does not pretty-print. Only
|
|
129
|
+
`TaskRunner#create` and its rake task run callbacks and IndexNow key handling.
|
|
130
|
+
The optional format task remains for legacy output. See the
|
|
131
|
+
[deployment guide](docs/deployment.md) for static/dynamic sources and delivery,
|
|
132
|
+
robots policy, and development/production checks. Main documentation describes
|
|
133
|
+
unreleased features; use tagged documentation while consuming 0.8.2.
|
|
134
|
+
|
|
135
|
+
### Generation, publication, and delivery
|
|
136
|
+
|
|
137
|
+
Generation now stages XML on temporary disk and partitions index sections by
|
|
138
|
+
`max_urls` and `max_bytes`. Defaults are the sitemap protocol limits. The first
|
|
139
|
+
legacy partition keeps its configured name; later files append `-002.xml`, etc.
|
|
140
|
+
Single-file mode still writes one file and fails if splitting would be required.
|
|
141
|
+
Locations must be shorter than 2,048 characters; repeated `loc`/`lastmod` fields
|
|
142
|
+
are rejected. Provision temporary disk for XML, validation spools, and duplicate
|
|
143
|
+
sorting. The full-document Nokogiri formatting pass is removed; XML meaning is preserved but
|
|
144
|
+
serialized bytes/checksums can change. Generation reports no longer download
|
|
145
|
+
published artifacts to count URLs.
|
|
146
|
+
|
|
147
|
+
Set `config.atomic = true` to opt into immutable generation children and guarded
|
|
148
|
+
root activation. Filesystem and Active Storage implement the required revision
|
|
149
|
+
and publish capabilities. Custom six-method adapters retain legacy behavior;
|
|
150
|
+
unsupported atomic mode fails explicitly. Generated child keys use a reserved
|
|
151
|
+
`sitemap-<owner>-<generation>-<section>-<part>.xml` namespace and cannot be
|
|
152
|
+
rewritten by built-in adapters. Update fixed filename route constraints, remove
|
|
153
|
+
static files that shadow new routes, and replace app cleanup callbacks that
|
|
154
|
+
recompute configured filenames. They would delete generated children.
|
|
155
|
+
|
|
156
|
+
Atomic Active Storage requires an unconditional unique filename index. Inspect
|
|
157
|
+
existing data/schema and use a generated host migration if needed. The host
|
|
158
|
+
artifact model remains app-owned. Retention uses archive record timestamps and
|
|
159
|
+
the model's normal attachment cleanup, which requires working purge-job workers.
|
|
160
|
+
Filesystem atomic mode requires shared lock/rename semantics; a worker-local
|
|
161
|
+
container directory is insufficient for multiple serving instances.
|
|
162
|
+
|
|
163
|
+
`Indexmap::SupersededPublication` means another build activated the root after
|
|
164
|
+
this build began. Retry by rebuilding; do not fall back to sequential writes.
|
|
165
|
+
Use native bounded Active Job retries in host jobs for this transient condition.
|
|
166
|
+
The same entrypoints support GoodJob and Solid Queue. The opt-in sitemap
|
|
167
|
+
controller serves configured roots and current/retained atomic children using
|
|
168
|
+
storage metadata and streaming reads. See [dynamic sitemap usage](docs/dynamic-sitemaps.md)
|
|
169
|
+
for routes, cache policy, adapter capabilities, and adoption steps.
|
|
170
|
+
|
|
171
|
+
Schedule `indexmap:sitemap:prune` separately. `RETENTION_HOURS` defaults to 48 and
|
|
172
|
+
must be at least 1; choose a grace period covering cached roots and rollbacks.
|
|
173
|
+
The format task refuses to mutate atomic publications; regenerate instead.
|
|
174
|
+
|
|
175
|
+
IndexNow selection now spools validated entries before sending, without retaining
|
|
176
|
+
all selected URLs. Optional bounded retries use `config.index_now.max_attempts`
|
|
177
|
+
(1..5, default 1) and `max_wait` (0..60 seconds, default 30). Attempt exports now
|
|
178
|
+
include attempt numbers. Long `Retry-After` values defer retries rather than
|
|
179
|
+
being shortened; this is not durable scheduling or exactly-once delivery.
|
|
180
|
+
|
|
181
|
+
Section entries are now consumed once per generation, including calculation of
|
|
182
|
+
the index's `lastmod`. Existing array sources keep the same XML meaning. Apps may
|
|
183
|
+
return fresh lazy enumerables from `config.sections`; do not reuse exhausted
|
|
184
|
+
one-shot sources between refreshes. Streaming generation and opt-in atomic
|
|
185
|
+
publication are described above.
|
|
186
|
+
|
|
187
|
+
Sitemap validation now rejects files larger than 52,428,800 uncompressed bytes,
|
|
188
|
+
URL sets and indexes with more than 50,000 entries, missing or incorrect sitemap
|
|
189
|
+
namespaces, DTDs, missing URL locations, cyclic indexes, unsafe child paths, and
|
|
190
|
+
child URLs outside the configured scheme, host, or port. Relative local child
|
|
191
|
+
references remain supported. Fix invalid existing files before upgrading.
|
|
192
|
+
Generation still validates before publication; sequential storage writes do not
|
|
193
|
+
provide atomic publication of the entire sitemap set.
|
|
194
|
+
|
|
195
|
+
IndexNow now validates the reachable sitemap before planning or sending. Duplicate
|
|
196
|
+
or otherwise invalid entries fail validation instead of being silently filtered.
|
|
197
|
+
Index sections partition automatically; use index format when a single-file
|
|
198
|
+
output would exceed its limits.
|
|
199
|
+
|
|
200
|
+
Date-only `lastmod` values now participate in IndexNow cutoff selection. Dates on
|
|
201
|
+
or after the cutoff's UTC calendar day are included, even when the cutoff has a
|
|
202
|
+
later time on that day. Missing dates remain included. Invalid `SINCE` still
|
|
203
|
+
raises and does not fall back to `INDEXNOW_RECENT_HOURS`. Batch sizes must be
|
|
204
|
+
integers from 1 through 10,000; malformed configuration is no longer truncated.
|
|
205
|
+
|
|
206
|
+
The new `indexmap:index_now:plan` task exports selection as JSON Lines without a
|
|
207
|
+
key or network submission. Existing dry runs still require a key. Set
|
|
208
|
+
`INDEXNOW_REPORT=1` on the ping task for JSON Lines containing attempt-started,
|
|
209
|
+
attempt-finished, and summary records. These are non-durable exports: save them
|
|
210
|
+
to private operator storage if needed, never public sitemap storage. HTTP 202
|
|
211
|
+
is reported as `validation_pending` in the attempt receipt. The summary retains
|
|
212
|
+
its existing `submitted` status for successful HTTP responses; it does not prove
|
|
213
|
+
indexing. Transport failures now appear in partial/failed results with reason
|
|
214
|
+
`unknown_delivery`, preserving earlier successful batches. Retries are opt-in as
|
|
215
|
+
described above; there is no durable delivery history.
|
|
216
|
+
|
|
217
|
+
Robots delivery is opt-in. Require `indexmap/robots_controller`, apply the host
|
|
218
|
+
configuration, and mount the controller explicitly as documented in the README.
|
|
219
|
+
Remove `public/robots.txt` when mounting it; static files can shadow Rails routes.
|
|
220
|
+
Crawling defaults to disabled. Configure an explicit predicate that also handles
|
|
221
|
+
staging running in production mode. The endpoint does not change host routes,
|
|
222
|
+
queue adapters, refresh jobs, or sitemap storage.
|
|
223
|
+
|
|
224
|
+
Search-engine ping tasks now treat `config.index_filename` as the authoritative
|
|
225
|
+
sitemap entrypoint. Google submits that sitemap once, and IndexNow recursively
|
|
226
|
+
reads the child sitemaps it references. Unreferenced `sitemap*.xml` files in the
|
|
227
|
+
configured storage are no longer submitted or included in IndexNow URL batches.
|
|
228
|
+
|
|
229
|
+
Before upgrading, ensure every current sitemap is reachable from the configured
|
|
230
|
+
root sitemap. If an application intentionally maintains an independent named
|
|
231
|
+
output, submit that output separately instead of relying on storage discovery.
|
|
232
|
+
|
|
233
|
+
## Upgrading To 0.6.x
|
|
234
|
+
|
|
235
|
+
Version 0.6.x routes every sitemap operation through the configured storage
|
|
236
|
+
backend. Generation, formatting, validation, parsing, Google submission,
|
|
237
|
+
IndexNow submission, and IndexNow verification files should all read and write
|
|
238
|
+
through the same storage object.
|
|
239
|
+
|
|
240
|
+
### 1. Configure Storage Explicitly
|
|
241
|
+
|
|
242
|
+
Set `config.storage` in the application initializer instead of relying on
|
|
243
|
+
implicit filesystem behavior.
|
|
244
|
+
|
|
245
|
+
For a Rails app that serves sitemap files from `public/`:
|
|
246
|
+
|
|
247
|
+
```ruby
|
|
248
|
+
Indexmap.configure do |config|
|
|
249
|
+
config.base_url = -> { "https://example.com" }
|
|
250
|
+
config.storage = -> do
|
|
251
|
+
Indexmap::Storage::Filesystem.new(
|
|
252
|
+
path: Rails.public_path,
|
|
253
|
+
public_url: config.base_url
|
|
254
|
+
)
|
|
255
|
+
end
|
|
256
|
+
end
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
For a production app that stores sitemap files elsewhere, point the storage
|
|
260
|
+
adapter at that production location and keep `public_url` on the public origin
|
|
261
|
+
where search engines fetch the files.
|
|
262
|
+
|
|
263
|
+
```ruby
|
|
264
|
+
Indexmap.configure do |config|
|
|
265
|
+
config.base_url = -> { "https://example.com" }
|
|
266
|
+
config.storage = -> do
|
|
267
|
+
Indexmap::Storage::Filesystem.new(
|
|
268
|
+
path: Rails.root.join("storage/sitemaps"),
|
|
269
|
+
public_url: config.base_url
|
|
270
|
+
)
|
|
271
|
+
end
|
|
272
|
+
end
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### 2. Keep Relative Storage Keys Stable
|
|
276
|
+
|
|
277
|
+
If your app stores sitemap files under a directory, keep those directory
|
|
278
|
+
prefixes in `config.index_filename`, section filenames, and named output
|
|
279
|
+
filenames.
|
|
280
|
+
|
|
281
|
+
```ruby
|
|
282
|
+
Indexmap.configure do |config|
|
|
283
|
+
config.index_filename = "sitemaps/sitemap.xml"
|
|
284
|
+
config.sections = -> do
|
|
285
|
+
[
|
|
286
|
+
Indexmap::Section.new(
|
|
287
|
+
filename: "sitemaps/sitemap-pages.xml",
|
|
288
|
+
entries: Sitemap.pages
|
|
289
|
+
)
|
|
290
|
+
]
|
|
291
|
+
end
|
|
292
|
+
end
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Do not work around older versions by stripping paths down to basenames. The
|
|
296
|
+
parser, validator, pingers, and filesystem adapter now preserve relative storage
|
|
297
|
+
keys such as `sitemaps/sitemap.xml`.
|
|
298
|
+
|
|
299
|
+
### 3. Update Custom Storage Backends
|
|
300
|
+
|
|
301
|
+
Custom storage backends must accept the documented `content_type:` keyword on
|
|
302
|
+
`write`. The format task rewrites XML files and passes the XML content type.
|
|
303
|
+
|
|
304
|
+
```ruby
|
|
305
|
+
class SitemapStorage
|
|
306
|
+
def write(filename, body, content_type:)
|
|
307
|
+
# Persist body under filename and store content_type when the backend
|
|
308
|
+
# uses it.
|
|
309
|
+
end
|
|
310
|
+
|
|
311
|
+
def read(filename)
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def exist?(filename)
|
|
315
|
+
end
|
|
316
|
+
|
|
317
|
+
def list(prefix:, suffix:)
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def delete(filename)
|
|
321
|
+
end
|
|
322
|
+
|
|
323
|
+
def public_url(filename)
|
|
324
|
+
end
|
|
325
|
+
end
|
|
326
|
+
```
|
|
327
|
+
|
|
328
|
+
### 4. Verify Development And Production
|
|
329
|
+
|
|
330
|
+
Run the same lifecycle tasks against the storage configuration each environment
|
|
331
|
+
will use.
|
|
332
|
+
|
|
333
|
+
Development:
|
|
334
|
+
|
|
335
|
+
```bash
|
|
336
|
+
bin/rails indexmap:sitemap:create
|
|
337
|
+
bin/rails indexmap:sitemap:validate
|
|
338
|
+
bin/rails indexmap:sitemap:format
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
Production or staging:
|
|
342
|
+
|
|
343
|
+
```bash
|
|
344
|
+
RAILS_ENV=production bin/rails indexmap:sitemap:create
|
|
345
|
+
RAILS_ENV=production bin/rails indexmap:sitemap:validate
|
|
346
|
+
RAILS_ENV=production bin/rails indexmap:ping
|
|
347
|
+
```
|
|
348
|
+
|
|
349
|
+
Before deploying, confirm:
|
|
350
|
+
|
|
351
|
+
- `config.base_url` is the public origin for the environment.
|
|
352
|
+
- `config.index_filename` identifies the public root sitemap and exists in the
|
|
353
|
+
configured storage.
|
|
354
|
+
- The root sitemap references every current child sitemap. Unreferenced sitemap
|
|
355
|
+
files in storage are intentionally ignored by the pingers.
|
|
356
|
+
- `config.storage.public_url(config.index_filename)` returns the fetchable root
|
|
357
|
+
sitemap URL.
|
|
358
|
+
- `indexmap:sitemap:validate` passes against the same storage used by
|
|
359
|
+
production.
|
|
360
|
+
- If IndexNow is enabled, the `<key>.txt` verification file is present in the
|
|
361
|
+
configured storage or `config.index_now.key` is set.
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# How Indexmap works
|
|
2
|
+
|
|
3
|
+
> This describes unreleased main. See [release status](../README.md#release-status).
|
|
4
|
+
|
|
5
|
+
The host supplies eligible URLs and content dates. Indexmap generates, validates,
|
|
6
|
+
and publishes XML. A web server, CDN, or explicitly mounted Rails controller
|
|
7
|
+
serves those artifacts. Crawlers do not trigger generation or query section sources.
|
|
8
|
+
|
|
9
|
+
```mermaid
|
|
10
|
+
flowchart TD
|
|
11
|
+
manifest["Host manifest: static entries or database enumeration"] --> writer
|
|
12
|
+
ruby["Indexmap.create(output)"] --> creator["Creator: selected output"]
|
|
13
|
+
task["Rake create task"] --> runner["TaskRunner#create: default output"]
|
|
14
|
+
job["Host refresh job"] --> runner
|
|
15
|
+
runner --> creator
|
|
16
|
+
creator --> writer["Writer: consume sources once and partition XML"]
|
|
17
|
+
writer --> staging["Temporary disk staging"]
|
|
18
|
+
staging --> validate["Validator: complete graph and disk-backed duplicate checks"]
|
|
19
|
+
validate --> mode{"Publication mode"}
|
|
20
|
+
mode -->|legacy| sequential["Write children, then stable root sequentially"]
|
|
21
|
+
mode -->|atomic| prepare["Prepare children outside the root lock"]
|
|
22
|
+
prepare --> atomic["Check revision and activate root under a short lock"]
|
|
23
|
+
sequential --> storage["Configured storage"]
|
|
24
|
+
atomic --> storage
|
|
25
|
+
creator -->|successful return to TaskRunner| hooks["IndexNow key handling, then after_create callbacks"]
|
|
26
|
+
storage --> static["Static server or CDN"]
|
|
27
|
+
storage --> controller["Opt-in Rails sitemap controller"]
|
|
28
|
+
static --> crawler["Crawler fetches published XML"]
|
|
29
|
+
controller --> crawler
|
|
30
|
+
config["Host robots policy"] --> robots["Static robots.txt or opt-in Rails robots controller"]
|
|
31
|
+
robots --> crawler
|
|
32
|
+
storage --> ping["Separate Google or IndexNow submission"]
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Generation and validation errors occur before publication. Legacy writes can
|
|
36
|
+
leave mixed files if publication itself fails. Atomic mode retains the previous
|
|
37
|
+
root until activation succeeds; a competing winner causes
|
|
38
|
+
`Indexmap::SupersededPublication`. Built-in adapters clean up ordinary failed
|
|
39
|
+
builds; retention covers artifacts left by crashes. Cleanup errors are reported
|
|
40
|
+
explicitly. Callbacks and key handling run after publication,
|
|
41
|
+
so their failures do not undo it. Neither generation entrypoint submits to search
|
|
42
|
+
engines automatically. A host can choose to submit from a callback, but separate
|
|
43
|
+
submission makes refresh retries easier to operate.
|
|
44
|
+
|
|
45
|
+
| Entrypoint | Purpose |
|
|
46
|
+
| --- | --- |
|
|
47
|
+
| `Indexmap.create(:name)` | Generate, validate, and publish one output; defaults to `:default`. |
|
|
48
|
+
| `Indexmap::TaskRunner.new.create` | Generate the default output, handle the IndexNow key, then invoke callbacks. |
|
|
49
|
+
| `indexmap:sitemap:create` | Run `TaskRunner#create` and print the writer's recorded counts. |
|
|
50
|
+
| `Writer#each_file` / `Writer#write` | Render XML files; callers own validation and publication. `write` collects all files. |
|
|
51
|
+
| `indexmap:sitemap:format` | Optionally pretty-print legacy stored files; atomic outputs reject mutation. |
|
|
52
|
+
| Sitemap / robots controllers | Serve artifacts or configuration; never generate a sitemap. |
|
|
53
|
+
|
|
54
|
+
See [domain terms](domain-terms.md), [deployment choices](deployment.md), and the
|
|
55
|
+
[storage contract](storage.md).
|
data/docs/deployment.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# Choose a deployment path
|
|
2
|
+
|
|
3
|
+
> This guide describes unreleased main, not RubyGems 0.8.2.
|
|
4
|
+
> See [release status](../README.md#release-status) before installing.
|
|
5
|
+
|
|
6
|
+
Source data and HTTP delivery are separate decisions. A database-backed sitemap
|
|
7
|
+
can be served as a static file after a scheduled refresh. Static marketing URLs
|
|
8
|
+
can be served by a Rails controller. Neither choice generates XML on crawler
|
|
9
|
+
requests. Robots policy is independent of both.
|
|
10
|
+
|
|
11
|
+
| Situation | Generate | Store and serve | Robots |
|
|
12
|
+
| --- | --- | --- | --- |
|
|
13
|
+
| Static marketing site | Build or release step from an explicit manifest; no database required by the source | Include `public/` artifacts in the deployed image/site | Static file, or Rails endpoint if environment policy varies |
|
|
14
|
+
| Database content, one server | Refresh after database preparation and on a host schedule | Persistent filesystem shared by the refresh and web processes; static server or Rails | Static or dynamic policy |
|
|
15
|
+
| Database content, multiple containers | Host job on GoodJob/Solid Queue or a release task | Shared persistent filesystem with working locks, or Active Storage with shared backing storage | Dynamic policy is useful for production-mode previews |
|
|
16
|
+
| Development or staging | Run the same generation/validation lifecycle with test content | Local filesystem or isolated staging storage | Disallow crawling; do not submit to search engines |
|
|
17
|
+
|
|
18
|
+
See the [pipeline diagram](architecture.md) for the generation/publication boundary.
|
|
19
|
+
|
|
20
|
+
## Install and configure
|
|
21
|
+
|
|
22
|
+
Follow the [Rails installation](../README.md#rails-usage), including
|
|
23
|
+
`require: false` and `bin/rails generate indexmap:install`. Edit the configure
|
|
24
|
+
block inside `Indexmap::HostConfiguration.apply`, keeping its idempotent wrapper.
|
|
25
|
+
Construct storage once there when its settings are fixed:
|
|
26
|
+
|
|
27
|
+
```ruby
|
|
28
|
+
config.base_url = ENV.fetch("INDEXMAP_BASE_URL", "http://localhost:3000")
|
|
29
|
+
config.storage = Indexmap::Storage::Filesystem.new(
|
|
30
|
+
path: Rails.public_path, public_url: config.base_url
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Always set `INDEXMAP_BASE_URL` to the actual public origin in deployment. For
|
|
35
|
+
private filesystem storage change `path` to `Rails.root.join("storage/sitemaps")`
|
|
36
|
+
and explicitly serve it through the Rails controller or web server. That directory
|
|
37
|
+
does not become publicly reachable merely because it has a `public_url` setting.
|
|
38
|
+
Use separate storage namespaces for development, staging, and production.
|
|
39
|
+
|
|
40
|
+
### Static sources
|
|
41
|
+
|
|
42
|
+
For a small, database-free manifest, add to the same configure block:
|
|
43
|
+
|
|
44
|
+
```ruby
|
|
45
|
+
config.format = :single_file
|
|
46
|
+
config.entries = -> do
|
|
47
|
+
["/", "/about", "/pricing"].map do |path|
|
|
48
|
+
Indexmap::Entry.new(loc: "#{config.base_url}#{path}")
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
Use an origin without a trailing slash for this example. Supply actual content
|
|
54
|
+
modification dates when available; omit `lastmod` when they are unknown. For
|
|
55
|
+
larger or grouped manifests use `config.sections` and index mode as in the
|
|
56
|
+
[README](../README.md#default-index-mode). Single-file mode fails if it needs to
|
|
57
|
+
split; index mode partitions automatically.
|
|
58
|
+
|
|
59
|
+
The gem does not require a database for a static source. The host Rails boot may
|
|
60
|
+
still require services through unrelated initializers. Keep its static build
|
|
61
|
+
entrypoint free of database queries, or generate through a plain Ruby script
|
|
62
|
+
with `require "indexmap"`, `Indexmap.configure`, and `Indexmap.create`.
|
|
63
|
+
|
|
64
|
+
### Database-backed sources
|
|
65
|
+
|
|
66
|
+
Use the [database-backed guide](dynamic-sitemaps.md#configure-generation) for lazy
|
|
67
|
+
eligible-record enumeration. Generate after migrations and source transactions
|
|
68
|
+
commit. Refresh on a recurring schedule when eligibility depends on time, and
|
|
69
|
+
on meaningful publishing events if needed. Hosts own queries, routes, meaningful
|
|
70
|
+
dates, authorization context, jobs, and schedules.
|
|
71
|
+
|
|
72
|
+
For independent static and database outputs, use
|
|
73
|
+
[named outputs](../README.md#named-outputs). They do not automatically form a
|
|
74
|
+
combined index. Advertise every independent public root in robots.txt and submit
|
|
75
|
+
each separately if needed. Generate a deferred output successfully before
|
|
76
|
+
advertising it.
|
|
77
|
+
|
|
78
|
+
## Development verification
|
|
79
|
+
|
|
80
|
+
```sh
|
|
81
|
+
INDEXMAP_BASE_URL=http://localhost:3000 bin/rails indexmap:sitemap:create
|
|
82
|
+
INDEXMAP_BASE_URL=http://localhost:3000 bin/rails indexmap:sitemap:validate
|
|
83
|
+
bin/rails server
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
From another terminal, fetch the chosen endpoints:
|
|
87
|
+
|
|
88
|
+
```sh
|
|
89
|
+
curl -i http://localhost:3000/sitemap.xml
|
|
90
|
+
curl -i http://localhost:3000/robots.txt
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Expect XML with the configured localhost URLs and robots policy that disallows
|
|
94
|
+
crawling. If the root is an index, fetch one referenced child too. Check HEAD
|
|
95
|
+
and conditional responses when using controllers. Creation validates before
|
|
96
|
+
publishing; the separate validate command checks the stored result. Formatting
|
|
97
|
+
is optional for legacy output and unavailable for atomic publications.
|
|
98
|
+
|
|
99
|
+
## Production static files
|
|
100
|
+
|
|
101
|
+
For static manifests, generate with the final public origin during a build or
|
|
102
|
+
release step, then include every generated root and child in the deployed
|
|
103
|
+
artifacts:
|
|
104
|
+
|
|
105
|
+
```sh
|
|
106
|
+
RAILS_ENV=production INDEXMAP_BASE_URL=https://example.com \
|
|
107
|
+
bin/rails indexmap:sitemap:create
|
|
108
|
+
RAILS_ENV=production INDEXMAP_BASE_URL=https://example.com \
|
|
109
|
+
bin/rails indexmap:sitemap:validate
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Do not generate database content during an image build unless database access
|
|
113
|
+
and snapshot semantics are deliberate. Use a post-migration release step or job
|
|
114
|
+
instead. A release container's generated files must reach the serving instances;
|
|
115
|
+
files left on its private filesystem are unavailable to web containers.
|
|
116
|
+
|
|
117
|
+
Serve `public/` through the deployment's web server/CDN, or explicitly enable
|
|
118
|
+
Rails static serving. If the host uses an environment flag, configure its
|
|
119
|
+
production environment accordingly:
|
|
120
|
+
|
|
121
|
+
```ruby
|
|
122
|
+
config.public_file_server.enabled = ENV["RAILS_SERVE_STATIC_FILES"] == "1"
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
Set the flag only where Rails should serve these files. A reverse proxy or CDN
|
|
126
|
+
can serve them without Rails static serving. Keep stable roots on a short TTL,
|
|
127
|
+
and use a long immutable TTL only for atomic generated children. Legacy child
|
|
128
|
+
names are overwritten and must not receive immutable caching. Keep previously
|
|
129
|
+
referenced children available through the cached-root and rollback period.
|
|
130
|
+
|
|
131
|
+
A complete image deployed to one server can be enough for a static manifest.
|
|
132
|
+
Rolling deployments across different images can expose roots and children from
|
|
133
|
+
different releases; use shared storage or deploy all artifacts before switching
|
|
134
|
+
the root. Atomic mode protects publication in its configured store, not an
|
|
135
|
+
arbitrary deployment system's separate file-copy step.
|
|
136
|
+
|
|
137
|
+
## Production shared storage and Rails delivery
|
|
138
|
+
|
|
139
|
+
For refresh jobs across containers, use shared persistent storage. Configure
|
|
140
|
+
`config.atomic = true` for revision-checked activation and immutable children.
|
|
141
|
+
Choose Filesystem only when locks and rename work across processes, or use the
|
|
142
|
+
[Active Storage adapter](../README.md#storage) with a unique artifact filename
|
|
143
|
+
index. A shared Active Storage database alone is insufficient if each container
|
|
144
|
+
uses a different local disk service.
|
|
145
|
+
|
|
146
|
+
Mount the root and generated child routes from
|
|
147
|
+
[Serve published XML](dynamic-sitemaps.md#serve-published-xml). Store XML outside
|
|
148
|
+
`public/` when the controller should own delivery, and remove old static files
|
|
149
|
+
that would shadow its routes. The shared controller serves configured roots and
|
|
150
|
+
atomic children; legacy index children require deliberate static or host delivery.
|
|
151
|
+
A single-file output needs only its root route.
|
|
152
|
+
|
|
153
|
+
Use one host refresh owner and the host's actual queue adapter; see
|
|
154
|
+
[background generation](../README.md#background-generation). Apply the generated
|
|
155
|
+
configuration in jobs. `TaskRunner#create` includes key-file handling and
|
|
156
|
+
callbacks; `Indexmap.create(:name)` only generates an output. Callbacks can fail
|
|
157
|
+
after files are published, so make them safe to repeat and monitor job failures.
|
|
158
|
+
|
|
159
|
+
Schedule pruning separately for atomic output:
|
|
160
|
+
|
|
161
|
+
```sh
|
|
162
|
+
RAILS_ENV=production RETENTION_HOURS=48 bin/rails indexmap:sitemap:prune
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
Supply the same storage/origin settings as generation. Retain children longer
|
|
166
|
+
than cached roots, stale responses, and rollback windows can reference them.
|
|
167
|
+
Replace old callbacks that delete files absent from configured section names.
|
|
168
|
+
|
|
169
|
+
## Static or dynamic robots.txt
|
|
170
|
+
|
|
171
|
+
Choose one owner for `/robots.txt`:
|
|
172
|
+
|
|
173
|
+
- **Static:** manage `public/robots.txt` or a web-server file. For a public
|
|
174
|
+
production site, it can contain the following. Deploy a separate disallow file
|
|
175
|
+
for development, staging, and previews.
|
|
176
|
+
- **Dynamic:** mount the [robots controller](../README.md#dynamic-robots), remove
|
|
177
|
+
`public/robots.txt`, and configure an explicit environment predicate. It works
|
|
178
|
+
with static or controller-served sitemap XML and does not read sitemap storage.
|
|
179
|
+
|
|
180
|
+
Public production file:
|
|
181
|
+
|
|
182
|
+
```text
|
|
183
|
+
User-agent: *
|
|
184
|
+
Allow: /
|
|
185
|
+
Sitemap: https://example.com/sitemap.xml
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
Development, staging, or preview file:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
User-agent: *
|
|
192
|
+
Disallow: /
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Dynamic policy inside the configure block:
|
|
196
|
+
|
|
197
|
+
```ruby
|
|
198
|
+
config.robots.indexable = -> { Rails.env.production? && ENV["PUBLIC_INDEXING"] == "1" }
|
|
199
|
+
config.robots.groups = [{user_agent: "*", allow: ["/"], disallow: ["/admin/", "/search"]}]
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Set `PUBLIC_INDEXING=1` only on the intended public deployment. A preview can
|
|
203
|
+
run with `RAILS_ENV=production`; that alone must not enable crawling. When the
|
|
204
|
+
predicate is false, Indexmap emits disallow-all without sitemap advertisements.
|
|
205
|
+
Static robots files ignore this predicate. Keep host-specific exclusions in the
|
|
206
|
+
chosen policy. Robots instructions are not access control and do not ensure a
|
|
207
|
+
page is removed from an index; protect private routes independently.
|
|
208
|
+
|
|
209
|
+
## Verify the deployed result
|
|
210
|
+
|
|
211
|
+
Fetch the actual public root, a referenced child (in index mode), and robots.txt.
|
|
212
|
+
Check content, content types, redirects, cache headers, and intended allow/disallow
|
|
213
|
+
rules from outside the container. Confirm that job writes are visible from every
|
|
214
|
+
serving instance and that a refresh failure retains the old root in atomic mode.
|
|
215
|
+
A storage validation pass does not prove HTTP delivery.
|
|
216
|
+
|
|
217
|
+
Keep search-engine submission separate. Test with
|
|
218
|
+
`INDEXNOW_DRY_RUN=1 bin/rails indexmap:index_now:ping` against the correct
|
|
219
|
+
environment before live submission; a dry run still needs an IndexNow key.
|
|
220
|
+
[Provision the verification key](search-engine-ping.md) at its public URL,
|
|
221
|
+
especially when XML lives outside `public/`. Do not submit development or staging
|
|
222
|
+
URLs. Google credentials and property setup, IndexNow selection previews, and
|
|
223
|
+
attempt reports are covered in the [search-engine guide](search-engine-ping.md).
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# Domain terms
|
|
2
|
+
|
|
3
|
+
| Term | Meaning |
|
|
4
|
+
| --- | --- |
|
|
5
|
+
| Configuration | `Indexmap::Configuration` holds gem settings, sources, outputs, callbacks, and robots policy. |
|
|
6
|
+
| Configuration loader | Generated host module `Indexmap::HostConfiguration.apply`; applies the settings once per process. It is separate from the gem's configuration object. |
|
|
7
|
+
| Output | One independently generated target with a stable root filename. Named outputs share storage and are not automatically linked into the default output. |
|
|
8
|
+
| Section | A host-defined filename and enumerable of page entries, used in index mode. |
|
|
9
|
+
| Page entry | `Indexmap::Entry`: the host's `loc` and optional meaningful content `lastmod`. |
|
|
10
|
+
| Parsed entry | `Indexmap::Parser::Entry`: `loc`, `lastmod`, and the source sitemap key/URL. Returned by parsing and validation. |
|
|
11
|
+
| Partition | A bounded child XML document produced from a section. One section may produce several partitions. |
|
|
12
|
+
| Root | The configured `index_filename`: an index of children, or a URL set in single-file mode. |
|
|
13
|
+
| Publication | One generated output made visible in storage. Atomic publications have immutable children and a guarded root activation. |
|
|
14
|
+
| Generation | The unique identifier used for an atomic build's child keys and retained root archive. |
|
|
15
|
+
| Artifact | A stored XML file, including a root, child, or retained root archive. `Indexmap::Artifact` is the writer's in-memory filename/body/content-type value, not Ruby's filesystem `::File`. |
|
|
16
|
+
| Storage key | A relative filename, possibly including directories, such as `sitemaps/sitemap.xml`. It is separate from its public HTTP URL. |
|
|
17
|
+
| Static source | Explicit routes or manifest entries that can be generated without querying a database. |
|
|
18
|
+
| Dynamic source | Host data selected at refresh time, often through a lazy database enumeration. It does not mean generation during HTTP requests. |
|
|
19
|
+
| Format | `:index` or `:single_file` selects XML shape. The separate `format` task changes whitespace in legacy artifacts. |
|
|
20
|
+
| Robots policy | Crawl instructions served as `/robots.txt`. It is independent of sitemap storage and refresh scheduling. |
|
|
21
|
+
|
|
22
|
+
Use these terms in host manifests and operational instructions. The generated
|
|
23
|
+
host loader is `Indexmap::HostConfiguration`; `Indexmap::Configuration` is the
|
|
24
|
+
gem's settings object. `Indexmap::Artifact` is rendered data and `::File` is Ruby's
|
|
25
|
+
filesystem class. The former `IndexmapConfiguration` and `Indexmap::Storage::File`
|
|
26
|
+
names are replaced by this unreleased contract change; see
|
|
27
|
+
[UPGRADE.md](../UPGRADE.md#host-loader-and-rendered-artifacts) for migration.
|