ruby-repo-kit 0.1.0 → 0.1.1

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: 2909f2324f7d85ad154aa579190c1a6163b2245b742e96075ba7f8ba45c0eb73
4
- data.tar.gz: e67253ccb000a0c816a0fca22aaf9ae1e0984ca16344f4d1f814fe7df0f96ab0
3
+ metadata.gz: ffeaa438a6aabff617d58d356184c706f8e95d279bd74d292a9077c51e32c9d9
4
+ data.tar.gz: 72e653e7fea5dcc94dcaa99e4116c5dec4e670ca8e7e2bd4f0dcd78c7d9991ff
5
5
  SHA512:
6
- metadata.gz: 2e008180735fbb712e1860ad300104fbecfa27c6d8e677e16487660bd976b3c6318a7778ac0fae1593a4f3b56fb783e622494aaf09ba9ea8310bc19c1905c24e
7
- data.tar.gz: 5cb158be5ed9a2153ca460026b8184d88f7bd50b9fd6be22039badc17cd71f8d9d0a48485ad7d82b5b55a19c9565ef2d2b0ea81e1adcb34330b60ca62848c632
6
+ metadata.gz: b0955bcebae7be6166af09ce531861e01a8396f8c49fdf42d2eb69467c8847ca39760bd7e27bff3a229abbf28162eb1f50d97394a553ed5e8a7cdb9967c906ec
7
+ data.tar.gz: d532a0e0a945ef4bd39d47eb28fc7bec468200419e0b77ad022fefb589346dc815dfbd684cd07295e19c0c49fe64ac8bbb055aaf60248e3bcdf8b8f6a7d2fc56
data/CHANGELOG.md CHANGED
@@ -2,6 +2,20 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [0.1.1] - 2026-10-05
6
+
7
+ ### Added
8
+
9
+ - Preserve consumer package smoke checks with application environment overrides while keeping isolated gem loading.
10
+ - Configure hotfix-branch protection and required review-thread resolution without replacing the shared GitHub policy engine.
11
+ - Document supported release-task and packaging APIs for existing repositories with their own maintenance tasks.
12
+
13
+ ### Fixed
14
+
15
+ - Prevent automatic RubyGems and Bundler setup from loading dependencies or checkout code outside package smoke isolation.
16
+ - Detect missing or empty RubyGems lockfile checksums in doctor and repo:check before frozen CI fails, without fetching or changing dependencies.
17
+ - Keep generated CLI release notes under Unreleased so the first 0.1.0 release can be prepared without correcting a premature release history.
18
+
5
19
  ## [0.1.0] - 2026-10-05
6
20
 
7
21
  ### Added
@@ -10,5 +24,6 @@
10
24
  - Share release preparation, recovery, artifact verification and isolated package checks through a development gem.
11
25
  - Validate repository contracts and plan, verify or apply the initial GitHub maintenance policy.
12
26
 
13
- [Unreleased]: https://github.com/hvpaiva/ruby-repo-kit/compare/v0.1.0...HEAD
27
+ [Unreleased]: https://github.com/hvpaiva/ruby-repo-kit/compare/v0.1.1...HEAD
14
28
  [0.1.0]: https://github.com/hvpaiva/ruby-repo-kit/releases/tag/v0.1.0
29
+ [0.1.1]: https://github.com/hvpaiva/ruby-repo-kit/releases/tag/v0.1.1
data/README.md CHANGED
@@ -83,7 +83,14 @@ the Ruby API, ownership boundaries and configuration example.
83
83
 
84
84
  Doctor validates the local maintenance contract: required files, package
85
85
  metadata, version consistency, package manifest, development dependency and
86
- shared lint configuration. It does not certify code quality, run the full test
86
+ shared lint configuration. If Gemfile.lock enables CHECKSUMS, it also detects
87
+ missing or empty checksums for RubyGems dependencies before frozen CI installation.
88
+ PATH and Git dependencies do not need gem artifact checksums. This inspection
89
+ does not install dependencies, access the network or modify the lockfile. To fix
90
+ incomplete checksums, run `bundle lock --add-checksums`, review and commit the
91
+ lockfile. This check verifies checksum presence; Bundler verifies artifact bytes
92
+ when installing. Existing lockfiles without CHECKSUMS retain Bundler's opt-in
93
+ behavior. Doctor does not certify code quality, run the full test
87
94
  suite, or verify hosted settings.
88
95
 
89
96
  The repository's check task combines lint, tests with coverage, structural checks
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler"
4
+
5
+ module RubyRepoKit
6
+ class Checks
7
+ # Parse only: never resolve, install, fetch or rewrite the consumer's bundle.
8
+ class Lockfile
9
+ def initialize(path:)
10
+ @path = path
11
+ end
12
+
13
+ def check
14
+ return true unless File.exist?(@path)
15
+
16
+ parsed = Bundler::LockfileParser.new(File.read(@path))
17
+ # Checksums are opt-in for existing lockfiles, including Bundler 2.6.
18
+ return true unless parsed.checksums
19
+
20
+ incomplete = parsed.specs.select do |spec|
21
+ spec.source.is_a?(Bundler::Source::Rubygems) &&
22
+ spec.source.checksum_store.to_lock(spec) == spec.name_tuple.lock_name
23
+ end
24
+ return true if incomplete.empty?
25
+
26
+ raise Error, "Gemfile.lock has empty or missing CHECKSUMS for: #{incomplete.map(&:full_name).join(', ')}. " \
27
+ "Run `bundle lock --add-checksums`, then review and commit Gemfile.lock before frozen CI."
28
+ rescue Bundler::BundlerError, ArgumentError => e
29
+ raise Error, "Cannot inspect Gemfile.lock checksums: #{e.message}"
30
+ end
31
+ end
32
+ end
33
+ end
@@ -5,6 +5,7 @@ require "open3"
5
5
  require "psych"
6
6
  require "rbconfig"
7
7
  require_relative "specification"
8
+ require_relative "checks/lockfile"
8
9
 
9
10
  module RubyRepoKit
10
11
  # Local structural checks. Evaluating a gemspec/Gemfile is trusted project code;
@@ -27,6 +28,7 @@ module RubyRepoKit
27
28
  [@project.gemspec, @project.version_file, @project.changelog, "Gemfile", "Rakefile"].each do |file|
28
29
  require_file(file)
29
30
  end
31
+ Lockfile.new(path: @project.path("Gemfile.lock")).check
30
32
  specification = Specification.load(project: @project)
31
33
  metadata(specification)
32
34
  manifest(specification)
@@ -21,12 +21,13 @@ module RubyRepoKit
21
21
  def main_ruleset
22
22
  {
23
23
  "name" => "main", "target" => "branch", "enforcement" => "active", "bypass_actors" => [],
24
- "conditions" => { "ref_name" => { "include" => %w[refs/heads/main refs/heads/hotfix/*], "exclude" => [] } },
24
+ "conditions" => { "ref_name" => { "include" => protected_branches, "exclude" => [] } },
25
25
  "rules" => [
26
26
  { "type" => "pull_request", "parameters" => {
27
27
  "required_approving_review_count" => 0, "dismiss_stale_reviews_on_push" => false,
28
28
  "require_code_owner_review" => false, "require_last_push_approval" => false,
29
- "required_review_thread_resolution" => true, "allowed_merge_methods" => ["merge"]
29
+ "required_review_thread_resolution" => @project.require_review_thread_resolution,
30
+ "allowed_merge_methods" => ["merge"]
30
31
  } },
31
32
  { "type" => "required_status_checks", "parameters" => {
32
33
  "strict_required_status_checks_policy" => true, "do_not_enforce_on_create" => false,
@@ -47,6 +48,14 @@ module RubyRepoKit
47
48
  "rules" => %w[creation update deletion].map { |type| { "type" => type } }
48
49
  }
49
50
  end
51
+
52
+ private
53
+
54
+ def protected_branches
55
+ ["refs/heads/main"].tap do |branches|
56
+ branches << "refs/heads/hotfix/*" if @project.protect_hotfix_branches
57
+ end
58
+ end
50
59
  end
51
60
  end
52
61
  end
@@ -25,15 +25,17 @@ module RubyRepoKit
25
25
  target
26
26
  end
27
27
 
28
- def check(artifact: nil)
28
+ def check(artifact: nil, environment: {})
29
+ environment = validated_environment(environment)
29
30
  Dir.mktmpdir("ruby-repo-package-") do |dir|
30
31
  package = artifact ? File.expand_path(artifact, @project.root) : build(output: File.join(dir, "package.gem"))
31
32
  spec = verify(package)
32
33
  home = File.join(dir, "gems")
33
34
  cache_dependencies(dir)
34
35
  Bundler.with_unbundled_env do
35
- env = { "GEM_HOME" => home, "GEM_PATH" => home, "RUBYOPT" => nil, "RUBYLIB" => nil,
36
- "XDG_CONFIG_HOME" => File.join(dir, "config"), "NO_COLOR" => "1" }
36
+ env = environment.merge("GEM_HOME" => home, "GEM_PATH" => home, "RUBYOPT" => nil, "RUBYLIB" => nil,
37
+ "BUNDLER_SETUP" => nil, "RUBYGEMS_GEMDEPS" => nil,
38
+ "XDG_CONFIG_HOME" => File.join(dir, "config"), "NO_COLOR" => "1")
37
39
  # GEM_HOME selects the destination. --install-dir would make RubyGems
38
40
  # ignore installed specifications, including Ruby's uncached default gems.
39
41
  execute(env, [*gem_command, "install", "--local", "--no-document", "--norc",
@@ -70,6 +72,16 @@ module RubyRepoKit
70
72
 
71
73
  private
72
74
 
75
+ def validated_environment(environment)
76
+ valid = environment.is_a?(Hash) && environment.all? do |key, value|
77
+ key.is_a?(String) && key.match?(/\A[A-Za-z_][A-Za-z0-9_]*\z/) &&
78
+ (value.nil? || (value.is_a?(String) && !value.include?("\0")))
79
+ end
80
+ raise Error, "Package environment must map variable names to strings or nil" unless valid
81
+
82
+ environment.to_h { |key, value| [key.dup, value&.dup] }
83
+ end
84
+
73
85
  def specification
74
86
  spec = Specification.load(project: @project)
75
87
  raise Error, "Gemspec name differs from project name" unless spec.name == @project.name
@@ -10,10 +10,11 @@ module RubyRepoKit
10
10
  DEFAULT_CHECKS = ["quality", "commits", "audit", "fresh-dependencies", "test (ubuntu-latest, 3.4)",
11
11
  "test (ubuntu-latest, 4.0)", "test (macos-latest, 4.0)"].freeze
12
12
  KEYS = %w[schema name repository version_file gemspec changelog generated_paths
13
- generate_command check_command required_checks workflow environment].freeze
13
+ generate_command check_command required_checks workflow environment
14
+ protect_hotfix_branches require_review_thread_resolution].freeze
14
15
  attr_reader :root, :name, :repository, :version_file, :gemspec, :changelog,
15
16
  :generated_paths, :generate_command, :check_command, :required_checks,
16
- :workflow, :environment
17
+ :workflow, :environment, :protect_hotfix_branches, :require_review_thread_resolution
17
18
 
18
19
  def self.load(root: Dir.pwd)
19
20
  source = File.join(root, CONFIG_FILE)
@@ -25,9 +26,7 @@ module RubyRepoKit
25
26
  end
26
27
 
27
28
  def initialize(root:, settings:)
28
- raise Error, "Project configuration must be a mapping" unless settings.is_a?(Hash)
29
- raise Error, "Unknown project settings: #{settings.keys - KEYS}" unless (settings.keys - KEYS).empty?
30
- raise Error, "Unsupported configuration schema (expected 1)" unless settings.fetch("schema", nil) == 1
29
+ validate_settings(settings)
31
30
 
32
31
  @root = File.realpath(root).freeze
33
32
  @name = identifier(settings["name"], /\A[a-z][a-z0-9]*(?:[-_][a-z0-9]+)*\z/, "gem name")
@@ -43,6 +42,8 @@ module RubyRepoKit
43
42
  @workflow = identifier(settings.fetch("workflow", "release.yml"), /\A[a-zA-Z0-9_-]+\.ya?ml\z/,
44
43
  "workflow filename")
45
44
  @environment = identifier(settings.fetch("environment", "release"), /\A[a-zA-Z0-9_-]+\z/, "environment")
45
+ @protect_hotfix_branches = boolean_setting(settings, "protect_hotfix_branches")
46
+ @require_review_thread_resolution = boolean_setting(settings, "require_review_thread_resolution")
46
47
  freeze
47
48
  end
48
49
 
@@ -67,6 +68,19 @@ module RubyRepoKit
67
68
 
68
69
  private
69
70
 
71
+ def validate_settings(settings)
72
+ raise Error, "Project configuration must be a mapping" unless settings.is_a?(Hash)
73
+ raise Error, "Unknown project settings: #{settings.keys - KEYS}" unless (settings.keys - KEYS).empty?
74
+ raise Error, "Unsupported configuration schema (expected 1)" unless settings.fetch("schema", nil) == 1
75
+ end
76
+
77
+ def boolean_setting(settings, name)
78
+ value = settings.fetch(name, true)
79
+ raise Error, "#{name} must be true or false" unless [true, false].include?(value)
80
+
81
+ value
82
+ end
83
+
70
84
  def identifier(value, pattern, description)
71
85
  raise Error, "Invalid #{description}: #{value.inspect}" unless value.is_a?(String) && pattern.match?(value)
72
86
 
@@ -10,10 +10,7 @@ module RubyRepoKit
10
10
  extend Rake::DSL
11
11
 
12
12
  def self.install(project:)
13
- if Rake::Task.task_defined?("release")
14
- raise Error,
15
- "A release task already exists; remove competing release tooling before installing RubyRepoKit tasks"
16
- end
13
+ install_release(project: project)
17
14
 
18
15
  desc "Validate local repository maintenance contracts"
19
16
  task "repo:check" do
@@ -43,11 +40,15 @@ module RubyRepoKit
43
40
  sh "bundle", "exec", "bundler-audit", "check", "--update"
44
41
  end
45
42
 
46
- install_release(project)
47
43
  install_github(project)
48
44
  end
49
45
 
50
- def self.install_release(project)
46
+ def self.install_release(project:)
47
+ if Rake::Task.task_defined?("release")
48
+ raise Error,
49
+ "A release task already exists; remove competing release tooling before installing RubyRepoKit tasks"
50
+ end
51
+
51
52
  require_relative "release"
52
53
 
53
54
  desc "Verify the release tag, version and changelog"
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyRepoKit
4
- VERSION = "0.1.0"
4
+ VERSION = "0.1.1"
5
5
  end
@@ -4,11 +4,8 @@ All notable user-visible changes are documented here.
4
4
 
5
5
  ## [Unreleased]
6
6
 
7
- ## [0.1.0] - <%= Time.now.strftime("%Y-%m-%d") %>
8
-
9
7
  ### Added
10
8
 
11
9
  - Initial text-printing CLI with help, version and predictable exit statuses.
12
10
 
13
- [Unreleased]: https://github.com/<%= @repository %>/compare/v0.1.0...HEAD
14
- [0.1.0]: https://github.com/<%= @repository %>/releases/tag/v0.1.0
11
+ [Unreleased]: https://github.com/<%= @repository %>/commits/main
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-repo-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.0
4
+ version: 0.1.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Highlander Paiva
@@ -174,6 +174,7 @@ files:
174
174
  - lib/ruby_repo_kit.rb
175
175
  - lib/ruby_repo_kit/checks.rb
176
176
  - lib/ruby_repo_kit/checks/commits.rb
177
+ - lib/ruby_repo_kit/checks/lockfile.rb
177
178
  - lib/ruby_repo_kit/cli.rb
178
179
  - lib/ruby_repo_kit/commands.rb
179
180
  - lib/ruby_repo_kit/github.rb