indexmap 0.9.0 → 0.10.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1b4cefe268685e0e7558e8265a10c195369ae24eb117a31f9f880104db68a593
4
- data.tar.gz: c10e968aff2cd046d825723656b29c1f05bde39ce956c55a34e2df49d6293b70
3
+ metadata.gz: 0a88a19e175e22156b0b054041e4d673365eee9089e5fc42347a31f1583dc2e9
4
+ data.tar.gz: 1404999d7723a994cdf5763715dc61c6366a435dace94d9f0796bd82aad69753
5
5
  SHA512:
6
- metadata.gz: 81179a3e30e5c4717e431aa6b4a27c7844bd4c3d57a9676d28e26603b9872a7972a889fc1474c2a4c627f4869b838c8d102b1de3069a061e321e7b6c90489c01
7
- data.tar.gz: 0720dc59bb0adeee8529266aff3c01f69f766a6602eec4253e51918897e9c2123784a2ff8a357f87d1f4e5ade869f2da7f2c36cd6037857aa41e945d7879d2db
6
+ metadata.gz: e610924028d149f000c15f5d0c570232457040aa01b77769c271fe94d4ecc9da4ce067a78167b9c659b892c75f923e6d5b0861838ed0ff7563d7c63753c24673
7
+ data.tar.gz: 5c4986f19bcd67274948b9c4c9a6db4c8a1b7855f3b3c20774179fdc13ee0c300df4c10bf79249d42f358faf4636ea0ba60788eb692fe055a5a987814c6ddfc6
data/CHANGELOG.md CHANGED
@@ -5,6 +5,26 @@ 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.10.0] - 2026-11-04
9
+
10
+
11
+ ### Added
12
+
13
+ - require Ruby 4 and adopt Gemkit
14
+
15
+ - require Ruby 4 and adopt Gemkit ([#27](https://github.com/ethos-link/indexmap/pull/27))
16
+
17
+
18
+
19
+
20
+ ### Fixed
21
+
22
+ - push the version tag during preparation
23
+
24
+ - push the version tag during preparation ([#26](https://github.com/ethos-link/indexmap/pull/26))
25
+
26
+
27
+
8
28
  ## [0.9.0] - 2026-11-04
9
29
 
10
30
 
data/README.md CHANGED
@@ -5,7 +5,7 @@
5
5
 
6
6
  `indexmap` is a small Ruby gem for generating XML sitemaps from explicit Ruby data.
7
7
 
8
- It is designed for Rails apps that want:
8
+ Use it from Ruby or an optional Rails 8 integration for:
9
9
 
10
10
  - deterministic sitemap output
11
11
  - plain Ruby configuration
@@ -14,15 +14,8 @@ 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.
17
+ See [CHANGELOG.md](CHANGELOG.md) for published changes and [UPGRADE.md](UPGRADE.md)
18
+ for host migration steps.
26
19
 
27
20
  ## Installation
28
21
 
@@ -559,103 +552,45 @@ If Google credentials are blank, `indexmap:google:ping` skips Google
559
552
  submission. If neither a configured IndexNow key nor a valid key file is
560
553
  present, `indexmap:index_now:ping` skips IndexNow submission.
561
554
 
562
- ## Development
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
-
571
- Run tests:
572
-
573
- ```bash
574
- bundle exec rake test
575
- ```
576
-
577
- Run lint:
578
-
579
- ```bash
580
- bundle exec rake standard
581
- ```
582
-
583
- Run the full default task:
584
-
585
- ```bash
586
- bundle exec rake
587
- ```
588
-
589
- Tests generate a coverage report automatically.
590
-
591
- Note: `Gemfile.lock` is intentionally not tracked for this gem, following normal Ruby library conventions.
555
+ ## License
592
556
 
593
- ### Git hooks
557
+ MIT License, see [LICENSE.txt](LICENSE.txt)
594
558
 
595
- We use [lefthook](https://lefthook.dev/) with the Ruby
596
- [commitlint](https://github.com/arandilopez/commitlint) gem to enforce
597
- Conventional Commits on every commit. We also use
598
- [Standard Ruby](https://standardrb.com/) to keep code style consistent. CI
599
- validates commit messages, Standard Ruby, tests, and git-cliff changelog
600
- generation on pull requests and pushes to main/master.
559
+ ## Development and releases
601
560
 
602
- Run the hook installer once per clone:
561
+ Shared tooling uses published [Ethos Gemkit](https://github.com/ethos-link/ethos-gemkit).
562
+ Keep the exact Gemfile version and workflow commit pins on the same release.
603
563
 
604
- ```bash
564
+ ```sh
565
+ bundle install
605
566
  bundle exec lefthook install
567
+ bundle exec rake verify
606
568
  ```
607
569
 
608
- ## Release
609
-
610
- Releases are tag-driven and published by GitHub Actions to RubyGems.
611
- Local release commands never publish directly.
612
-
613
- Install [git-cliff](https://git-cliff.org/) locally before preparing a
614
- release. The release task prepends the next `CHANGELOG.md` section from
615
- Conventional Commits.
570
+ The PostgreSQL integration suite covers Active Storage, GoodJob and Solid Queue:
616
571
 
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.
620
-
621
- Then run one of:
622
-
623
- ```bash
624
- bundle exec rake 'release:prepare[patch]'
625
- bundle exec rake 'release:prepare[minor]'
626
- bundle exec rake 'release:prepare[major]'
627
- bundle exec rake 'release:prepare[0.9.0]'
572
+ ```sh
573
+ BUNDLE_GEMFILE=gemfiles/integration.gemfile bundle install
574
+ INDEXMAP_DATABASE_URL=postgresql://postgres@localhost:5432/indexmap_test \
575
+ BUNDLE_GEMFILE=gemfiles/integration.gemfile bundle exec ruby script/integration
628
576
  ```
629
577
 
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:
578
+ Use a disposable `indexmap_test` database; the integration suite recreates it.
639
579
 
640
- ```bash
641
- bundle exec rake release:tag
642
- ```
643
-
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.
648
-
649
- The `Release` workflow then runs tests, publishes the gem to RubyGems,
650
- and creates the GitHub release from the changelog entry.
651
-
652
- ## License
653
-
654
- MIT License, see [LICENSE.txt](LICENSE.txt)
580
+ From clean, current `main`, run `bundle exec rake 'release:prepare[patch]'`
581
+ (or `minor`, `major`, or an explicit version). Review and merge the generated
582
+ release PR to publish through Actions. Shared prerequisites, registry credentials
583
+ and failed-job recovery are in the [Gemkit release guide](https://github.com/ethos-link/ethos-gemkit#releasing-a-gem).
655
584
 
656
- ## About
585
+ ## Built by Ethos Link
657
586
 
658
- Made by the team at [Ethos Link](https://www.ethos-link.com) — practical software for growing businesses. We build tools for hospitality operators who need clear workflows, fast onboarding, and real human support.
587
+ [Ethos Link](https://www.ethos-link.com) builds business software and integrations
588
+ that replace manual handoffs with connected workflows. We help teams automate
589
+ routine work, use their data and improve the processes their business runs on.
659
590
 
660
- We also build [Reviato](https://www.reviato.com), “Capture. Interpret. Act.”.
661
- Turn guest feedback into clear next steps for your team. Collect private appraisals, spot patterns across reviews, and act before small issues turn into public ones.
591
+ - [Reviato](https://www.reviato.com): Read public review trends and collect
592
+ private customer feedback, so your team can decide what to improve.
593
+ - [EthosPlaces](https://www.ethos.place): Give customers the details they need to
594
+ choose and visit your business, from opening hours to directions.
595
+ - [Nuntio](https://www.nuntiohq.com): Draft, review and schedule social posts in
596
+ one workspace, then track each publishing result.
data/UPGRADE.md CHANGED
@@ -3,7 +3,19 @@
3
3
  Use this guide when upgrading an application that already generates, validates,
4
4
  or pings sitemaps with `indexmap`.
5
5
 
6
- ## Unreleased
6
+ ## 0.10.0
7
+
8
+ ### Shared development and release tooling
9
+
10
+ Use Ruby 4 and published Gemkit 0.2.0. Install the bundle and hooks, then run the
11
+ project's verification command from the README. Prepare releases from clean,
12
+ current main; merging the generated release PR publishes through Actions.
13
+ See the [Gemkit upgrade guide](https://github.com/ethos-link/ethos-gemkit/blob/main/UPGRADE.md)
14
+ for workflow permissions and registry setup.
15
+
16
+ Rails integration, where provided, requires host-installed Rails 8. Plain Ruby
17
+ entry points remain available without Railties.
18
+
7
19
 
8
20
  ### Host loader and rendered artifacts
9
21
 
@@ -73,18 +85,6 @@ unattached-blob cleanup, and working attachment purge jobs. A
73
85
  its cause. Retain those private diagnostics; a failed service deletion can require
74
86
  cleanup by object key even when Active Storage already removed the blob row.
75
87
 
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
88
 
89
89
  ### Task loading, parsing, and documentation
90
90
 
@@ -3,7 +3,6 @@
3
3
  require "net/http"
4
4
  require "nokogiri"
5
5
  require "uri"
6
- require "pathname"
7
6
  require "stringio"
8
7
 
9
8
  module Indexmap
@@ -253,7 +252,7 @@ module Indexmap
253
252
 
254
253
  if loc.start_with?("http://", "https://")
255
254
  path = URI.parse(loc).path
256
- (path.nil? || path.empty?) ? "/" : path
255
+ path.to_s.empty? ? "/" : path
257
256
  elsif loc.start_with?("/")
258
257
  loc
259
258
  else
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails"
4
+ require "rails/railtie"
5
+ require "indexmap"
6
+ require "indexmap/railtie"
@@ -1,5 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require "rails"
4
+ require "rails/railtie"
5
+
6
+ unless Gem::Requirement.new(">= 8.0", "< 9").satisfied_by?(Gem.loaded_specs.fetch("railties").version)
7
+ raise LoadError, "indexmap integration requires Rails 8.0 or newer within Rails 8"
8
+ end
9
+
3
10
  module Indexmap
4
11
  class Railtie < Rails::Railtie
5
12
  rake_tasks do
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Indexmap
4
- VERSION = "0.9.0"
4
+ VERSION = "0.10.0"
5
5
  end
@@ -163,7 +163,7 @@ class IndexmapPublicationTest < Minitest::Test
163
163
  assert IO.select([reader], nil, nil, 5), "build did not reach destination staging"
164
164
  reader.read(7)
165
165
  staged = Dir.glob(File.join(directory, ".indexmap-builds/*/build-*")).fetch(0)
166
- File.utime(Time.at(0), Time.at(0), staged)
166
+ File.utime(Time.at(0).getlocal, Time.at(0).getlocal, staged)
167
167
  assert_empty config.storage.prune(root: "sitemap.xml", before: Time.now.utc - 3600)
168
168
  assert File.directory?(staged)
169
169
  Process.kill("KILL", pid)
@@ -197,7 +197,7 @@ class IndexmapPublicationTest < Minitest::Test
197
197
  assert_raises(Indexmap::SupersededPublication) do
198
198
  config.storage.publish(nil, files: [], root: "sitemap.xml", expected: expected)
199
199
  end
200
- config.storage.list(suffix: ".xml").each { |name| File.utime(Time.at(0), Time.at(0), File.join(directory, name)) }
200
+ config.storage.list(suffix: ".xml").each { |name| File.utime(Time.at(0).getlocal, Time.at(0).getlocal, File.join(directory, name)) }
201
201
  deleted = config.storage.prune(root: "sitemap.xml", before: Time.now.utc - 3600)
202
202
  assert (old - ["sitemap.xml"]).all? { |file| deleted.include?(file) }
203
203
  assert other.all? { |file| config.storage.exist?(file) }
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "test_helper"
4
+ require "open3"
5
+ require "rbconfig"
6
+
7
+ class IndexmapRailsTest < Minitest::Test
8
+ def test_core_stays_independent_and_explicit_rails_integration_loads_after_it
9
+ script = <<~RUBY
10
+ require "indexmap"
11
+ abort "Core loaded Railties" if $LOADED_FEATURES.any? { |file| file.end_with?("/rails/railtie.rb") }
12
+ require "indexmap/rails"
13
+ abort "Railtie missing" unless Indexmap::Railtie < Rails::Railtie
14
+ app = Class.new(Rails::Application)
15
+ app.config.eager_load = false
16
+ app.load_tasks
17
+ abort "Sitemap task missing" unless Rake::Task.task_defined?("indexmap:sitemap:create")
18
+ RUBY
19
+ output, error, status = Open3.capture3(RbConfig.ruby, "-e", script)
20
+ assert status.success?, output + error
21
+ end
22
+ 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.9.0
4
+ version: 0.10.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paulo Fidalgo
@@ -16,14 +16,20 @@ dependencies:
16
16
  requirements:
17
17
  - - ">="
18
18
  - !ruby/object:Gem::Version
19
- version: '7.1'
19
+ version: '8.0'
20
+ - - "<"
21
+ - !ruby/object:Gem::Version
22
+ version: '9'
20
23
  type: :runtime
21
24
  prerelease: false
22
25
  version_requirements: !ruby/object:Gem::Requirement
23
26
  requirements:
24
27
  - - ">="
25
28
  - !ruby/object:Gem::Version
26
- version: '7.1'
29
+ version: '8.0'
30
+ - - "<"
31
+ - !ruby/object:Gem::Version
32
+ version: '9'
27
33
  - !ruby/object:Gem::Dependency
28
34
  name: faraday
29
35
  requirement: !ruby/object:Gem::Requirement
@@ -80,20 +86,6 @@ dependencies:
80
86
  - - ">="
81
87
  - !ruby/object:Gem::Version
82
88
  version: '1.16'
83
- - !ruby/object:Gem::Dependency
84
- name: railties
85
- requirement: !ruby/object:Gem::Requirement
86
- requirements:
87
- - - ">="
88
- - !ruby/object:Gem::Version
89
- version: '7.1'
90
- type: :runtime
91
- prerelease: false
92
- version_requirements: !ruby/object:Gem::Requirement
93
- requirements:
94
- - - ">="
95
- - !ruby/object:Gem::Version
96
- version: '7.1'
97
89
  - !ruby/object:Gem::Dependency
98
90
  name: minitest
99
91
  requirement: !ruby/object:Gem::Requirement
@@ -108,20 +100,6 @@ dependencies:
108
100
  - - "~>"
109
101
  - !ruby/object:Gem::Version
110
102
  version: '5.0'
111
- - !ruby/object:Gem::Dependency
112
- name: rake
113
- requirement: !ruby/object:Gem::Requirement
114
- requirements:
115
- - - "~>"
116
- - !ruby/object:Gem::Version
117
- version: '13.0'
118
- type: :development
119
- prerelease: false
120
- version_requirements: !ruby/object:Gem::Requirement
121
- requirements:
122
- - - "~>"
123
- - !ruby/object:Gem::Version
124
- version: '13.0'
125
103
  - !ruby/object:Gem::Dependency
126
104
  name: simplecov
127
105
  requirement: !ruby/object:Gem::Requirement
@@ -136,20 +114,6 @@ dependencies:
136
114
  - - "~>"
137
115
  - !ruby/object:Gem::Version
138
116
  version: '0.22'
139
- - !ruby/object:Gem::Dependency
140
- name: standard
141
- requirement: !ruby/object:Gem::Requirement
142
- requirements:
143
- - - "~>"
144
- - !ruby/object:Gem::Version
145
- version: '1.0'
146
- type: :development
147
- prerelease: false
148
- version_requirements: !ruby/object:Gem::Requirement
149
- requirements:
150
- - - "~>"
151
- - !ruby/object:Gem::Version
152
- version: '1.0'
153
117
  - !ruby/object:Gem::Dependency
154
118
  name: webmock
155
119
  requirement: !ruby/object:Gem::Requirement
@@ -197,6 +161,7 @@ files:
197
161
  - lib/indexmap/pinger/google.rb
198
162
  - lib/indexmap/pinger/index_now.rb
199
163
  - lib/indexmap/publication.rb
164
+ - lib/indexmap/rails.rb
200
165
  - lib/indexmap/railtie.rb
201
166
  - lib/indexmap/robots.rb
202
167
  - lib/indexmap/robots_controller.rb
@@ -220,6 +185,7 @@ files:
220
185
  - test/indexmap/pinger/google_test.rb
221
186
  - test/indexmap/pinger/index_now_test.rb
222
187
  - test/indexmap/publication_test.rb
188
+ - test/indexmap/rails_test.rb
223
189
  - test/indexmap/robots_test.rb
224
190
  - test/indexmap/sitemaps_controller_test.rb
225
191
  - test/indexmap/storage_test.rb
@@ -227,7 +193,6 @@ files:
227
193
  - test/indexmap/validator_test.rb
228
194
  - test/indexmap/writer_test.rb
229
195
  - test/integration/queue_checks.rb
230
- - test/release_task_test.rb
231
196
  - test/test_helper.rb
232
197
  homepage: https://www.ethos-link.com/opensource/indexmap
233
198
  licenses:
@@ -236,8 +201,8 @@ metadata:
236
201
  homepage_uri: https://www.ethos-link.com/opensource/indexmap
237
202
  source_code_uri: https://github.com/ethos-link/indexmap
238
203
  bug_tracker_uri: https://github.com/ethos-link/indexmap/issues
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
204
+ changelog_uri: https://github.com/ethos-link/indexmap/blob/v0.10.0/CHANGELOG.md
205
+ documentation_uri: https://github.com/ethos-link/indexmap/blob/v0.10.0/README.md
241
206
  funding_uri: https://www.reviato.com/
242
207
  github_repo: ssh://github.com/ethos-link/indexmap
243
208
  allowed_push_host: https://rubygems.org
@@ -249,7 +214,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
249
214
  requirements:
250
215
  - - ">="
251
216
  - !ruby/object:Gem::Version
252
- version: 3.2.0
217
+ version: '4.0'
253
218
  required_rubygems_version: !ruby/object:Gem::Requirement
254
219
  requirements:
255
220
  - - ">="
@@ -1,239 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "test_helper"
4
- require "rake"
5
- require "open3"
6
- require "rbconfig"
7
-
8
- unless respond_to?(:release_version, true)
9
- load File.expand_path("../Rakefile", __dir__)
10
- end
11
-
12
- class ReleaseTaskTest < Minitest::Test
13
- def test_release_version_increments_patch_from_current_version
14
- major, minor, patch = Indexmap::VERSION.split(".").map(&:to_i)
15
-
16
- assert_equal "#{major}.#{minor}.#{patch + 1}", release_version("patch")
17
- end
18
-
19
- def test_release_version_accepts_explicit_semantic_version
20
- assert_equal "0.3.0", release_version("0.3.0")
21
- end
22
-
23
- def test_validate_release_version_rejects_current_version
24
- error = assert_raises(ArgumentError) do
25
- validate_release_version!("0.2.7", "0.2.7")
26
- end
27
-
28
- assert_equal(
29
- "Release version 0.2.7 must be newer than current version 0.2.7.",
30
- error.message
31
- )
32
- end
33
-
34
- def test_validate_release_version_rejects_existing_local_tag
35
- @local_release_tag_exists = true
36
- @remote_release_tag_exists = false
37
-
38
- error = assert_raises(ArgumentError) do
39
- validate_release_version!("0.2.8", "0.2.7")
40
- end
41
-
42
- assert_equal "Release tag v0.2.8 already exists locally.", error.message
43
- end
44
-
45
- def test_validate_release_version_rejects_existing_remote_tag
46
- @local_release_tag_exists = false
47
- @remote_release_tag_exists = true
48
-
49
- error = assert_raises(ArgumentError) do
50
- validate_release_version!("0.2.8", "0.2.7")
51
- end
52
-
53
- assert_equal "Release tag v0.2.8 already exists on origin.", error.message
54
- end
55
-
56
- def test_remote_release_tag_command_asks_git_to_fail_when_no_tag_matches
57
- assert_equal(
58
- "git ls-remote --exit-code --tags origin refs/tags/v0.2.8",
59
- remote_release_tag_command("v0.2.8")
60
- )
61
- end
62
-
63
- def test_changelog_command_prepends_the_next_release
64
- assert_equal(
65
- [
66
- "git-cliff",
67
- "-c",
68
- "cliff.toml",
69
- "--unreleased",
70
- "--tag",
71
- "v0.2.8",
72
- "--prepend",
73
- "CHANGELOG.md"
74
- ],
75
- changelog_command("0.2.8")
76
- )
77
- end
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
-
194
- private
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
-
232
- def local_release_tag_exists?(_tag)
233
- @local_release_tag_exists || false
234
- end
235
-
236
- def remote_release_tag_exists?(_tag)
237
- @remote_release_tag_exists || false
238
- end
239
- end