semverve 0.1.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.
data/README.md ADDED
@@ -0,0 +1,551 @@
1
+ # Semverve
2
+ Rake tasks for handling the tedium surrounding maintaining a version number in
3
+ your Ruby project, with gusto!
4
+
5
+ ## About
6
+ Maintaining a gem version is not hard, but there are so many little pieces that
7
+ are easy to forget. How many times have you had changes where the code was
8
+ ready, the tests were green, the PR was merged, you go to push the gem, and you
9
+ realize you forgot to bump the version? Then comes the tiny follow-up PR that
10
+ forces you to waste CI minutes for a two-line change, you submit it, and... oh,
11
+ no! You still have references to the old version number in your documentation!
12
+ Rinse and repeat until you finally remember all the things.
13
+
14
+ Semverve is meant to make that tedium boring in the best way. It provides a
15
+ small set of Rake tasks for reading the current version, generating a version
16
+ file, incrementing patch/minor/major versions, setting an exact version, and
17
+ checking the places where version numbers tend to drift, like `.gemspec` files and
18
+ documentation.
19
+
20
+ In a nutshell, `rake semverve:increment:(patch|minor|major)` updates
21
+ your configured `version.rb` file, and `rake semverve:check` checks whether the
22
+ surrounding project still agrees with that version. It can catch stale README
23
+ references, safe code literals, `.gemspec` drift, and a stale `Gemfile.lock`
24
+ entry. If you want Semverve to do the mechanical cleanup, the matching `*:fix`
25
+ tasks can update safe references and run `bundle lock` for generated lockfile
26
+ drift. Specific findings can be skipped with magic comments, similar to RuboCop
27
+ and RDoc.
28
+
29
+ ## Installation
30
+ Add the gem to your Gemfile:
31
+
32
+ ```ruby
33
+ gem "semverve"
34
+ ```
35
+
36
+ Then add this to your Rakefile:
37
+
38
+ ```ruby
39
+ require "semverve/task"
40
+ ```
41
+
42
+ This defines:
43
+
44
+ ```text
45
+ rake semverve:current
46
+ rake semverve:increment:patch
47
+ rake semverve:increment:minor
48
+ rake semverve:increment:major
49
+ rake semverve:generate
50
+ rake semverve:set VERSION=1.2.3
51
+ rake semverve:check
52
+ rake semverve:fix
53
+ rake semverve:check:references
54
+ rake semverve:fix:references
55
+ rake semverve:check:code
56
+ rake semverve:fix:code
57
+ rake semverve:check:metadata
58
+ rake semverve:fix:metadata
59
+ ```
60
+
61
+ ## Configuration
62
+ By default, Semverve reads the single `.gemspec` in the project root, uses
63
+ `spec.name` as the gem name, and manages `lib/<gem_name>/version.rb`.
64
+
65
+ For a conventional gem, this may be all you need:
66
+
67
+ ```ruby
68
+ require "semverve/task"
69
+ ```
70
+
71
+ That automatically installs the `semverve:*` Rake tasks. If you want to make
72
+ the setup explicit, or if you want to change any defaults, configure Semverve
73
+ from your Rakefile:
74
+
75
+ ```ruby
76
+ require "semverve/task"
77
+
78
+ Semverve.configure do |config|
79
+ config.format = :module
80
+ config.bundle_lock = true
81
+ config.version_file = "lib/my_gem/version.rb"
82
+ config.module_name = "MyGem"
83
+ config.version_checks = [:doc_references, :code_references, :metadata]
84
+ config.version_code_reference_files.append("lib/**/*.rb")
85
+ config.version_doc_reference_files.append("doc/**/*.md")
86
+ config.version_reference_mode = :non_current
87
+ end
88
+ ```
89
+
90
+ The core defaults are equivalent to:
91
+
92
+ ```ruby
93
+ Semverve.configure do |config|
94
+ config.format = :module
95
+ config.bundle_lock = false
96
+ config.root = Dir.pwd
97
+ config.version_checks = [:doc_references, :code_references, :metadata]
98
+ config.version_reference_mode = :older
99
+ config.version_code_reference_files = Rake::FileList[]
100
+ config.version_code_reference_pattern =
101
+ /^\s*(?:(?:[A-Z]\w*::)*(?:[A-Z]\w*VERSION[A-Z0-9_]*|VERSION)|(?:[a-z_]\w*|self)\.version)\s*=\s*(?<quote>["'])(?<version>\d+\.\d+\.\d+)\k<quote>/
102
+ config.version_doc_reference_files = Rake::FileList["README*", "**/README*"].exclude(
103
+ ".git/**/*",
104
+ "coverage/**/*",
105
+ "tmp/**/*",
106
+ "vendor/**/*"
107
+ )
108
+ end
109
+ ```
110
+
111
+ The empty `version_code_reference_files` default only applies to arbitrary code
112
+ literal scanning. `rake semverve:check` still checks the resolved `.gemspec`
113
+ version and matching `Gemfile.lock` entry through its default metadata check.
114
+
115
+ The gem name, module name, and version-file path are inferred by default:
116
+
117
+ ```ruby
118
+ config.gem_name # spec.name from the single .gemspec
119
+ config.module_name # camelized gem name, such as "MyGem"
120
+ config.version_file # lib/<gem_name>/version.rb
121
+ ```
122
+
123
+ Override them when your project does something unusual:
124
+
125
+ ```ruby
126
+ Semverve.configure do |config|
127
+ config.gem_name = "my-gem"
128
+ config.module_name = "MyGem"
129
+ config.version_file = "lib/my_gem/version.rb"
130
+ end
131
+ ```
132
+
133
+ Explicit task setup is also supported:
134
+
135
+ ```ruby
136
+ Semverve::Task.new do |config|
137
+ config.bundle_lock = true
138
+ end
139
+ ```
140
+
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
+ ## Rails apps
184
+ Rails applications do not need gem-style version files, but an application
185
+ version can still be useful for release notes, support/debug screens,
186
+ deployment metadata, or API output.
187
+
188
+ When Rails is loaded, Semverve's Railtie installs the same `semverve:*` Rake
189
+ tasks for `bin/rails`/`rails` automatically. To use Rails-style defaults, set
190
+ the Rails preset:
191
+
192
+ ```ruby
193
+ Semverve.configure do |config|
194
+ config.preset = :rails
195
+ end
196
+ ```
197
+
198
+ The Rails preset uses `Rails.root`, stores the version in
199
+ `config/version.rb`, uses the `:simple` format, and infers the module name from
200
+ your Rails application module when possible.
201
+
202
+ Generate the file with:
203
+
204
+ ```sh
205
+ bin/rails semverve:generate
206
+ ```
207
+
208
+ If your app keeps the version somewhere else, override the path:
209
+
210
+ ```ruby
211
+ Semverve.configure do |config|
212
+ config.preset = :rails
213
+ config.version_file = "config/releases/version.rb"
214
+ end
215
+ ```
216
+
217
+ Rails support is only a preset and a Railtie; Semverve does not require a dummy
218
+ app, a Rails plugin layout, or a Rails dependency.
219
+
220
+ ## Formats
221
+ The default `:module` format stores `MAJOR`, `MINOR`, and `PATCH` constants
222
+ under a `Version` module and exposes a top-level `VERSION` constant.
223
+
224
+ ```ruby
225
+ module MyGem
226
+ module Version
227
+ MAJOR = 0
228
+ MINOR = 1
229
+ PATCH = 0
230
+
231
+ module_function
232
+
233
+ def to_a
234
+ [MAJOR, MINOR, PATCH]
235
+ end
236
+
237
+ def to_s
238
+ to_a.join(".")
239
+ end
240
+ end
241
+
242
+ VERSION = Version.to_s
243
+ end
244
+ ```
245
+
246
+ The `:simple` format stores only:
247
+
248
+ ```ruby
249
+ module MyGem
250
+ VERSION = "1.0.0"
251
+ end
252
+ ```
253
+
254
+ ## Generating
255
+ Generate the default module format:
256
+
257
+ ```sh
258
+ rake semverve:generate
259
+ ```
260
+
261
+ Generate a specific version or format:
262
+
263
+ ```sh
264
+ rake semverve:generate VERSION=1.0.0 FORMAT=simple
265
+ ```
266
+
267
+ Generation fails if the target file already exists. To replace it:
268
+
269
+ ```sh
270
+ rake semverve:generate FORCE=true
271
+ ```
272
+
273
+ ## Incrementing
274
+ ```sh
275
+ rake semverve:increment:patch
276
+ rake semverve:increment:minor
277
+ rake semverve:increment:major
278
+ ```
279
+
280
+ Patch increments only patch. Minor increments minor and resets patch to `0`.
281
+ Major increments major and resets minor and patch to `0`.
282
+
283
+ Successful increments print the version change:
284
+
285
+ ```text
286
+ Updating to version 2.0.2 (was 2.0.1)
287
+ ```
288
+
289
+ Set `config.bundle_lock = true` to run `bundle lock` after increments to update
290
+ your gem's version in `Gemfile.lock`.
291
+
292
+ ## Setting
293
+ Set an exact version without incrementing:
294
+
295
+ ```sh
296
+ rake semverve:set VERSION=1.2.3
297
+ ```
298
+
299
+ Successful updates print the version change:
300
+
301
+ ```text
302
+ Updating to version 1.2.3 (was 1.2.2)
303
+ ```
304
+
305
+ Setting the current version again does not rewrite the version file:
306
+
307
+ ```text
308
+ Version is already 1.2.3
309
+ ```
310
+
311
+ Setting a lower version prints a warning but still updates the file:
312
+
313
+ ```text
314
+ Warning: updating to version 1.9.9, which is lower than the current version 2.0.1.
315
+ Updating to version 1.9.9 (was 2.0.1)
316
+ ```
317
+
318
+ This will also run `bundle lock` on success if you have `config.bundle_lock =
319
+ true` in your config.
320
+
321
+ ## Checking version references, code, and metadata
322
+ Run every version check with:
323
+
324
+ ```sh
325
+ rake semverve:check
326
+ ```
327
+
328
+ By default, this checks:
329
+
330
+ - README version references, plus any configured docs or comment files
331
+ - configured code files for safe version literals
332
+ - the gemspec version and `Gemfile.lock` entry
333
+
334
+ Findings are printed in parseable formats and the task exits non-zero:
335
+
336
+ ```text
337
+ README.md:12:24: version reference 1.2.2 -> 1.2.3
338
+ lib/my_gem/constants.rb:1:16: code version literal 1.2.2 -> 1.2.3
339
+ my_gem.gemspec:3:18: gemspec version 1.2.2 -> 1.2.3
340
+ Gemfile.lock:4:13: locked version 1.2.2 -> 1.2.3
341
+ ```
342
+
343
+ Run every available fix:
344
+
345
+ ```sh
346
+ rake semverve:fix
347
+ ```
348
+
349
+ Choose which surfaces the umbrella `check` and `fix` tasks run with
350
+ `config.version_checks`:
351
+
352
+ ```ruby
353
+ Semverve.configure do |config|
354
+ config.version_checks = [:doc_references, :metadata]
355
+ end
356
+ ```
357
+
358
+ The allowed values are `:doc_references`, `:code_references`, and `:metadata`.
359
+
360
+ Use focused tasks when you want only one surface:
361
+
362
+ ```sh
363
+ rake semverve:check:references
364
+ rake semverve:fix:references
365
+ rake semverve:check:code
366
+ rake semverve:fix:code
367
+ rake semverve:check:metadata
368
+ rake semverve:fix:metadata
369
+ ```
370
+
371
+ `semverve:fix:metadata` rewrites literal gemspec versions when safe and
372
+ runs `bundle lock` for `Gemfile.lock` drift.
373
+
374
+ ### Version references
375
+ Version references are prose-like references to versions. These are usually in
376
+ README files, docs, guides, or comments. By default, Semverve scans README files
377
+ throughout the repo:
378
+
379
+ ```ruby
380
+ Semverve.configure do |config|
381
+ config.version_doc_reference_files = Rake::FileList["README*", "**/README*"].exclude(
382
+ ".git/**/*",
383
+ "coverage/**/*",
384
+ "tmp/**/*",
385
+ "vendor/**/*"
386
+ )
387
+ end
388
+ ```
389
+
390
+ Add docs or Ruby comments without replacing the README defaults:
391
+
392
+ ```ruby
393
+ Semverve.configure do |config|
394
+ config.version_doc_reference_files.append("doc/**/*.md", "lib/**/*.rb")
395
+ end
396
+ ```
397
+
398
+ Replace the defaults entirely:
399
+
400
+ ```ruby
401
+ Semverve.configure do |config|
402
+ config.version_doc_reference_files = Rake::FileList["guides/**/*.md"]
403
+ end
404
+ ```
405
+
406
+ Ruby files are scanned only in comments. Text files with `.md`, `.markdown`,
407
+ `.txt`, `.rdoc`, and `.adoc` extensions are scanned as full text.
408
+
409
+ The default reference mode is `:older`, which flags only semantic versions lower
410
+ than the current version:
411
+
412
+ ```ruby
413
+ Semverve.configure do |config|
414
+ config.version_reference_mode = :older
415
+ end
416
+ ```
417
+
418
+ Use `:non_current` when every reference should match the current version:
419
+
420
+ ```ruby
421
+ Semverve.configure do |config|
422
+ config.version_reference_mode = :non_current
423
+ end
424
+ ```
425
+
426
+ Ignore an intentional reference with `semverve:ignore-version-reference` on the
427
+ same line or the preceding nonblank line.
428
+
429
+ ```markdown
430
+ This migration note intentionally mentions 1.0.0. <!-- semverve:ignore-version-reference -->
431
+ ```
432
+
433
+ ### Code version literals
434
+ Code scanning is opt-in to avoid false positives. This is for arbitrary project
435
+ code, not gem metadata. The default is:
436
+
437
+ ```ruby
438
+ Semverve.configure do |config|
439
+ config.version_code_reference_files = Rake::FileList[]
440
+ end
441
+ ```
442
+
443
+ Append files when you want Semverve to check safe code literals:
444
+
445
+ ```ruby
446
+ Semverve.configure do |config|
447
+ config.version_code_reference_files.append("lib/**/*.rb")
448
+ end
449
+ ```
450
+
451
+ Or replace the list entirely:
452
+
453
+ ```ruby
454
+ Semverve.configure do |config|
455
+ config.version_code_reference_files = Rake::FileList["lib/**/*.rb", "*.gemspec"]
456
+ end
457
+ ```
458
+
459
+ Ruby code checks only obvious version assignments/constants, such as:
460
+
461
+ ```ruby
462
+ APP_VERSION = "1.2.2"
463
+ spec.version = "1.2.2"
464
+ ```
465
+
466
+ The default pattern is Ruby-oriented. Semverve does not inspect file extensions
467
+ or parse other languages for code literals; non-Ruby files are scanned as plain
468
+ text with the same pattern. If a JavaScript, Python, or other source file uses a
469
+ different version-literal shape, configure a custom pattern before adding those
470
+ files.
471
+
472
+ Arbitrary string examples are ignored.
473
+
474
+ If your project has a different safe version-literal shape, provide your own
475
+ pattern:
476
+
477
+ ```ruby
478
+ Semverve.configure do |config|
479
+ config.version_code_reference_files.append("lib/**/*.rb")
480
+ config.version_code_reference_pattern = /release ["'](?<version>\d+\.\d+\.\d+)["']/
481
+ end
482
+ ```
483
+
484
+ With that pattern, this line:
485
+
486
+ ```ruby
487
+ release "1.2.2"
488
+ ```
489
+
490
+ matches the full `release "1.2.2"` text, but only `1.2.2` is captured as
491
+ `version`. If `rake semverve:fix:code` is updating references to `1.2.3`, the
492
+ line becomes:
493
+
494
+ ```ruby
495
+ release "1.2.3"
496
+ ```
497
+
498
+ The custom value must be a `Regexp` and must include a named capture called
499
+ `version`. Semverve replaces only that capture when running
500
+ `rake semverve:fix:code`, and the captured value still has to parse as a
501
+ semantic version.
502
+
503
+ ### Metadata
504
+ Metadata checks are part of `rake semverve:check` by default. They compare the
505
+ current version file against:
506
+
507
+ - the resolved `.gemspec` version
508
+ - the matching `Gemfile.lock` entry, when a lockfile exists
509
+
510
+ Metadata always requires an exact match, regardless of
511
+ `config.version_reference_mode`.
512
+
513
+ No file-list configuration is needed for these checks. Semverve resolves the
514
+ gemspec from the project root and reads `Gemfile.lock` when one exists.
515
+
516
+ Dynamic gemspec versions work as expected:
517
+
518
+ ```ruby
519
+ require_relative "lib/my_gem/version"
520
+
521
+ Gem::Specification.new do |spec|
522
+ spec.name = "my_gem"
523
+ spec.version = MyGem::VERSION
524
+ end
525
+ ```
526
+
527
+ Literal gemspec versions can be fixed automatically:
528
+
529
+ ```ruby
530
+ Gem::Specification.new do |spec|
531
+ spec.name = "my_gem"
532
+ spec.version = "1.2.2"
533
+ end
534
+ ```
535
+
536
+ `rake semverve:fix:metadata` updates safe literal gemspec assignments and
537
+ runs `bundle lock` when the lockfile has drifted.
538
+
539
+ ## Reporting Bugs and Requesting Features
540
+ If you have an idea or find a bug, please [create an
541
+ issue](https://github.com/evanthegrayt/semverve/issues/new). Just make sure
542
+ the topic doesn't already exist. Better yet, you can always submit a Pull
543
+ Request.
544
+
545
+ ## Support this project
546
+ I love knowing when people find my work useful. Any kind of support is very much
547
+ appreciated!
548
+
549
+ - ⭐️ Like the project? Star [the repository](https://github.com/evanthegrayt/semverve)!
550
+ - ❤️ Love the project? Follow me [on GitHub](https://github.com/evanthegrayt)!
551
+ - 💸 *Really* love it? Consider [buying me a tea](https://paypal.me/evanrgray)!
data/Rakefile ADDED
@@ -0,0 +1,45 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "lib/semverve/task"
4
+ require_relative "lib/semverve/docs_publisher/task"
5
+ require "bundler/gem_tasks"
6
+ require "rdoc/task"
7
+ require "rake/testtask"
8
+
9
+ Rake::TestTask.new do |t|
10
+ t.libs = ["lib"]
11
+ t.warning = true
12
+ t.verbose = true
13
+ t.test_files = FileList["test/**/*_test.rb"]
14
+ end
15
+
16
+ RDoc::Task.new do |rdoc|
17
+ rdoc.main = "README.md"
18
+ rdoc.rdoc_dir = "docs"
19
+ rdoc.rdoc_files.include("README.md", "lib/**/*.rb")
20
+ end
21
+
22
+ Semverve::Task.new do |t|
23
+ t.bundle_lock = true
24
+ t.version_code_reference_files.append("lib/**/*.rb", "semverve.gemspec", "Rakefile")
25
+ end
26
+
27
+ Semverve::DocsPublisher::Task.new
28
+
29
+ standardrb = ->(*args) do
30
+ sh(["bundle", "exec", "standardrb", *args].join(" "))
31
+ end
32
+
33
+ desc "Run Standard Ruby"
34
+ task :standard do
35
+ standardrb.call
36
+ end
37
+
38
+ namespace :standard do
39
+ desc "Fix Standard Ruby offenses"
40
+ task :fix do
41
+ standardrb.call("--fix")
42
+ end
43
+ end
44
+
45
+ task default: [:test, "semverve:check"]