semverve 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 9f0366a11f400a32928725b5f954c9f71e3659a75cb5757cf268a3efc018659c
4
- data.tar.gz: 870ae675510264ea80bc7a44dbe86efd5248a4020a98f3884b3123cb1f68bea6
3
+ metadata.gz: 436e6dece23a174761d954e1c1efc74d95d68b8b47d82f876bb5b785eb204748
4
+ data.tar.gz: 62e9b4496586062f8e03f71d0838ddd510e5e42faa200d10fbef2560f214fa4c
5
5
  SHA512:
6
- metadata.gz: e945ab75c4b9b4eaf3d232ccc5f0be75b7c024a37e9f5488c2a9d31cefe1bfbd1bc6105b27f1b9d51d600d393ea2ca5b3a51445d9bf712ef279a362192ba5166
7
- data.tar.gz: 5dcdc6bb2287825fe348c84c289878f76b06e2b453257848990dc1c9cf42a3581dbafc2fd65e04d88ed2861a2f2c17496ce865d929823c5f8bd36e4e8acff13e
6
+ metadata.gz: cc055ab321689871de38a8fc0276a8cc85011ea9c7765c7844d97c71962bba2765cd867cd9dbb96236b11ea695edc63e8289c087fd5a90482ab1ea376d82981a
7
+ data.tar.gz: 8a4cbb86d9548b4aeeedf94bd883e300532a6d4c062dc1c1c1170d412ae5bb94a4fe50bdfbc29098482a51632a194ef32be93cee35ef8a5219d77f5957d54c98
data/.gitignore CHANGED
@@ -1,3 +1,4 @@
1
1
  semverve-*.gem
2
2
  coverage/*
3
3
  docs/*
4
+ _config.yml
data/CONTRIBUTING.md CHANGED
@@ -123,7 +123,7 @@ bundle exec rake semverve:current
123
123
  bundle exec rake semverve:increment:patch
124
124
  bundle exec rake semverve:increment:minor
125
125
  bundle exec rake semverve:increment:major
126
- bundle exec rake semverve:set VERSION=x.y.z
126
+ bundle exec rake 'semverve:set[x.y.z]'
127
127
  bundle exec rake semverve:check
128
128
  bundle exec rake semverve:fix
129
129
  ```
data/Gemfile.lock CHANGED
@@ -1,7 +1,7 @@
1
1
  PATH
2
2
  remote: .
3
3
  specs:
4
- semverve (0.1.0)
4
+ semverve (0.2.0)
5
5
 
6
6
  GEM
7
7
  remote: https://rubygems.org/
@@ -84,10 +84,10 @@ PLATFORMS
84
84
  DEPENDENCIES
85
85
  rake (~> 13.0, >= 13.0.1)
86
86
  rdoc
87
+ semverve!
87
88
  simplecov
88
89
  standard (~> 1.54.0)
89
90
  test-unit (~> 3.3, >= 3.3.5)
90
- semverve!
91
91
 
92
92
  BUNDLED WITH
93
93
  4.0.10
data/README.md CHANGED
@@ -1,4 +1,9 @@
1
1
  # Semverve
2
+ [![Build Status](https://img.shields.io/endpoint.svg?url=https%3A%2F%2Factions-badge.atrox.dev%2Fevanthegrayt%2Fsemverve%2Fbadge%3Fref%3Dmaster&style=flat)](https://actions-badge.atrox.dev/evanthegrayt/semverve/goto?ref=master)
3
+ [![Language: Ruby](https://img.shields.io/static/v1?label=language&message=Ruby&color=CC342D&style=flat&logo=ruby&logoColor=CC342D)](https://www.ruby-lang.org/)
4
+ [![Gem Version](https://img.shields.io/gem/v/semverve.svg)](https://rubygems.org/gems/semverve)
5
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
6
+
2
7
  Rake tasks for handling the tedium surrounding maintaining a version number in
3
8
  your Ruby project, with gusto!
4
9
 
@@ -26,6 +31,9 @@ tasks can update safe references and run `bundle lock` for generated lockfile
26
31
  drift. Specific findings can be skipped with magic comments, similar to RuboCop
27
32
  and RDoc.
28
33
 
34
+ You can view the documentation
35
+ [here](https://evanthegrayt.github.io/semverve/).
36
+
29
37
  ## Installation
30
38
  Add the gem to your Gemfile:
31
39
 
@@ -47,7 +55,7 @@ rake semverve:increment:patch
47
55
  rake semverve:increment:minor
48
56
  rake semverve:increment:major
49
57
  rake semverve:generate
50
- rake semverve:set VERSION=1.2.3
58
+ rake 'semverve:set[1.2.3]'
51
59
  rake semverve:check
52
60
  rake semverve:fix
53
61
  rake semverve:check:references
@@ -56,6 +64,8 @@ rake semverve:check:code
56
64
  rake semverve:fix:code
57
65
  rake semverve:check:metadata
58
66
  rake semverve:fix:metadata
67
+ rake semverve:check:rubygems
68
+ rake semverve:check:release
59
69
  ```
60
70
 
61
71
  ## Configuration
@@ -81,6 +91,8 @@ Semverve.configure do |config|
81
91
  config.version_file = "lib/my_gem/version.rb"
82
92
  config.module_name = "MyGem"
83
93
  config.version_checks = [:doc_references, :code_references, :metadata]
94
+ config.release_checks = [:rubygems]
95
+ config.rubygems_host = "https://rubygems.org"
84
96
  config.version_code_reference_files.append("lib/**/*.rb")
85
97
  config.version_doc_reference_files.append("doc/**/*.md")
86
98
  config.version_reference_mode = :non_current
@@ -95,6 +107,8 @@ Semverve.configure do |config|
95
107
  config.bundle_lock = false
96
108
  config.root = Dir.pwd
97
109
  config.version_checks = [:doc_references, :code_references, :metadata]
110
+ config.release_checks = []
111
+ config.rubygems_host = "https://rubygems.org"
98
112
  config.version_reference_mode = :older
99
113
  config.version_code_reference_files = Rake::FileList[]
100
114
  config.version_code_reference_pattern =
@@ -111,6 +125,22 @@ end
111
125
  The empty `version_code_reference_files` default only applies to arbitrary code
112
126
  literal scanning. `rake semverve:check` still checks the resolved `.gemspec`
113
127
  version and matching `Gemfile.lock` entry through its default metadata check.
128
+ Release checks are empty by default because they may make network requests and
129
+ are intended for release pipelines rather than every local or pull-request run.
130
+
131
+ Semverve tasks use Rake task arguments for values:
132
+
133
+ ```sh
134
+ rake 'semverve:set[1.2.3]'
135
+ rake 'semverve:generate[1.0.0,simple]'
136
+ rake 'semverve:generate[force]'
137
+ ```
138
+
139
+ Quote task invocations that include square brackets. Shells such as zsh may
140
+ otherwise treat brackets as glob patterns before Rake sees them. Flag syntax
141
+ such as `rake semverve:set --version 1.2.3` is not used because `--version` is
142
+ already a Rake option; Semverve stays within Rake's native argument syntax
143
+ instead.
114
144
 
115
145
  The gem name, module name, and version-file path are inferred by default:
116
146
 
@@ -138,48 +168,6 @@ Semverve::Task.new do |config|
138
168
  end
139
169
  ```
140
170
 
141
- ## Publishing generated docs
142
- Semverve also includes an optional GitHub Pages publishing task for generated
143
- documentation. It builds docs on your current branch, opens the publishing
144
- branch in a temporary Git worktree, copies the generated docs there, commits any
145
- changes, and pushes the publishing branch.
146
-
147
- Add this to your Rakefile:
148
-
149
- ```ruby
150
- require "semverve/docs_publisher/task"
151
-
152
- Semverve::DocsPublisher::Task.new
153
- ```
154
-
155
- This defines:
156
-
157
- ```text
158
- rake docs:publish
159
- rake docs:publish:dry_run
160
- ```
161
-
162
- The defaults are equivalent to:
163
-
164
- ```ruby
165
- Semverve::DocsPublisher::Task.new do |task|
166
- task.root = Dir.pwd
167
- task.build_task = "rerdoc"
168
- task.source_dir = "docs"
169
- task.target_dir = "docs"
170
- task.branch = "gh-pages"
171
- task.remote = "origin"
172
- task.commit_message = "Update generated documentation"
173
- task.allow_dirty = false
174
- task.push = true
175
- task.output = $stdout
176
- end
177
- ```
178
-
179
- `docs:publish` requires a clean working tree by default. Set
180
- `task.allow_dirty = true` only when you intentionally want to publish generated
181
- documentation from uncommitted source changes.
182
-
183
171
  ## Rails apps
184
172
  Rails applications do not need gem-style version files, but an application
185
173
  version can still be useful for release notes, support/debug screens,
@@ -261,13 +249,21 @@ rake semverve:generate
261
249
  Generate a specific version or format:
262
250
 
263
251
  ```sh
264
- rake semverve:generate VERSION=1.0.0 FORMAT=simple
252
+ rake 'semverve:generate[1.0.0,simple]'
265
253
  ```
266
254
 
267
255
  Generation fails if the target file already exists. To replace it:
268
256
 
269
257
  ```sh
270
- rake semverve:generate FORCE=true
258
+ rake 'semverve:generate[force]'
259
+ ```
260
+
261
+ `semverve:generate` accepts optional `version` and `format` arguments. Add a
262
+ `force` token to overwrite an existing version file:
263
+
264
+ ```sh
265
+ rake 'semverve:generate[1.0.0,force]'
266
+ rake 'semverve:generate[1.0.0,simple,force]'
271
267
  ```
272
268
 
273
269
  ## Incrementing
@@ -293,7 +289,7 @@ your gem's version in `Gemfile.lock`.
293
289
  Set an exact version without incrementing:
294
290
 
295
291
  ```sh
296
- rake semverve:set VERSION=1.2.3
292
+ rake 'semverve:set[1.2.3]'
297
293
  ```
298
294
 
299
295
  Successful updates print the version change:
@@ -325,6 +321,9 @@ Run every version check with:
325
321
  rake semverve:check
326
322
  ```
327
323
 
324
+ This task is designed for normal CI. It uses local project files, prints
325
+ parseable findings, and exits non-zero when it finds drift.
326
+
328
327
  By default, this checks:
329
328
 
330
329
  - README version references, plus any configured docs or comment files
@@ -430,6 +429,13 @@ same line or the preceding nonblank line.
430
429
  This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->
431
430
  ```
432
431
 
432
+ Audit ignored references by setting `SEMVERVE_REPORT_IGNORED=true` when running
433
+ check tasks:
434
+
435
+ ```sh
436
+ SEMVERVE_REPORT_IGNORED=true rake semverve:check
437
+ ```
438
+
433
439
  ### Code version literals
434
440
  Code scanning is opt-in to avoid false positives. This is for arbitrary project
435
441
  code, not gem metadata. The default is:
@@ -463,6 +469,13 @@ APP_VERSION = "1.2.2"
463
469
  spec.version = "1.2.2"
464
470
  ```
465
471
 
472
+ Ignore an intentional code literal with `semverve:ignore-version-reference` on
473
+ the same line or the preceding nonblank line.
474
+
475
+ Set `SEMVERVE_REPORT_IGNORED=true` with `semverve:check` or
476
+ `semverve:check:code` to report ignored stale literals without changing
477
+ `semverve:fix` behavior.
478
+
466
479
  The default pattern is Ruby-oriented. Semverve does not inspect file extensions
467
480
  or parse other languages for code literals; non-Ruby files are scanned as plain
468
481
  text with the same pattern. If a JavaScript, Python, or other source file uses a
@@ -536,6 +549,77 @@ end
536
549
  `rake semverve:fix:metadata` updates safe literal gemspec assignments and
537
550
  runs `bundle lock` when the lockfile has drifted.
538
551
 
552
+ ## Checking release readiness
553
+ Release checks are separate from `rake semverve:check`. They are useful in CI,
554
+ but they are meant for release workflows, tag builds, or pre-publish jobs rather
555
+ than every pull request.
556
+
557
+ Enable the RubyGems published-version check:
558
+
559
+ ```ruby
560
+ Semverve.configure do |config|
561
+ config.release_checks = [:rubygems]
562
+ end
563
+ ```
564
+
565
+ Then run:
566
+
567
+ ```sh
568
+ rake semverve:check:release
569
+ ```
570
+
571
+ With `:rubygems` enabled, `semverve:check:release` asks the configured
572
+ RubyGems-compatible host whether the current version already exists. If it does,
573
+ the task exits non-zero with a message like:
574
+
575
+ ```text
576
+ my_gem 1.2.3 already exists on https://rubygems.org.
577
+ ```
578
+
579
+ If all configured release checks pass, it prints:
580
+
581
+ ```text
582
+ Release checks passed.
583
+ ```
584
+
585
+ You can also run the RubyGems check directly without changing
586
+ `config.release_checks`:
587
+
588
+ ```sh
589
+ rake semverve:check:rubygems
590
+ ```
591
+
592
+ When the current version is not published, the focused task prints:
593
+
594
+ ```text
595
+ my_gem 1.2.3 is not published on https://rubygems.org.
596
+ ```
597
+
598
+ Use `config.rubygems_host` for a private RubyGems-compatible server:
599
+
600
+ ```ruby
601
+ Semverve.configure do |config|
602
+ config.release_checks = [:rubygems]
603
+ config.rubygems_host = "https://gems.example.com"
604
+ end
605
+ ```
606
+
607
+ The published-version check treats a missing gem as unpublished. It fails closed
608
+ on registry errors, malformed responses, and network failures because release
609
+ pipelines should not silently publish after an inconclusive preflight.
610
+
611
+ For ordinary CI, keep using the local checks:
612
+
613
+ ```sh
614
+ bundle exec rake test semverve:check
615
+ ```
616
+
617
+ For release CI, run the release check before building or pushing:
618
+
619
+ ```sh
620
+ bundle exec rake semverve:check:release build
621
+ ```
622
+
539
623
  ## Reporting Bugs and Requesting Features
540
624
  If you have an idea or find a bug, please [create an
541
625
  issue](https://github.com/evanthegrayt/semverve/issues/new). Just make sure
@@ -16,6 +16,12 @@ module Semverve
16
16
  # @return [Array<Symbol>]
17
17
  VALID_VERSION_CHECKS = [:doc_references, :code_references, :metadata].freeze
18
18
 
19
+ ##
20
+ # Release-readiness surfaces supported by release check tasks.
21
+ #
22
+ # @return [Array<Symbol>]
23
+ VALID_RELEASE_CHECKS = [:rubygems].freeze
24
+
19
25
  ##
20
26
  # Default Ruby pattern for code version literals that are safe to rewrite.
21
27
  #
@@ -58,12 +64,24 @@ module Semverve
58
64
  # @return [Symbol, String, nil]
59
65
  attr_reader :preset
60
66
 
67
+ ##
68
+ # Release-readiness surfaces run by the release check task.
69
+ #
70
+ # @return [Array<Symbol, String>]
71
+ attr_reader :release_checks
72
+
61
73
  ##
62
74
  # Project root used when inferring metadata and expanding paths.
63
75
  #
64
76
  # @return [String, nil]
65
77
  attr_reader :root
66
78
 
79
+ ##
80
+ # RubyGems-compatible host used for published-version checks.
81
+ #
82
+ # @return [String]
83
+ attr_accessor :rubygems_host
84
+
67
85
  ##
68
86
  # Explicit version-file path relative to the project root.
69
87
  #
@@ -110,6 +128,7 @@ module Semverve
110
128
  @command_runner = ->(command) { system(command) }
111
129
  @format = :module
112
130
  @preset = nil
131
+ @rubygems_host = "https://rubygems.org"
113
132
  @version_code_reference_files = Rake::FileList[]
114
133
  self.version_code_reference_pattern = DEFAULT_VERSION_CODE_REFERENCE_PATTERN
115
134
  @version_doc_reference_files = Rake::FileList["README*", "**/README*"].exclude(
@@ -119,6 +138,7 @@ module Semverve
119
138
  "vendor/**/*"
120
139
  )
121
140
  @version_reference_mode = :older
141
+ self.release_checks = []
122
142
  self.version_checks = VALID_VERSION_CHECKS
123
143
  end
124
144
 
@@ -135,7 +155,9 @@ module Semverve
135
155
  format: normalized_format,
136
156
  gem_name: metadata.gem_name,
137
157
  module_name: metadata.module_name,
158
+ release_checks: normalized_release_checks,
138
159
  root: expanded_root,
160
+ rubygems_host: rubygems_host,
139
161
  version_file: metadata.version_file,
140
162
  version_checks: normalized_version_checks,
141
163
  version_code_reference_files: version_code_reference_files,
@@ -186,6 +208,16 @@ module Semverve
186
208
  @preset = preset.nil? ? nil : Presets.fetch(preset).name
187
209
  end
188
210
 
211
+ ##
212
+ # Sets the release-readiness surfaces run by the release check task.
213
+ #
214
+ # @param [Array<Symbol, String>] checks
215
+ #
216
+ # @return [Array<Symbol>]
217
+ def release_checks=(checks)
218
+ @release_checks = normalize_release_checks(checks)
219
+ end
220
+
189
221
  ##
190
222
  # Sets the project root used when expanding paths.
191
223
  #
@@ -243,6 +275,14 @@ module Semverve
243
275
  resolved_value(:format).to_sym
244
276
  end
245
277
 
278
+ ##
279
+ # Configured release checks normalized for lookup.
280
+ #
281
+ # @return [Array<Symbol>]
282
+ def normalized_release_checks
283
+ normalize_release_checks(release_checks)
284
+ end
285
+
246
286
  ##
247
287
  # Resolved value for a configurable attribute before metadata inference.
248
288
  #
@@ -290,6 +330,22 @@ module Semverve
290
330
  raise Error, "Unknown version check #{invalid_checks.map(&:inspect).join(", ")}. Use #{valid_checks}."
291
331
  end
292
332
 
333
+ ##
334
+ # Normalizes and validates release checks.
335
+ #
336
+ # @param [Array<Symbol, String>] checks
337
+ #
338
+ # @return [Array<Symbol>]
339
+ def normalize_release_checks(checks)
340
+ normalized_checks = Array(checks).map do |check|
341
+ check.respond_to?(:to_sym) ? check.to_sym : check
342
+ end
343
+ return normalized_checks if normalized_checks.all? { |check| VALID_RELEASE_CHECKS.include?(check) }
344
+
345
+ invalid_checks = normalized_checks.reject { |check| VALID_RELEASE_CHECKS.include?(check) }
346
+ raise Error, "Unknown release check #{invalid_checks.map(&:inspect).join(", ")}. Use :rubygems."
347
+ end
348
+
293
349
  ##
294
350
  # Validates the configured code reference pattern.
295
351
  #
@@ -373,12 +429,24 @@ module Semverve
373
429
  # @return [String]
374
430
  attr_reader :module_name
375
431
 
432
+ ##
433
+ # Resolved release-readiness surfaces run by the release check task.
434
+ #
435
+ # @return [Array<Symbol>]
436
+ attr_reader :release_checks
437
+
376
438
  ##
377
439
  # Absolute project root.
378
440
  #
379
441
  # @return [String]
380
442
  attr_reader :root
381
443
 
444
+ ##
445
+ # Resolved RubyGems-compatible host used for published-version checks.
446
+ #
447
+ # @return [String]
448
+ attr_reader :rubygems_host
449
+
382
450
  ##
383
451
  # Resolved version-file path relative to the project root.
384
452
  #
@@ -423,7 +491,9 @@ module Semverve
423
491
  # @param [Symbol] format
424
492
  # @param [String] gem_name
425
493
  # @param [String] module_name
494
+ # @param [Array<Symbol>] release_checks
426
495
  # @param [String] root
496
+ # @param [String] rubygems_host
427
497
  # @param [String] version_file
428
498
  # @param [Array<Symbol>] version_checks
429
499
  # @param [Rake::FileList] version_code_reference_files
@@ -438,7 +508,9 @@ module Semverve
438
508
  format:,
439
509
  gem_name:,
440
510
  module_name:,
511
+ release_checks:,
441
512
  root:,
513
+ rubygems_host:,
442
514
  version_file:,
443
515
  version_checks:,
444
516
  version_code_reference_files:,
@@ -451,7 +523,9 @@ module Semverve
451
523
  @format = format
452
524
  @gem_name = gem_name
453
525
  @module_name = module_name
526
+ @release_checks = release_checks
454
527
  @root = root
528
+ @rubygems_host = rubygems_host
455
529
  @version_file = version_file
456
530
  @version_checks = version_checks
457
531
  @version_code_reference_files = version_code_reference_files
@@ -11,21 +11,25 @@ module Semverve
11
11
  # Generates new Ruby version files from Semverve configuration.
12
12
  class Generator
13
13
  ##
14
- # Default version used when +VERSION+ is not provided.
14
+ # Default version used when a version argument is not provided.
15
15
  #
16
16
  # @return [String]
17
- DEFAULT_VERSION = "0.1.0"
17
+ DEFAULT_VERSION = "0.1.0" # semverve:ignore-version-reference
18
18
 
19
19
  ##
20
20
  # Initializes a version-file generator.
21
21
  #
22
22
  # @param [Semverve::ResolvedConfiguration] configuration
23
- # @param [#fetch] env
23
+ # @param [String, nil] version
24
+ # @param [String, Symbol, nil] format
25
+ # @param [Boolean] force
24
26
  #
25
27
  # @return [Semverve::Generator]
26
- def initialize(configuration, env = ENV)
28
+ def initialize(configuration, version: nil, format: nil, force: false)
27
29
  @configuration = configuration
28
- @env = env
30
+ @version = version
31
+ @format = format
32
+ @force = force
29
33
  end
30
34
 
31
35
  ##
@@ -33,12 +37,12 @@ module Semverve
33
37
  #
34
38
  # @return [String]
35
39
  def generate
36
- version = SemanticVersion.parse(env.fetch("VERSION", DEFAULT_VERSION))
37
- format = Formats.fetch(env.fetch("FORMAT", configuration.format))
40
+ version = SemanticVersion.parse(requested_version)
41
+ format = Formats.fetch(requested_format)
38
42
  path = configuration.absolute_version_file
39
43
 
40
44
  if File.exist?(path) && !force?
41
- raise Error, "Version file already exists at #{path}. Set FORCE=true to overwrite it."
45
+ raise Error, "Version file already exists at #{path}. Run rake 'semverve:generate[force]' to overwrite it."
42
46
  end
43
47
 
44
48
  FileUtils.mkdir_p(File.dirname(path))
@@ -53,14 +57,30 @@ module Semverve
53
57
  # Resolved configuration used for generation.
54
58
  #
55
59
  # @return [Semverve::ResolvedConfiguration]
56
- attr_reader :configuration, :env
60
+ attr_reader :configuration, :version, :format, :force
61
+
62
+ ##
63
+ # Requested version or default.
64
+ #
65
+ # @return [String]
66
+ def requested_version
67
+ version || DEFAULT_VERSION
68
+ end
69
+
70
+ ##
71
+ # Requested format or configured default.
72
+ #
73
+ # @return [String, Symbol]
74
+ def requested_format
75
+ format || configuration.format
76
+ end
57
77
 
58
78
  ##
59
79
  # Whether generation should overwrite an existing version file.
60
80
  #
61
81
  # @return [Boolean]
62
82
  def force?
63
- env.fetch("FORCE", "false").match?(/\A(true|1|yes)\z/i)
83
+ force
64
84
  end
65
85
  end
66
86
  end
@@ -0,0 +1,117 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "json"
4
+ require "net/http"
5
+ require "timeout"
6
+ require "uri"
7
+
8
+ require_relative "error"
9
+
10
+ module Semverve
11
+ ##
12
+ # Checks whether the current gem version already exists on a
13
+ # RubyGems-compatible host.
14
+ class PublishedVersion
15
+ class << self
16
+ ##
17
+ # Callable used to fetch RubyGems-compatible API responses.
18
+ #
19
+ # @return [#call]
20
+ attr_writer :http_getter
21
+
22
+ ##
23
+ # Returns the HTTP response fetcher.
24
+ #
25
+ # @return [#call]
26
+ def http_getter
27
+ @http_getter ||= ->(uri) { Net::HTTP.get_response(uri) }
28
+ end
29
+ end
30
+
31
+ ##
32
+ # Initializes a published-version check.
33
+ #
34
+ # @param [Semverve::ResolvedConfiguration] configuration
35
+ # @param [Semverve::SemanticVersion] current_version
36
+ #
37
+ # @return [Semverve::PublishedVersion]
38
+ def initialize(configuration, current_version)
39
+ @configuration = configuration
40
+ @current_version = current_version
41
+ end
42
+
43
+ ##
44
+ # Whether the current version already exists on the configured host.
45
+ #
46
+ # @return [Boolean]
47
+ def published?
48
+ versions.any? { |version| version.fetch("number", nil).to_s == current_version.to_s }
49
+ end
50
+
51
+ ##
52
+ # Raises if the current version already exists.
53
+ #
54
+ # @return [void]
55
+ def check
56
+ if published?
57
+ raise Error, "#{configuration.gem_name} #{current_version} already exists on #{configuration.rubygems_host}."
58
+ end
59
+ end
60
+
61
+ private
62
+
63
+ ##
64
+ # Resolved Semverve configuration.
65
+ #
66
+ # @return [Semverve::ResolvedConfiguration]
67
+ attr_reader :configuration
68
+
69
+ ##
70
+ # Current gem version.
71
+ #
72
+ # @return [Semverve::SemanticVersion]
73
+ attr_reader :current_version
74
+
75
+ ##
76
+ # Published versions from the configured RubyGems-compatible host.
77
+ #
78
+ # @return [Array<Hash>]
79
+ def versions
80
+ response = self.class.http_getter.call(versions_uri)
81
+
82
+ case response.code.to_i
83
+ when 200
84
+ parse_versions(response.body)
85
+ when 404
86
+ []
87
+ else
88
+ raise Error, "Could not check #{configuration.gem_name} on #{configuration.rubygems_host}: HTTP #{response.code}."
89
+ end
90
+ rescue JSON::ParserError => error
91
+ raise Error, "Could not parse published versions for #{configuration.gem_name}: #{error.message}"
92
+ rescue SystemCallError, Timeout::Error, SocketError, URI::Error => error
93
+ raise Error, "Could not check #{configuration.gem_name} on #{configuration.rubygems_host}: #{error.message}"
94
+ end
95
+
96
+ ##
97
+ # Endpoint for a gem's published versions.
98
+ #
99
+ # @return [URI::HTTP, URI::HTTPS]
100
+ def versions_uri
101
+ URI("#{configuration.rubygems_host.chomp("/")}/api/v1/versions/#{URI.encode_www_form_component(configuration.gem_name)}.json")
102
+ end
103
+
104
+ ##
105
+ # Parsed version response.
106
+ #
107
+ # @param [String] body
108
+ #
109
+ # @return [Array<Hash>]
110
+ def parse_versions(body)
111
+ versions = JSON.parse(body)
112
+ return versions if versions.is_a?(Array)
113
+
114
+ raise Error, "Could not parse published versions for #{configuration.gem_name}: expected an array."
115
+ end
116
+ end
117
+ end
data/lib/semverve/task.rb CHANGED
@@ -4,6 +4,7 @@ require "rake"
4
4
 
5
5
  require_relative "../semverve"
6
6
  require_relative "generator"
7
+ require_relative "published_version"
7
8
  require_relative "semantic_version"
8
9
  require_relative "version_file"
9
10
  require_relative "version_code_references"
@@ -98,13 +99,19 @@ module Semverve
98
99
  end
99
100
 
100
101
  desc "Generate a version.rb file"
101
- task :generate do
102
- puts "Generated #{Generator.new(Semverve.configuration.resolved).generate}"
102
+ task :generate, [:version, :format, :force] do |_task, args|
103
+ generator_options = generate_options(args)
104
+ puts "Generated #{Generator.new(
105
+ Semverve.configuration.resolved,
106
+ version: generator_options[:version],
107
+ format: generator_options[:format],
108
+ force: generator_options[:force]
109
+ ).generate}"
103
110
  end
104
111
 
105
- desc "Set the version.rb file to VERSION"
106
- task :set do
107
- set
112
+ desc "Set the version.rb file to version"
113
+ task :set, [:version] do |_task, args|
114
+ set(args)
108
115
  end
109
116
 
110
117
  desc "Check version references, code literals, and metadata"
@@ -127,6 +134,16 @@ module Semverve
127
134
  task :metadata do
128
135
  check_metadata
129
136
  end
137
+
138
+ desc "Check whether the current gem version is already published"
139
+ task :rubygems do
140
+ check_rubygems
141
+ end
142
+
143
+ desc "Check configured release-readiness surfaces"
144
+ task :release do
145
+ check_release
146
+ end
130
147
  end
131
148
 
132
149
  desc "Fix version references, code literals, and metadata"
@@ -169,14 +186,14 @@ module Semverve
169
186
  end
170
187
 
171
188
  ##
172
- # Sets the version to the value from the +VERSION+ environment variable.
189
+ # Sets the version to the value from the Rake +version+ argument.
190
+ #
191
+ # @param [Rake::TaskArguments] args
173
192
  #
174
193
  # @return [void]
175
- def set
194
+ def set(args)
176
195
  configuration = Semverve.configuration.resolved
177
- requested_version = SemanticVersion.parse(
178
- ENV.fetch("VERSION") { raise Error, "Set VERSION=MAJOR.MINOR.PATCH." }
179
- )
196
+ requested_version = SemanticVersion.parse(requested_version_argument(args))
180
197
  update = VersionFile.new(configuration).set(requested_version)
181
198
 
182
199
  report(update, configuration)
@@ -211,7 +228,7 @@ module Semverve
211
228
  def check_references
212
229
  configuration, current_version = check_context
213
230
  report_findings(
214
- [["version reference", VersionReferences.new(configuration, current_version).findings]],
231
+ [["version reference", VersionReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]],
215
232
  current_version,
216
233
  clean_message: "Version references are current."
217
234
  )
@@ -236,7 +253,7 @@ module Semverve
236
253
  def check_code
237
254
  configuration, current_version = check_context
238
255
  report_findings(
239
- [["code version literal", VersionCodeReferences.new(configuration, current_version).findings]],
256
+ [["code version literal", VersionCodeReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]],
240
257
  current_version,
241
258
  clean_message: "Code version literals are current."
242
259
  )
@@ -267,6 +284,31 @@ module Semverve
267
284
  )
268
285
  end
269
286
 
287
+ ##
288
+ # Checks whether the current gem version is already published.
289
+ #
290
+ # @return [void]
291
+ def check_rubygems
292
+ configuration, current_version = check_context
293
+ PublishedVersion.new(configuration, current_version).check
294
+
295
+ puts "#{configuration.gem_name} #{current_version} is not published on #{configuration.rubygems_host}."
296
+ end
297
+
298
+ ##
299
+ # Checks configured release-readiness surfaces.
300
+ #
301
+ # @return [void]
302
+ def check_release
303
+ configuration, current_version = check_context
304
+
305
+ if configuration.release_checks.include?(:rubygems)
306
+ PublishedVersion.new(configuration, current_version).check
307
+ end
308
+
309
+ puts "Release checks passed."
310
+ end
311
+
270
312
  ##
271
313
  # Fixes safe gem metadata version mismatches.
272
314
  #
@@ -298,10 +340,10 @@ module Semverve
298
340
  def check_groups(configuration, current_version)
299
341
  groups = []
300
342
  if configuration.version_checks.include?(:doc_references)
301
- groups << ["version reference", VersionReferences.new(configuration, current_version).findings]
343
+ groups << ["version reference", VersionReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]
302
344
  end
303
345
  if configuration.version_checks.include?(:code_references)
304
- groups << ["code version literal", VersionCodeReferences.new(configuration, current_version).findings]
346
+ groups << ["code version literal", VersionCodeReferences.new(configuration, current_version, include_ignored: report_ignored?).findings]
305
347
  end
306
348
  if configuration.version_checks.include?(:metadata)
307
349
  groups << [nil, VersionMetadata.new(configuration, current_version).findings]
@@ -309,6 +351,47 @@ module Semverve
309
351
  groups
310
352
  end
311
353
 
354
+ ##
355
+ # Whether check tasks should report references hidden by ignore markers.
356
+ #
357
+ # @return [Boolean]
358
+ def report_ignored?
359
+ ENV.fetch("SEMVERVE_REPORT_IGNORED", "false").match?(/\A(true|1|yes)\z/i)
360
+ end
361
+
362
+ ##
363
+ # Parsed arguments for +semverve:generate+.
364
+ #
365
+ # @param [Rake::TaskArguments] args
366
+ #
367
+ # @return [Hash]
368
+ def generate_options(args)
369
+ values = args.to_a
370
+ force = values.delete("force")
371
+ if values.length > 2
372
+ raise Error, "Run rake 'semverve:generate[VERSION,FORMAT,force]'."
373
+ end
374
+
375
+ {
376
+ version: values[0],
377
+ format: values[1],
378
+ force: !force.nil?
379
+ }
380
+ end
381
+
382
+ ##
383
+ # Required version argument for +semverve:set+.
384
+ #
385
+ # @param [Rake::TaskArguments] args
386
+ #
387
+ # @return [String]
388
+ def requested_version_argument(args)
389
+ version = args[:version]
390
+ raise Error, "Run rake 'semverve:set[MAJOR.MINOR.PATCH]'." if version.nil? || version.empty?
391
+
392
+ version
393
+ end
394
+
312
395
  ##
313
396
  # Fix results enabled for the umbrella fix task.
314
397
  #
@@ -15,7 +15,7 @@ module Semverve
15
15
  # Minor version.
16
16
  #
17
17
  # @return [Integer]
18
- MINOR = 1
18
+ MINOR = 2
19
19
 
20
20
  ##
21
21
  # Patch version.
@@ -8,6 +8,12 @@ module Semverve
8
8
  ##
9
9
  # Finds and updates safe version literals in configured code files.
10
10
  class VersionCodeReferences
11
+ ##
12
+ # Marker that suppresses version-reference findings.
13
+ #
14
+ # @return [String]
15
+ IGNORE_MARKER = "semverve:ignore-version-reference"
16
+
11
17
  ##
12
18
  # Ruby assignments that are safe enough to rewrite automatically.
13
19
  #
@@ -93,9 +99,10 @@ module Semverve
93
99
  # @param [Semverve::SemanticVersion] current_version
94
100
  #
95
101
  # @return [Semverve::VersionCodeReferences]
96
- def initialize(configuration, current_version)
102
+ def initialize(configuration, current_version, include_ignored: false)
97
103
  @configuration = configuration
98
104
  @current_version = current_version
105
+ @include_ignored = include_ignored
99
106
  end
100
107
 
101
108
  ##
@@ -142,6 +149,12 @@ module Semverve
142
149
  # @return [Semverve::SemanticVersion]
143
150
  attr_reader :current_version
144
151
 
152
+ ##
153
+ # Whether ignored code literals should be included in findings.
154
+ #
155
+ # @return [Boolean]
156
+ attr_reader :include_ignored
157
+
145
158
  ##
146
159
  # Absolute configured project root.
147
160
  #
@@ -176,7 +189,11 @@ module Semverve
176
189
  #
177
190
  # @return [Array<Semverve::VersionCodeReferences::Finding>]
178
191
  def findings_for_file(path)
179
- File.readlines(path).filter_map.with_index(1) do |line, line_number|
192
+ lines = File.readlines(path)
193
+
194
+ lines.filter_map.with_index(1) do |line, line_number|
195
+ next if !include_ignored && ignored_line?(line_number, lines)
196
+
180
197
  match = line.match(pattern)
181
198
  next unless match
182
199
 
@@ -200,8 +217,11 @@ module Semverve
200
217
  # @return [Array(String, Integer)]
201
218
  def fixed_content(content)
202
219
  replacement_count = 0
220
+ lines = content.lines
221
+
222
+ fixed = lines.map.with_index(1) do |line, line_number|
223
+ next line if ignored_line?(line_number, lines)
203
224
 
204
- fixed = content.lines.map do |line|
205
225
  match = line.match(pattern)
206
226
  next line unless match
207
227
 
@@ -215,6 +235,23 @@ module Semverve
215
235
  [fixed, replacement_count]
216
236
  end
217
237
 
238
+ ##
239
+ # Whether findings on a line should be ignored.
240
+ #
241
+ # @param [Integer] line_number
242
+ # @param [Array<String>] lines
243
+ #
244
+ # @return [Boolean]
245
+ def ignored_line?(line_number, lines)
246
+ return true if lines.fetch(line_number - 1).include?(IGNORE_MARKER)
247
+
248
+ # Ruby 3.2 and 3.3 do not support Array#rfind.
249
+ # rubocop:disable Style/ReverseFind
250
+ previous_nonblank_line = lines[0...(line_number - 1)].reverse_each.find { |candidate| !candidate.strip.empty? }
251
+ # rubocop:enable Style/ReverseFind
252
+ previous_nonblank_line&.include?(IGNORE_MARKER)
253
+ end
254
+
218
255
  ##
219
256
  # Replaces only the named version capture in the first pattern match.
220
257
  #
@@ -107,9 +107,10 @@ module Semverve
107
107
  # @param [Semverve::SemanticVersion] current_version
108
108
  #
109
109
  # @return [Semverve::VersionReferences]
110
- def initialize(configuration, current_version)
110
+ def initialize(configuration, current_version, include_ignored: false)
111
111
  @configuration = configuration
112
112
  @current_version = current_version
113
+ @include_ignored = include_ignored
113
114
  end
114
115
 
115
116
  ##
@@ -156,6 +157,12 @@ module Semverve
156
157
  # @return [Semverve::SemanticVersion]
157
158
  attr_reader :current_version
158
159
 
160
+ ##
161
+ # Whether ignored references should be included in findings.
162
+ #
163
+ # @return [Boolean]
164
+ attr_reader :include_ignored
165
+
159
166
  ##
160
167
  # Absolute configured project root.
161
168
  #
@@ -346,7 +353,7 @@ module Semverve
346
353
  #
347
354
  # @return [Array<Hash>]
348
355
  def references_for_segment(segment, content)
349
- return [] if ignored_line?(segment[:line], content)
356
+ return [] if !include_ignored && ignored_line?(segment[:line], content)
350
357
 
351
358
  segment[:text].to_enum(:scan, VERSION_PATTERN).filter_map do
352
359
  match = Regexp.last_match
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: semverve
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Evan Gray
@@ -119,6 +119,7 @@ files:
119
119
  - lib/semverve/generator.rb
120
120
  - lib/semverve/presets.rb
121
121
  - lib/semverve/project_metadata.rb
122
+ - lib/semverve/published_version.rb
122
123
  - lib/semverve/railtie.rb
123
124
  - lib/semverve/semantic_version.rb
124
125
  - lib/semverve/task.rb