Class: GemKit::Release::Project

Inherits:
Object
  • Object
show all
Defined in:
lib/gem_kit/release/project.rb

Overview

What the tooling needs to know about the project it is running in.

Almost all of it is already declared in the project's .gemspec — name, version, and (through require_relative) where the version constant lives. So the normal case is no configuration at all: point it at a directory and it works out the rest.

project = GemKit::Release::Project.detect
project.name        # => "brute"
project.version     # => Gem::Version.new("4.1.0")
project.version_file # => ".../lib/brute/version.rb"

Anything that cannot be inferred is an override:

GemKit::Release.configure do |config|
config.changelog = "HISTORY.md"
config.test_command = "bin/test"
end

Defined Under Namespace

Classes: Ambiguous, Config, NotFound

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(gemspec_path, config: GemKit::Release.config) ⇒ Project

Returns a new instance of Project.



63
64
65
66
67
# File 'lib/gem_kit/release/project.rb', line 63

def initialize(gemspec_path, config: GemKit::Release.config)
  @gemspec_path = File.expand_path(gemspec_path)
  @root         = File.dirname(@gemspec_path)
  @config       = config
end

Instance Attribute Details

#configObject (readonly)

Returns the value of attribute config.



61
62
63
# File 'lib/gem_kit/release/project.rb', line 61

def config
  @config
end

#gemspec_pathObject (readonly)

Returns the value of attribute gemspec_path.



61
62
63
# File 'lib/gem_kit/release/project.rb', line 61

def gemspec_path
  @gemspec_path
end

#rootObject (readonly)

Returns the value of attribute root.



61
62
63
# File 'lib/gem_kit/release/project.rb', line 61

def root
  @root
end

Class Method Details

.detect(dir = Dir.pwd, name: nil, config: GemKit::Release.config) ⇒ Object

name picks one gem out of a repository that holds several — matched against the gemspec's filename, so --gem gem_kit finds gem_kit.gemspec and not gem_kit-release.gemspec.

Raises:



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
# File 'lib/gem_kit/release/project.rb', line 39

def self.detect(dir = Dir.pwd, name: nil, config: GemKit::Release.config)
  gemspecs = Dir[File.join(dir, "*.gemspec")].sort
  raise NotFound, "no .gemspec in #{dir}" if gemspecs.empty?

  if name
    match = gemspecs.find { |path| File.basename(path, ".gemspec") == name }
    raise NotFound, "no #{name}.gemspec in #{dir}" if match.nil?

    return new(match, config: config)
  end

  # Silently picking the first would mean bumping the wrong gem's version
  # and only finding out on release.
  if gemspecs.size > 1
    names = gemspecs.map { |path| File.basename(path, ".gemspec") }
    raise Ambiguous, "#{gemspecs.size} gemspecs in #{dir} (#{names.join(", ")}); " \
                     "name one with --gem"
  end

  new(gemspecs.first, config: config)
end

Instance Method Details

#changelog_pathObject

One changelog per gem. A shared file cannot work: the release gate asks whether the version being cut is the topmost released section, and two gems interleaved in one file means the older one never is — so the second gem could never be released.



100
101
102
# File 'lib/gem_kit/release/project.rb', line 100

def changelog_path
  File.expand_path(config.changelog || default_changelog, root)
end

#changelog_writerObject



160
# File 'lib/gem_kit/release/project.rb', line 160

def changelog_writer = config.changelog_writer || "claude"

#default_changelogObject



104
105
106
# File 'lib/gem_kit/release/project.rb', line 104

def default_changelog
  multi_gem? ? "CHANGELOG-#{name}.md" : "CHANGELOG.md"
end

#load!Object

Load the library, so Deprecate's registry reflects this project.



148
149
150
151
152
153
154
155
156
# File 'lib/gem_kit/release/project.rb', line 148

def load!
  $LOAD_PATH.unshift(File.join(root, "lib")) unless $LOAD_PATH.include?(File.join(root, "lib"))
  require require_path
  true
rescue LoadError => error
  warn "gem_kit-release: could not require #{require_path.inspect} (#{error.message});" \
       " deprecations will not be detected"
  false
end

#lockfile_pathObject



108
109
110
# File 'lib/gem_kit/release/project.rb', line 108

def lockfile_path
  File.join(root, "Gemfile.lock")
end

#multi_gem?Boolean

Returns:

  • (Boolean)


94
# File 'lib/gem_kit/release/project.rb', line 94

def multi_gem? = siblings > 1

#nameObject



76
# File 'lib/gem_kit/release/project.rb', line 76

def name = spec.name

#next_major_versionObject

The next major version — the usual deadline for a deprecation. Written "5.0" rather than "5": it reads as a version in prose and in a declaration, and Gem::Version treats the two as equal anyway.



83
84
85
# File 'lib/gem_kit/release/project.rb', line 83

def next_major_version
  "#{version.segments.first.to_i + 1}.0"
end

#require_pathObject

What to require so that deprecation declarations register themselves. "gem_kit-release" -> "gem_kit/release".



143
144
145
# File 'lib/gem_kit/release/project.rb', line 143

def require_path
  config.require_path || name.tr("-", "/")
end

#self_locked?Boolean

A Gemfile that says gemspec or path: "." puts this gem in its own bundle, and the lockfile then records this gem's version. Bumping without relocking leaves the two disagreeing — which bundler reports as "the gemspecs for path gems changed", and refuses outright under frozen mode, as bundlerEnv sets.

Returns:

  • (Boolean)


117
118
119
120
121
# File 'lib/gem_kit/release/project.rb', line 117

def self_locked?
  return false unless File.exist?(lockfile_path)

  File.read(lockfile_path).match?(/^\s{4}#{Regexp.escape(name)} \(\d/)
end

#siblingsObject

Every gemspec beside this one, this one included. A repository with more than one releases each gem on its own schedule, and several things below have to know that.



90
91
92
# File 'lib/gem_kit/release/project.rb', line 90

def siblings
  @siblings ||= Dir[File.join(root, "*.gemspec")].size
end

#specObject

The evaluated gemspec. Loaded from the project root, because a gemspec typically require_relative's its own version file.



71
72
73
74
# File 'lib/gem_kit/release/project.rb', line 71

def spec
  @spec ||= Dir.chdir(root) { Gem::Specification.load(gemspec_path) } or
    raise NotFound, "could not load #{gemspec_path}"
end

#tag_prefixObject

v1.2.3 says which version but not which gem, which is fine until a repository holds two of them at the same version.



125
126
127
# File 'lib/gem_kit/release/project.rb', line 125

def tag_prefix
  multi_gem? ? "#{name}-v" : "v"
end

#test_commandObject



158
# File 'lib/gem_kit/release/project.rb', line 158

def test_command = config.test_command || "bin/test"

#versionObject



78
# File 'lib/gem_kit/release/project.rb', line 78

def version = Gem::Version.new(spec.version.to_s)

#version_fileObject

lib/gem_kit/release/version.rb for "gem_kit-release", lib/brute/version.rb for "brute" — the convention every bundle gem project follows.



131
132
133
# File 'lib/gem_kit/release/project.rb', line 131

def version_file
  File.expand_path(config.version_file || File.join("lib", *name.split("-"), "version.rb"), root)
end

#version_templateObject

An ERB template beside the version file, if the project generates it.



136
137
138
139
# File 'lib/gem_kit/release/project.rb', line 136

def version_template
  candidate = "#{version_file}.erb"
  candidate if File.exist?(candidate)
end