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.
Files changed (51) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +287 -35
  4. data/UPGRADE.md +361 -0
  5. data/docs/architecture.md +55 -0
  6. data/docs/deployment.md +223 -0
  7. data/docs/domain-terms.md +27 -0
  8. data/docs/dynamic-sitemaps.md +302 -0
  9. data/docs/search-engine-ping.md +368 -0
  10. data/docs/storage.md +94 -0
  11. data/lib/generators/indexmap/install_generator.rb +1 -1
  12. data/lib/generators/indexmap/templates/initializer.rb.tt +18 -18
  13. data/lib/indexmap/artifact.rb +9 -0
  14. data/lib/indexmap/configuration.rb +12 -1
  15. data/lib/indexmap/creator.rb +45 -42
  16. data/lib/indexmap/index_now_configuration.rb +3 -1
  17. data/lib/indexmap/output.rb +19 -5
  18. data/lib/indexmap/parser.rb +112 -52
  19. data/lib/indexmap/pinger/base.rb +6 -5
  20. data/lib/indexmap/pinger/google.rb +12 -8
  21. data/lib/indexmap/pinger/index_now.rb +97 -52
  22. data/lib/indexmap/publication.rb +89 -0
  23. data/lib/indexmap/robots.rb +67 -0
  24. data/lib/indexmap/robots_controller.rb +13 -0
  25. data/lib/indexmap/sitemaps_controller.rb +57 -0
  26. data/lib/indexmap/storage/active_storage.rb +132 -1
  27. data/lib/indexmap/storage/filesystem.rb +182 -11
  28. data/lib/indexmap/storage/memory.rb +1 -1
  29. data/lib/indexmap/task_runner.rb +21 -28
  30. data/lib/indexmap/validator/duplicates.rb +83 -0
  31. data/lib/indexmap/validator.rb +171 -59
  32. data/lib/indexmap/version.rb +1 -1
  33. data/lib/indexmap/writer.rb +89 -34
  34. data/lib/indexmap.rb +14 -1
  35. data/lib/tasks/indexmap_tasks.rake +21 -2
  36. data/test/indexmap/correctness_test.rb +153 -0
  37. data/test/indexmap/install_generator_test.rb +36 -4
  38. data/test/indexmap/parser_test.rb +77 -0
  39. data/test/indexmap/pinger/google_test.rb +22 -0
  40. data/test/indexmap/pinger/index_now_test.rb +168 -0
  41. data/test/indexmap/publication_test.rb +280 -0
  42. data/test/indexmap/robots_test.rb +73 -0
  43. data/test/indexmap/sitemaps_controller_test.rb +50 -0
  44. data/test/indexmap/storage_test.rb +27 -0
  45. data/test/indexmap/task_runner_test.rb +24 -0
  46. data/test/indexmap/validator_test.rb +14 -0
  47. data/test/indexmap/writer_test.rb +42 -0
  48. data/test/integration/queue_checks.rb +244 -0
  49. data/test/release_task_test.rb +153 -0
  50. metadata +22 -5
  51. data/lib/indexmap/storage/file.rb +0 -11
@@ -0,0 +1,244 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "minitest/autorun"
4
+ require "indexmap"
5
+ require "timeout"
6
+ require "rbconfig"
7
+
8
+ class QueueChecks < Minitest::Test
9
+ def setup
10
+ Indexmap::HostConfiguration.apply
11
+ PublicPage.delete_all
12
+ 5.times { |id| PublicPage.create!(slug: "page-#{id}", published: true) }
13
+ end
14
+
15
+ def test_good_job_executes_database_projection_and_surfaces_validation_failure
16
+ IntegrationRefreshJob.queue_adapter = :good_job
17
+ job = IntegrationRefreshJob.perform_later
18
+ GoodJob.perform_inline
19
+ assert GoodJob::Job.find(job.provider_job_id).finished_at
20
+ assert_equal 5, Indexmap::Parser.new.entries.size
21
+ previous = Indexmap.configuration.storage.read("sitemap.xml")
22
+ PublicPage.create!(slug: "page-0", published: true)
23
+ IntegrationRefreshJob.perform_later
24
+ assert_raises(Indexmap::ValidationError) { GoodJob.perform_inline }
25
+ assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
26
+ end
27
+
28
+ def test_solid_queue_worker_executes_and_records_failed_generation
29
+ IntegrationRefreshJob.queue_adapter = :solid_queue
30
+ reader, writer = IO.pipe
31
+ pid = Process.spawn({"INDEXMAP_ADAPTER" => "solid_queue"}, RbConfig.ruby, "bin/jobs",
32
+ chdir: Rails.root, out: writer, err: writer, pgroup: true)
33
+ writer.close
34
+ job = IntegrationRefreshJob.perform_later
35
+ wait_for(reader) { SolidQueue::Job.find_by(active_job_id: job.job_id)&.finished_at }
36
+ assert_equal 5, Indexmap::Parser.new.entries.size
37
+ previous = Indexmap.configuration.storage.read("sitemap.xml")
38
+ PublicPage.create!(slug: "page-0", published: true)
39
+ failed = IntegrationRefreshJob.perform_later
40
+ wait_for(reader) { SolidQueue::FailedExecution.joins(:job).where(solid_queue_jobs: {active_job_id: failed.job_id}).exists? }
41
+ assert_equal previous, Indexmap.configuration.storage.read("sitemap.xml")
42
+ ensure
43
+ if pid
44
+ Process.kill("TERM", -pid)
45
+ Timeout.timeout(15) { Process.wait(pid) }
46
+ end
47
+ reader&.close
48
+ writer&.close unless writer&.closed?
49
+ end
50
+
51
+ def test_active_storage_atomic_publication_and_retention
52
+ config = Indexmap.configuration
53
+ config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
54
+ first = Indexmap.create
55
+ previous = config.storage.read("sitemap.xml")
56
+ SitemapArtifact.find_by!(filename: "sitemap.xml").file.blob.update!(created_at: 4.days.ago)
57
+ revision = config.storage.revision("sitemap.xml")
58
+ config.storage.define_singleton_method(:open) do |filename, &block|
59
+ raise "Root body must not be downloaded during activation" if filename == "sitemap.xml"
60
+
61
+ super(filename, &block)
62
+ end
63
+ second = Indexmap.create
64
+ config.storage.singleton_class.remove_method(:open)
65
+ refute_equal first, second
66
+ assert_empty config.storage.prune(root: "sitemap.xml", before: 2.days.ago)
67
+ assert first.all? { |filename| config.storage.exist?(filename) }
68
+ assert_raises(Indexmap::SupersededPublication) do
69
+ Dir.mktmpdir do |directory|
70
+ staged = Indexmap::Storage::Filesystem.new(path: directory)
71
+ staged.write("sitemap.xml", previous)
72
+ config.storage.publish(staged, files: ["sitemap.xml"], root: "sitemap.xml", expected: revision)
73
+ end
74
+ end
75
+ assert Indexmap::Validator.new(configuration: config).validate!
76
+ config.storage.prune(root: "sitemap.xml", before: Time.now.utc + 1)
77
+ assert (first - ["sitemap.xml"]).none? { |filename| config.storage.exist?(filename) }
78
+ assert second.all? { |filename| config.storage.exist?(filename) }
79
+ end
80
+
81
+ def test_active_storage_rejects_stale_builds_without_uploading
82
+ config = active_storage_configuration("stale.xml")
83
+ Indexmap.create(configuration: config)
84
+ expected = config.storage.revision(config.index_filename)
85
+ Indexmap.create(configuration: config)
86
+ blobs = ActiveStorage::Blob.order(:id).pluck(:id)
87
+ assert_raises(Indexmap::SupersededPublication) do
88
+ config.storage.publish(nil, files: [], root: config.index_filename, expected: expected)
89
+ end
90
+ assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
91
+ end
92
+
93
+ def test_active_storage_cleans_partial_uploads_synchronously
94
+ config = active_storage_configuration("failed-upload.xml")
95
+ Indexmap.create(configuration: config)
96
+ previous = config.storage.read(config.index_filename)
97
+ blobs = ActiveStorage::Blob.order(:id).pluck(:id)
98
+ records = SitemapArtifact.order(:id).pluck(:id)
99
+ service = ActiveStorage::Blob.service
100
+ uploaded = []
101
+ service.define_singleton_method(:upload) do |key, io, **options|
102
+ super(key, io, **options)
103
+ uploaded << key
104
+ raise IOError, "upload interrupted after object write" if uploaded.size == 2
105
+ end
106
+ assert_raises(IOError) { Indexmap.create(configuration: config) }
107
+ assert_equal previous, config.storage.read(config.index_filename)
108
+ assert_equal blobs, ActiveStorage::Blob.order(:id).pluck(:id)
109
+ assert_equal records, SitemapArtifact.order(:id).pluck(:id)
110
+ assert uploaded.none? { |key| service.exist?(key) }
111
+ assert Indexmap::Validator.new(configuration: config).validate!
112
+ ensure
113
+ service&.singleton_class&.remove_method(:upload)
114
+ end
115
+
116
+ def test_active_storage_cleans_a_losing_build_without_pruning
117
+ config = active_storage_configuration("race.xml")
118
+ storage = config.storage
119
+ baseline = ActiveStorage::Blob.pluck(:id)
120
+ lost_files = []
121
+ lost_keys = []
122
+ winner = nil
123
+ storage.define_singleton_method(:publish) do |source, **options|
124
+ lost_files.concat(options.fetch(:files).reject { |name| name == options.fetch(:root) })
125
+ source.define_singleton_method(:open) do |filename, &block|
126
+ if filename == options.fetch(:root)
127
+ lost_keys.concat(ActiveStorage::Blob.where.not(id: baseline).pluck(:key))
128
+ competing = Indexmap::Configuration.new
129
+ competing.base_url = config.base_url
130
+ competing.index_filename = config.index_filename
131
+ competing.atomic = true
132
+ competing.sections = config.sections
133
+ competing.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
134
+ winner = Indexmap.create(configuration: competing)
135
+ end
136
+ super(filename, &block)
137
+ end
138
+ super(source, **options)
139
+ end
140
+ assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
141
+ assert_equal winner.size, ActiveStorage::Blob.where.not(id: baseline).count
142
+ assert lost_files.none? { |filename| storage.exist?(filename) }
143
+ assert lost_keys.none? { |key| ActiveStorage::Blob.service.exist?(key) }
144
+ assert winner.all? { |filename| storage.exist?(filename) }
145
+ assert Indexmap::Validator.new(configuration: config).validate!
146
+ end
147
+
148
+ def test_active_storage_does_not_activate_children_deleted_by_pruning
149
+ config = active_storage_configuration("prune-race.xml")
150
+ Indexmap.create(configuration: config)
151
+ previous = config.storage.read(config.index_filename)
152
+ blobs = ActiveStorage::Blob.pluck(:id).sort
153
+ config.storage.define_singleton_method(:publish) do |source, **options|
154
+ storage = self
155
+ source.define_singleton_method(:open) do |filename, &block|
156
+ storage.prune(root: options.fetch(:root), before: Time.now.utc + 1) if filename == options.fetch(:root)
157
+ super(filename, &block)
158
+ end
159
+ super(source, **options)
160
+ end
161
+ assert_raises(Indexmap::SupersededPublication) { Indexmap.create(configuration: config) }
162
+ assert_equal previous, config.storage.read(config.index_filename)
163
+ assert_equal blobs, ActiveStorage::Blob.pluck(:id).sort
164
+ assert Indexmap::Validator.new(configuration: config).validate!
165
+ end
166
+
167
+ def test_active_storage_preserves_a_commit_when_a_host_callback_raises
168
+ config = active_storage_configuration("callback.xml")
169
+ callback = proc do |record|
170
+ if record.filename == "callback.xml" && record.file.attached?
171
+ raise IOError, "host callback failed after commit"
172
+ end
173
+ end
174
+ SitemapArtifact.set_callback(:commit, :after, callback)
175
+ assert_raises(IOError) { Indexmap.create(configuration: config) }
176
+ assert config.storage.exist?(config.index_filename)
177
+ assert Indexmap::Validator.new(configuration: config).validate!
178
+ ensure
179
+ SitemapArtifact.skip_callback(:commit, :after, callback) if callback
180
+ end
181
+
182
+ def test_active_storage_reports_cleanup_failures_and_attempts_remaining_objects
183
+ config = active_storage_configuration("cleanup-error.xml")
184
+ Indexmap.create(configuration: config)
185
+ previous = config.storage.read(config.index_filename)
186
+ service = ActiveStorage::Blob.service
187
+ uploaded = []
188
+ attempted = []
189
+ service.define_singleton_method(:upload) do |key, io, **options|
190
+ super(key, io, **options)
191
+ uploaded << key
192
+ raise IOError, "original upload failure" if uploaded.size == 2
193
+ end
194
+ service.define_singleton_method(:delete) do |key|
195
+ attempted << key
196
+ raise IOError, "cleanup unavailable" if key == uploaded.first
197
+
198
+ super(key)
199
+ end
200
+ error = assert_raises(Indexmap::PublicationCleanupError) { Indexmap.create(configuration: config) }
201
+ assert_equal "original upload failure", error.cause.message
202
+ assert_equal [uploaded.first], error.failures.map { |failure| failure.fetch(:artifact) }
203
+ assert_equal uploaded.sort, attempted.sort
204
+ assert_equal previous, config.storage.read(config.index_filename)
205
+ assert Indexmap::Validator.new(configuration: config).validate!
206
+ ensure
207
+ if service
208
+ service.singleton_class.remove_method(:upload)
209
+ service.singleton_class.remove_method(:delete)
210
+ uploaded&.each { |key| service.delete(key) }
211
+ end
212
+ end
213
+
214
+ def test_active_storage_publishes_atomic_single_file_outputs
215
+ config = active_storage_configuration("single.xml")
216
+ config.format = :single_file
217
+ config.entries = [{loc: "https://example.com/page"}]
218
+ assert_equal ["single.xml"], Indexmap.create(configuration: config)
219
+ assert_equal ["single.xml"], Indexmap.create(configuration: config)
220
+ assert Indexmap::Validator.new(configuration: config).validate!
221
+ end
222
+
223
+ private
224
+
225
+ def active_storage_configuration(root)
226
+ Indexmap.configuration.tap do |config|
227
+ config.index_filename = root
228
+ config.storage = Indexmap::Storage::ActiveStorage.new(model: SitemapArtifact, public_url: config.base_url)
229
+ end
230
+ end
231
+
232
+ def wait_for(reader)
233
+ deadline = Process.clock_gettime(Process::CLOCK_MONOTONIC) + 30
234
+ log = +""
235
+ until yield
236
+ raise "Worker did not reach expected state: #{log[-2000, 2000]}" if Process.clock_gettime(Process::CLOCK_MONOTONIC) > deadline
237
+
238
+ if IO.select([reader], nil, nil, 0.1)
239
+ chunk = reader.read_nonblock(4096, exception: false)
240
+ log << chunk if chunk.is_a?(String)
241
+ end
242
+ end
243
+ end
244
+ end
@@ -2,6 +2,8 @@
2
2
 
3
3
  require "test_helper"
4
4
  require "rake"
5
+ require "open3"
6
+ require "rbconfig"
5
7
 
6
8
  unless respond_to?(:release_version, true)
7
9
  load File.expand_path("../Rakefile", __dir__)
@@ -74,8 +76,159 @@ class ReleaseTaskTest < Minitest::Test
74
76
  )
75
77
  end
76
78
 
79
+ def test_release_preparation_requires_main
80
+ ["release/0.9.0", "master", ""].each do |branch|
81
+ error = assert_raises(ArgumentError) { validate_release_branch!(branch) }
82
+ assert_match(/Prepare releases from main/, error.message)
83
+ end
84
+ assert_nil validate_release_branch!("main")
85
+ end
86
+
87
+ def test_prepare_commits_and_pushes_from_main_with_release_hook_exception
88
+ with_release_repository do |repo, origin|
89
+ before = git!(repo, "rev-parse", "HEAD").strip
90
+ history = File.read(File.join(repo, "CHANGELOG.md"))
91
+ hook = File.join(repo, ".git/hooks/pre-commit")
92
+ File.write(hook, "#!/bin/sh\nexec ruby script/check_commit_branch.rb\n")
93
+ FileUtils.chmod(0o755, hook)
94
+
95
+ stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
96
+ assert status.success?, stdout + stderr
97
+ expected = release_version("minor")
98
+ refute_equal before, git!(repo, "rev-parse", "HEAD").strip
99
+ assert_equal git!(repo, "rev-parse", "HEAD"), git!(origin, "rev-parse", "main")
100
+ assert_equal "CHANGELOG.md\nlib/indexmap/version.rb\n",
101
+ git!(repo, "diff-tree", "--no-commit-id", "--name-only", "-r", "HEAD")
102
+ assert_includes File.read(File.join(repo, "lib/indexmap/version.rb")), expected
103
+ assert_includes File.read(File.join(repo, "CHANGELOG.md")), "## [#{expected}]"
104
+ assert File.read(File.join(repo, "CHANGELOG.md")).end_with?(history[history.index("## [")..])
105
+ message = git!(repo, "log", "-1", "--format=%B")
106
+ assert_includes message, "chore(release): prepare v#{expected}"
107
+ %w[What: Why: Where:].each { |heading| assert_includes message, heading }
108
+ assert_empty git!(repo, "status", "--porcelain")
109
+ assert_empty git!(repo, "tag", "--list")
110
+ assert_empty git!(origin, "tag", "--list")
111
+ assert_includes stdout, "release:tag"
112
+
113
+ stdout, stderr, status = run_release_task(repo, "release:tag")
114
+ assert status.success?, stdout + stderr
115
+ assert_equal git!(repo, "rev-parse", "HEAD"), git!(origin, "rev-parse", "v#{expected}^{}")
116
+ end
117
+ end
118
+
119
+ def test_prepare_keeps_commit_hooks_enabled_and_does_not_push_when_they_fail
120
+ with_release_repository do |repo, origin|
121
+ before = git!(origin, "rev-parse", "main")
122
+ hook = File.join(repo, ".git/hooks/commit-msg")
123
+ File.write(hook, "#!/bin/sh\necho 'commit hook rejected release' >&2\nexit 1\n")
124
+ FileUtils.chmod(0o755, hook)
125
+
126
+ stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
127
+ refute status.success?, stdout + stderr
128
+ assert_includes stderr, "commit hook rejected release"
129
+ assert_equal before, git!(repo, "rev-parse", "HEAD")
130
+ assert_equal before, git!(origin, "rev-parse", "main")
131
+ assert_empty git!(origin, "tag", "--list")
132
+ end
133
+ end
134
+
135
+ def test_branch_hook_rejects_ordinary_main_commits_even_with_release_flag
136
+ with_release_repository do |repo, _origin|
137
+ File.write(File.join(repo, "unrelated.txt"), "unrelated")
138
+ git!(repo, "add", "unrelated.txt")
139
+ [nil, "1"].each do |flag|
140
+ _stdout, stderr, status = Open3.capture3(
141
+ {"INDEXMAP_RELEASE_COMMIT" => flag},
142
+ RbConfig.ruby, "script/check_commit_branch.rb", chdir: repo
143
+ )
144
+ refute status.success?
145
+ assert_includes stderr, "Direct commits to 'main' are not allowed"
146
+ end
147
+ end
148
+ end
149
+
150
+ def test_tag_pushes_only_the_prepared_merged_commit
151
+ with_release_repository do |repo, origin|
152
+ before = git!(repo, "rev-parse", "HEAD").strip
153
+ stdout, stderr, status = run_release_task(repo, "release:tag")
154
+ assert status.success?, stdout + stderr
155
+ assert_equal before, git!(repo, "rev-parse", "HEAD").strip
156
+ assert_equal before, git!(repo, "rev-parse", "v#{Indexmap::VERSION}^{}").strip
157
+ assert_includes git!(origin, "tag", "--list"), "v#{Indexmap::VERSION}"
158
+ assert_empty git!(repo, "status", "--porcelain")
159
+ end
160
+ end
161
+
162
+ def test_prepare_rejects_topic_and_detached_branches_without_changing_files
163
+ with_release_repository do |repo, origin|
164
+ before = git!(repo, "rev-parse", "HEAD").strip
165
+ git!(repo, "checkout", "-b", "release/next")
166
+ ["release/next", before].each do |ref|
167
+ git!(repo, "checkout", ref)
168
+ stdout, stderr, status = run_release_task(repo, "release:prepare[minor]")
169
+ refute status.success?, stdout + stderr
170
+ assert_includes stderr, "Prepare releases from main"
171
+ assert_equal before, git!(repo, "rev-parse", "HEAD").strip
172
+ assert_empty git!(repo, "status", "--porcelain")
173
+ assert_empty git!(origin, "tag", "--list")
174
+ end
175
+ end
176
+ end
177
+
178
+ def test_tag_rejects_main_that_has_not_been_merged_to_origin
179
+ with_release_repository do |repo, origin|
180
+ File.write(File.join(repo, "unmerged.txt"), "unmerged")
181
+ git!(repo, "add", "unmerged.txt")
182
+ git!(repo, "commit", "-m", "test: unmerged change")
183
+ %w[release:prepare[minor] release:tag].each do |task|
184
+ stdout, stderr, status = run_release_task(repo, task)
185
+ refute status.success?, stdout + stderr
186
+ assert_includes stderr, "Local main must match origin/main"
187
+ assert_empty git!(repo, "status", "--porcelain")
188
+ end
189
+ assert_empty git!(repo, "tag", "--list")
190
+ assert_empty git!(origin, "tag", "--list")
191
+ end
192
+ end
193
+
77
194
  private
78
195
 
196
+ def with_release_repository
197
+ Dir.mktmpdir("indexmap-release-test") do |directory|
198
+ repo = File.join(directory, "repo")
199
+ origin = File.join(directory, "origin.git")
200
+ FileUtils.mkdir_p([repo, origin, File.join(repo, "lib/indexmap")])
201
+ git!(origin, "init", "--bare")
202
+ git!(repo, "init", "-b", "main")
203
+ git!(repo, "config", "user.name", "Release Test")
204
+ git!(repo, "config", "user.email", "release@example.test")
205
+ %w[Rakefile indexmap.gemspec CHANGELOG.md cliff.toml lib/indexmap/version.rb script/check_commit_branch.rb].each do |file|
206
+ FileUtils.mkdir_p(File.dirname(File.join(repo, file)))
207
+ FileUtils.cp(File.expand_path("../#{file}", __dir__), File.join(repo, file))
208
+ end
209
+ git!(repo, "add", ".")
210
+ git!(repo, "commit", "-m", "test: prepare release fixture")
211
+ git!(repo, "remote", "add", "origin", origin)
212
+ git!(repo, "push", "-u", "origin", "main")
213
+ yield repo, origin
214
+ end
215
+ end
216
+
217
+ def git!(directory, *args)
218
+ stdout, stderr, status = Open3.capture3("git", *args, chdir: directory)
219
+ raise stderr unless status.success?
220
+
221
+ stdout
222
+ end
223
+
224
+ def run_release_task(directory, task)
225
+ Open3.capture3(
226
+ {"BUNDLE_GEMFILE" => File.expand_path(ENV.fetch("BUNDLE_GEMFILE", "Gemfile"))},
227
+ RbConfig.ruby, "-rrake", "-e", 'load "Rakefile"; Rake.application.invoke_task(ARGV.fetch(0))', task,
228
+ chdir: directory
229
+ )
230
+ end
231
+
79
232
  def local_release_tag_exists?(_tag)
80
233
  @local_release_tag_exists || false
81
234
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: indexmap
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.9.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Fidalgo
@@ -175,9 +175,17 @@ files:
175
175
  - CHANGELOG.md
176
176
  - LICENSE.txt
177
177
  - README.md
178
+ - UPGRADE.md
179
+ - docs/architecture.md
180
+ - docs/deployment.md
181
+ - docs/domain-terms.md
182
+ - docs/dynamic-sitemaps.md
183
+ - docs/search-engine-ping.md
184
+ - docs/storage.md
178
185
  - lib/generators/indexmap/install_generator.rb
179
186
  - lib/generators/indexmap/templates/initializer.rb.tt
180
187
  - lib/indexmap.rb
188
+ - lib/indexmap/artifact.rb
181
189
  - lib/indexmap/configuration.rb
182
190
  - lib/indexmap/creator.rb
183
191
  - lib/indexmap/entry.rb
@@ -188,28 +196,37 @@ files:
188
196
  - lib/indexmap/pinger/base.rb
189
197
  - lib/indexmap/pinger/google.rb
190
198
  - lib/indexmap/pinger/index_now.rb
199
+ - lib/indexmap/publication.rb
191
200
  - lib/indexmap/railtie.rb
201
+ - lib/indexmap/robots.rb
202
+ - lib/indexmap/robots_controller.rb
192
203
  - lib/indexmap/section.rb
204
+ - lib/indexmap/sitemaps_controller.rb
193
205
  - lib/indexmap/storage/active_storage.rb
194
- - lib/indexmap/storage/file.rb
195
206
  - lib/indexmap/storage/filesystem.rb
196
207
  - lib/indexmap/storage/memory.rb
197
208
  - lib/indexmap/task_runner.rb
198
209
  - lib/indexmap/tasks.rb
199
210
  - lib/indexmap/validator.rb
211
+ - lib/indexmap/validator/duplicates.rb
200
212
  - lib/indexmap/version.rb
201
213
  - lib/indexmap/writer.rb
202
214
  - lib/tasks/indexmap_tasks.rake
203
215
  - test/indexmap/configuration_test.rb
216
+ - test/indexmap/correctness_test.rb
204
217
  - test/indexmap/install_generator_test.rb
205
218
  - test/indexmap/lazy_loading_test.rb
206
219
  - test/indexmap/parser_test.rb
207
220
  - test/indexmap/pinger/google_test.rb
208
221
  - test/indexmap/pinger/index_now_test.rb
222
+ - test/indexmap/publication_test.rb
223
+ - test/indexmap/robots_test.rb
224
+ - test/indexmap/sitemaps_controller_test.rb
209
225
  - test/indexmap/storage_test.rb
210
226
  - test/indexmap/task_runner_test.rb
211
227
  - test/indexmap/validator_test.rb
212
228
  - test/indexmap/writer_test.rb
229
+ - test/integration/queue_checks.rb
213
230
  - test/release_task_test.rb
214
231
  - test/test_helper.rb
215
232
  homepage: https://www.ethos-link.com/opensource/indexmap
@@ -219,8 +236,8 @@ metadata:
219
236
  homepage_uri: https://www.ethos-link.com/opensource/indexmap
220
237
  source_code_uri: https://github.com/ethos-link/indexmap
221
238
  bug_tracker_uri: https://github.com/ethos-link/indexmap/issues
222
- changelog_uri: https://github.com/ethos-link/indexmap/blob/main/CHANGELOG.md
223
- documentation_uri: https://github.com/ethos-link/indexmap/blob/main/README.md
239
+ changelog_uri: https://github.com/ethos-link/indexmap/blob/v0.9.0/CHANGELOG.md
240
+ documentation_uri: https://github.com/ethos-link/indexmap/blob/v0.9.0/README.md
224
241
  funding_uri: https://www.reviato.com/
225
242
  github_repo: ssh://github.com/ethos-link/indexmap
226
243
  allowed_push_host: https://rubygems.org
@@ -239,7 +256,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
239
256
  - !ruby/object:Gem::Version
240
257
  version: '0'
241
258
  requirements: []
242
- rubygems_version: 4.0.16
259
+ rubygems_version: 4.0.20
243
260
  specification_version: 4
244
261
  summary: Generate sitemap indexes and child sitemaps with plain Ruby
245
262
  test_files: []
@@ -1,11 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module Indexmap
4
- module Storage
5
- File = Struct.new(:filename, :body, :content_type) do
6
- def basename
7
- ::File.basename(filename)
8
- end
9
- end
10
- end
11
- end