gempilot 0.2.1 → 0.2.3
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 +4 -4
- data/.rubocop.yml +16 -1
- data/CLAUDE.md +2 -9
- data/issues.rec +29 -2
- data/lib/gempilot/cli/commands/console.rb +2 -3
- data/lib/gempilot/cli/commands/create.rb +6 -3
- data/lib/gempilot/cli/commands/destroy.rb +8 -23
- data/lib/gempilot/cli/commands/new.rb +20 -39
- data/lib/gempilot/cli/commands/release.rb +2 -3
- data/lib/gempilot/cli/gem_context.rb +9 -13
- data/lib/gempilot/cli/generator.rb +2 -1
- data/lib/gempilot/gem_constant.rb +61 -0
- data/lib/gempilot/project.rb +25 -5
- data/lib/gempilot/version.rb +1 -1
- data/lib/gempilot/version_tag.rb +6 -2
- data/vendor/vendored.gemv +0 -0
- metadata +5 -13
- data/docs/command_kit_comparison.md +0 -249
- data/docs/command_kit_reference.md +0 -517
- data/docs/plans/2026-02-18-gempilot-add-command.md +0 -718
- data/docs/superpowers/plans/2026-04-01-rubocop-new-config.md +0 -838
- data/docs/superpowers/plans/2026-04-06-dogfood-inflectable.md +0 -659
- data/docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md +0 -166
- data/docs/superpowers/plans/2026-04-06-integrate-version-tools.md +0 -162
- data/docs/superpowers/plans/2026-04-06-new-readme.md +0 -185
- data/docs/version-management-redesign.md +0 -44
- data/notes.md +0 -31
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b4b895f3da5d0d8866cd13f835dd3c1ca71d8d433571c2d7531735636eeb6ad0
|
|
4
|
+
data.tar.gz: 878dad31a22bdecdb9ff607ebecf2330432070219edfc3e08b66a5022c205d77
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: ccfc9410fe3ccd3cc8f49e3ca34f68ef334a50183eb95d0788f09c9a5e972fcb4b1244cdfeee188b9898e25ba1c4542d6c4cf24ee24e54edebd4e8b8e907f6a1
|
|
7
|
+
data.tar.gz: e7deb74172aa7bcd75d84f41ad2d80022d9befcd7f1b69990c6f8b9fe6002168d8b25581242dbded279bdd601986c24552b1431573ff0ed62bec8316734e50fe
|
data/.rubocop.yml
CHANGED
|
@@ -15,10 +15,11 @@ plugins:
|
|
|
15
15
|
- rubocop-performance
|
|
16
16
|
- rubocop-rake
|
|
17
17
|
- rubocop-rspec
|
|
18
|
+
- rubocop-design
|
|
18
19
|
|
|
19
20
|
AllCops:
|
|
20
21
|
NewCops: enable
|
|
21
|
-
TargetRubyVersion:
|
|
22
|
+
TargetRubyVersion: 4.0
|
|
22
23
|
Exclude:
|
|
23
24
|
- bin/*
|
|
24
25
|
- vendor/**/*
|
|
@@ -279,3 +280,17 @@ RSpec/NamedSubject:
|
|
|
279
280
|
# have_file_content matcher accepts a filename string in expect()
|
|
280
281
|
RSpec/ExpectActual:
|
|
281
282
|
Enabled: false
|
|
283
|
+
|
|
284
|
+
Design/PositionalArguments:
|
|
285
|
+
Enabled: false
|
|
286
|
+
|
|
287
|
+
Design/KeywordArguments:
|
|
288
|
+
Enabled: false
|
|
289
|
+
|
|
290
|
+
Design/TotalArguments:
|
|
291
|
+
Enabled: false
|
|
292
|
+
|
|
293
|
+
Design/AgentNounClassName:
|
|
294
|
+
Exclude:
|
|
295
|
+
- 'lib/gempilot/generator.rb'
|
|
296
|
+
- 'test/gempilot/cli/generator_test.rb'
|
data/CLAUDE.md
CHANGED
|
@@ -20,6 +20,7 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
|
|
|
20
20
|
- Uses CommandKit::Inflector for name inflection
|
|
21
21
|
- Generator module (`lib/gempilot/cli/generator.rb`) provides template rendering via ERB
|
|
22
22
|
- GemContext module (`lib/gempilot/cli/gem_context.rb`) shared by new, destroy, release, console
|
|
23
|
+
- `GemConstant` value object (`lib/gempilot/gem_constant.rb`) owns constant→namespace/path resolution for `new`/`destroy`; constants are rooted at the gem module by construction
|
|
23
24
|
- CommandKit::Commands::AutoLoad maps filenames in `commands/` to command names
|
|
24
25
|
|
|
25
26
|
### Testing
|
|
@@ -34,12 +35,4 @@ A CLI tool for creating and managing Ruby gems, built on CommandKit.
|
|
|
34
35
|
- RuboCop with framework-specific plugins
|
|
35
36
|
- GitHub Actions CI workflow (`.github/workflows/ci.yml`)
|
|
36
37
|
- `git ls-files`-based gemspec with glob fallback for non-git repos
|
|
37
|
-
- Version
|
|
38
|
-
|
|
39
|
-
### Notes
|
|
40
|
-
- AutoLoad uses block form in `cli.rb` with explicit `summary:` per command (lazy loading means descriptions aren't available at help time without this)
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
## ISSUES
|
|
44
|
-
|
|
45
|
-
1. RESOLVED — `exe/gempilot` had unconditional `ENV["BUNDLE_GEMFILE"]` and `require "bundler/setup"`, causing `Gemfile not found` after `gem install`. Removed both lines; RubyGems handles load paths for installed gems. (Ronin avoids this with a conditional `Gemfile.lock` check, but gempilot's `exe/`+`bin/` separation makes that unnecessary.)
|
|
38
|
+
- Version lifecycle rake tasks installed via `Gempilot::VersionTask.new` (a `Rake::TaskLib`): `version:current/bump/commit/tag/untag/reset/revert`, composite `version:release`/`version:unrelease`, and `version:github:release/unrelease/list`
|
data/issues.rec
CHANGED
|
@@ -26,7 +26,7 @@ Id: F0833E66-A35C-4054-884B-F90C81F08838
|
|
|
26
26
|
Updated: Mon, 02 Mar 2026 10:37:33 -0500
|
|
27
27
|
Title: Error handling should be graceful
|
|
28
28
|
Description: I did "gempilot create", and entered "A foo" as the title, and it crashed horrifically. This particular example should have validation at the minimum. Need to also use command kit's error bubbling/handling mechanism to swallow errors and print message to user. Can always use verbose or debug mode to see full trace.
|
|
29
|
-
Status:
|
|
29
|
+
Status: closed
|
|
30
30
|
|
|
31
31
|
Id: 741FFD05-9BD7-44EB-BB99-E7D705F12681
|
|
32
32
|
Updated: Mon, 02 Mar 2026 11:25:24 -0500
|
|
@@ -68,7 +68,7 @@ Id: 74D0FD90-0421-4452-8591-2798EE6FD23E
|
|
|
68
68
|
Updated: Sat, 28 Mar 2026 12:59:40 -0400
|
|
69
69
|
Title: Publish gem
|
|
70
70
|
Description: At some point the repo should be moved to public viz, and the gem should be published.
|
|
71
|
-
Status:
|
|
71
|
+
Status: closed
|
|
72
72
|
|
|
73
73
|
Id: 8C356024-3122-11F1-89F3-FE6CB9572C2F
|
|
74
74
|
Updated: Mon, 06 Apr 2026 00:00:00 -0400
|
|
@@ -141,3 +141,30 @@ Description: "
|
|
|
141
141
|
+ ```
|
|
142
142
|
+ "
|
|
143
143
|
Status: closed
|
|
144
|
+
|
|
145
|
+
Id: 1C46F71C-70E9-11F1-B65B-FE6CB9572C2E
|
|
146
|
+
Updated: Thu, 25 Jun 2026 18:56:32 -0400
|
|
147
|
+
Title: Incorrectly creates test file
|
|
148
|
+
Description: When doing `gempilot new Reversal::Server`, in a project called "reversal-store", I get:
|
|
149
|
+
+
|
|
150
|
+
+ gempilot new class Reversal::Server
|
|
151
|
+
+
|
|
152
|
+
+ Adding class Reversal::Server...
|
|
153
|
+
+
|
|
154
|
+
+ create lib/reversal/server.rb
|
|
155
|
+
+ create test/reversal/store_test.rb
|
|
156
|
+
+
|
|
157
|
+
+ The test file is incorrectly named. Should be server_test.rb
|
|
158
|
+
Status: open
|
|
159
|
+
|
|
160
|
+
Id: F5486B48-71DB-11F1-980F-FE6CB9572C2E
|
|
161
|
+
Updated: Fri, 26 Jun 2026 23:54:55 -0400
|
|
162
|
+
Title: Rubymine <=> Minitest usage causes "no reporters allowed error"
|
|
163
|
+
Description: Has to do with how the ENV var RM_INFO is being interpreted
|
|
164
|
+
Status: open
|
|
165
|
+
|
|
166
|
+
Id: 8D847572-7273-11F1-B508-FE6CB9572C2E
|
|
167
|
+
Updated: Sat, 27 Jun 2026 18:00:04 -0400
|
|
168
|
+
Title: Gem extensions are broken
|
|
169
|
+
Description: when using gempilot on a gem extension, like 'foo-support', using idiomatic ruby patterns for dir structure, gempilot Project class throws an error
|
|
170
|
+
Status: open
|
|
@@ -27,9 +27,8 @@ module Gempilot
|
|
|
27
27
|
end
|
|
28
28
|
|
|
29
29
|
def print_console_banner
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
colors.bright_white("...")
|
|
30
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
31
|
+
puts colors.bright_white("Starting console for ") + styled_name + colors.bright_white("...")
|
|
33
32
|
puts
|
|
34
33
|
end
|
|
35
34
|
end
|
|
@@ -156,7 +156,8 @@ module Gempilot
|
|
|
156
156
|
|
|
157
157
|
puts
|
|
158
158
|
puts colors.bright_black("An executable in exe/ lets users run your gem from the command line.")
|
|
159
|
-
|
|
159
|
+
prompt = colors.green("Create an executable")
|
|
160
|
+
options[:exe] = ask_yes_or_no(prompt, default: false)
|
|
160
161
|
end
|
|
161
162
|
|
|
162
163
|
def collect_git_options
|
|
@@ -169,7 +170,8 @@ module Gempilot
|
|
|
169
170
|
|
|
170
171
|
puts
|
|
171
172
|
puts colors.bright_black("Initialize a git repository with an initial commit.")
|
|
172
|
-
|
|
173
|
+
prompt = colors.green("Initialize git repo")
|
|
174
|
+
options[:git] = ask_yes_or_no(prompt, default: true)
|
|
173
175
|
end
|
|
174
176
|
|
|
175
177
|
def collect_branch
|
|
@@ -181,8 +183,9 @@ module Gempilot
|
|
|
181
183
|
end
|
|
182
184
|
|
|
183
185
|
def print_header
|
|
186
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
184
187
|
puts
|
|
185
|
-
puts colors.bright_white("Creating gem '") +
|
|
188
|
+
puts colors.bright_white("Creating gem '") + styled_name + colors.bright_white("'...")
|
|
186
189
|
puts
|
|
187
190
|
end
|
|
188
191
|
|
|
@@ -27,7 +27,7 @@ module Gempilot
|
|
|
27
27
|
def run(type = nil, path = nil)
|
|
28
28
|
type ||= prompt_for_type
|
|
29
29
|
detect_gem_context
|
|
30
|
-
path ||= prompt_for_path
|
|
30
|
+
path ||= prompt_for_path(type)
|
|
31
31
|
dispatch_destroy(type, path)
|
|
32
32
|
end
|
|
33
33
|
|
|
@@ -38,16 +38,14 @@ module Gempilot
|
|
|
38
38
|
ask_multiple_choice(colors.green("Type"), %w[class module command])
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def prompt_for_path
|
|
42
|
-
|
|
43
|
-
puts colors.bright_black("Fully-qualified constant name (e.g., #{@gem_module}::Services::Authentication).")
|
|
44
|
-
ask(colors.green("Constant"), required: true)
|
|
41
|
+
def prompt_for_path(type)
|
|
42
|
+
ask(colors.green(type), required: true)
|
|
45
43
|
end
|
|
46
44
|
|
|
47
45
|
def dispatch_destroy(type, path)
|
|
48
46
|
case type
|
|
49
|
-
when "class" then destroy_class(path)
|
|
50
|
-
when "module" then destroy_module(path)
|
|
47
|
+
when "class" then destroy_class(gem_constant(path))
|
|
48
|
+
when "module" then destroy_module(gem_constant(path))
|
|
51
49
|
when "command" then destroy_command(path)
|
|
52
50
|
else
|
|
53
51
|
puts colors.red("Unknown type '#{type}'. Use class, module, or command.")
|
|
@@ -56,25 +54,14 @@ module Gempilot
|
|
|
56
54
|
end
|
|
57
55
|
|
|
58
56
|
def destroy_class(constant)
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
lib_path = "#{File.join("lib", *segments)}.rb"
|
|
63
|
-
test_path = test_path_for(segments)
|
|
57
|
+
lib_path = constant.lib_path
|
|
58
|
+
test_path = constant.test_path(@test_framework)
|
|
64
59
|
|
|
65
60
|
remove_file(lib_path)
|
|
66
61
|
remove_file(test_path)
|
|
67
62
|
cleanup_empty_dirs(lib_path, test_path)
|
|
68
63
|
end
|
|
69
64
|
|
|
70
|
-
def test_path_for(segments)
|
|
71
|
-
if @test_framework == :rspec
|
|
72
|
-
"#{File.join("spec", @require_path, *segments[1..])}_spec.rb"
|
|
73
|
-
else
|
|
74
|
-
"#{File.join("test", @require_path, *segments[1..])}_test.rb"
|
|
75
|
-
end
|
|
76
|
-
end
|
|
77
|
-
|
|
78
65
|
def cleanup_empty_dirs(lib_path, test_path)
|
|
79
66
|
remove_empty_parents(File.dirname(lib_path), File.join("lib", @require_path))
|
|
80
67
|
test_root = @test_framework == :rspec ? File.join("spec", @require_path) : File.join("test", @require_path)
|
|
@@ -82,10 +69,8 @@ module Gempilot
|
|
|
82
69
|
end
|
|
83
70
|
|
|
84
71
|
def destroy_module(constant)
|
|
85
|
-
|
|
86
|
-
validate_gem_root!(namespaces.first)
|
|
72
|
+
lib_path = constant.lib_path
|
|
87
73
|
|
|
88
|
-
lib_path = "#{File.join("lib", *segments)}.rb"
|
|
89
74
|
remove_file(lib_path)
|
|
90
75
|
remove_empty_parents(File.dirname(lib_path), File.join("lib", @require_path))
|
|
91
76
|
end
|
|
@@ -29,7 +29,7 @@ module Gempilot
|
|
|
29
29
|
def run(type = nil, path = nil)
|
|
30
30
|
type ||= prompt_for_type
|
|
31
31
|
detect_gem_context
|
|
32
|
-
path ||= prompt_for_path
|
|
32
|
+
path ||= prompt_for_path(type)
|
|
33
33
|
dispatch_add(type, path)
|
|
34
34
|
end
|
|
35
35
|
|
|
@@ -40,16 +40,14 @@ module Gempilot
|
|
|
40
40
|
ask_multiple_choice(colors.green("Type"), %w[class module command])
|
|
41
41
|
end
|
|
42
42
|
|
|
43
|
-
def prompt_for_path
|
|
44
|
-
|
|
45
|
-
puts colors.bright_black("Fully-qualified constant name (e.g., #{@gem_module}::Services::Authentication).")
|
|
46
|
-
ask(colors.green("Constant"), required: true)
|
|
43
|
+
def prompt_for_path(type)
|
|
44
|
+
ask(colors.green(type), required: true)
|
|
47
45
|
end
|
|
48
46
|
|
|
49
47
|
def dispatch_add(type, path)
|
|
50
48
|
case type
|
|
51
|
-
when "class" then add_class(path)
|
|
52
|
-
when "module" then add_module(path)
|
|
49
|
+
when "class" then add_class(gem_constant(path))
|
|
50
|
+
when "module" then add_module(gem_constant(path))
|
|
53
51
|
when "command" then add_command(path)
|
|
54
52
|
else
|
|
55
53
|
puts colors.red("Unknown type '#{type}'. Use class, module, or command.")
|
|
@@ -79,38 +77,29 @@ module Gempilot
|
|
|
79
77
|
end
|
|
80
78
|
|
|
81
79
|
def print_adding_banner(kind, label)
|
|
80
|
+
styled_label = colors.bold(colors.cyan(label))
|
|
82
81
|
puts
|
|
83
|
-
puts colors.bright_white("Adding #{kind} ") +
|
|
84
|
-
colors.bold(colors.cyan(label)) +
|
|
85
|
-
colors.bright_white("...")
|
|
82
|
+
puts colors.bright_white("Adding #{kind} ") + styled_label + colors.bright_white("...")
|
|
86
83
|
puts
|
|
87
84
|
end
|
|
88
85
|
|
|
89
|
-
def prepare_constant(constant)
|
|
90
|
-
namespaces, name, segments = parse_constant(constant)
|
|
91
|
-
validate_gem_root!(namespaces.first)
|
|
92
|
-
file_path = "#{File.join("lib", *segments)}.rb"
|
|
93
|
-
ensure_directory(File.dirname(file_path))
|
|
94
|
-
[namespaces, name, segments, file_path]
|
|
95
|
-
end
|
|
96
|
-
|
|
97
86
|
def ensure_directory(dir)
|
|
98
87
|
mkdir(dir) unless File.directory?(dir)
|
|
99
88
|
end
|
|
100
89
|
|
|
101
90
|
def add_class(constant)
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
source = build_nested_source(namespaces, "class",
|
|
105
|
-
create_file(
|
|
106
|
-
add_test_file(
|
|
91
|
+
print_adding_banner("class", constant.qualified)
|
|
92
|
+
ensure_directory(File.dirname(constant.lib_path))
|
|
93
|
+
source = build_nested_source(constant.namespaces, "class", constant.name)
|
|
94
|
+
create_file(constant.lib_path, source)
|
|
95
|
+
add_test_file(constant)
|
|
107
96
|
end
|
|
108
97
|
|
|
109
98
|
def add_module(constant)
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
source = build_nested_source(namespaces, "module",
|
|
113
|
-
create_file(
|
|
99
|
+
print_adding_banner("module", constant.qualified)
|
|
100
|
+
ensure_directory(File.dirname(constant.lib_path))
|
|
101
|
+
source = build_nested_source(constant.namespaces, "module", constant.name)
|
|
102
|
+
create_file(constant.lib_path, source)
|
|
114
103
|
end
|
|
115
104
|
|
|
116
105
|
def add_command(name)
|
|
@@ -179,14 +168,6 @@ module Gempilot
|
|
|
179
168
|
create_file(test_path, content)
|
|
180
169
|
end
|
|
181
170
|
|
|
182
|
-
def class_test_path(segments)
|
|
183
|
-
if @test_framework == :rspec
|
|
184
|
-
"#{File.join("spec", @require_path, *segments[1..])}_spec.rb"
|
|
185
|
-
else
|
|
186
|
-
"#{File.join("test", @require_path, *segments[1..])}_test.rb"
|
|
187
|
-
end
|
|
188
|
-
end
|
|
189
|
-
|
|
190
171
|
def rspec_class_content(namespaces, class_name)
|
|
191
172
|
<<~RUBY
|
|
192
173
|
require "spec_helper"
|
|
@@ -211,13 +192,13 @@ module Gempilot
|
|
|
211
192
|
RUBY
|
|
212
193
|
end
|
|
213
194
|
|
|
214
|
-
def add_test_file(
|
|
215
|
-
test_path =
|
|
195
|
+
def add_test_file(constant)
|
|
196
|
+
test_path = constant.test_path(@test_framework)
|
|
216
197
|
ensure_directory(File.dirname(test_path))
|
|
217
198
|
content = if @test_framework == :rspec
|
|
218
|
-
rspec_class_content(namespaces,
|
|
199
|
+
rspec_class_content(constant.namespaces, constant.name)
|
|
219
200
|
else
|
|
220
|
-
minitest_class_content(namespaces,
|
|
201
|
+
minitest_class_content(constant.namespaces, constant.name)
|
|
221
202
|
end
|
|
222
203
|
create_file(test_path, content)
|
|
223
204
|
end
|
|
@@ -18,9 +18,8 @@ module Gempilot
|
|
|
18
18
|
private
|
|
19
19
|
|
|
20
20
|
def print_release_banner
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
colors.bright_white("...")
|
|
21
|
+
styled_name = colors.bold(colors.cyan(@gem_name))
|
|
22
|
+
puts colors.bright_white("Releasing ") + styled_name + colors.bright_white("...")
|
|
24
23
|
puts
|
|
25
24
|
end
|
|
26
25
|
|
|
@@ -17,23 +17,19 @@ module Gempilot
|
|
|
17
17
|
@gem_name = File.basename(gemspec, ".gemspec")
|
|
18
18
|
@require_path = @gem_name.tr("-", "/")
|
|
19
19
|
@gem_module = @require_path.camelize
|
|
20
|
-
@test_framework =
|
|
20
|
+
@test_framework = detect_test_framework
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
segments = parts.map(&:underscore)
|
|
28
|
-
[namespaces, name, segments]
|
|
29
|
-
end
|
|
23
|
+
# Detect rspec by its canonical config files rather than the mere
|
|
24
|
+
# presence of a spec/ directory, which a minitest project may also have.
|
|
25
|
+
def detect_test_framework
|
|
26
|
+
return :rspec if File.exist?(".rspec") || File.exist?(File.join("spec", "spec_helper.rb"))
|
|
30
27
|
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
return if root == expected
|
|
28
|
+
:minitest
|
|
29
|
+
end
|
|
34
30
|
|
|
35
|
-
|
|
36
|
-
|
|
31
|
+
def gem_constant(input)
|
|
32
|
+
GemConstant.new(input: input, gem_module: @gem_module, require_path: @require_path)
|
|
37
33
|
end
|
|
38
34
|
end
|
|
39
35
|
end
|
|
@@ -37,8 +37,9 @@ module Gempilot
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
def print_action(command, dest, source: nil)
|
|
40
|
+
styled_command = colors.bold(colors.green(command))
|
|
40
41
|
line = +""
|
|
41
|
-
line << "\t" <<
|
|
42
|
+
line << "\t" << styled_command
|
|
42
43
|
line << "\t" << colors.green(source) if source
|
|
43
44
|
line << "\t" << colors.green(dest) if dest
|
|
44
45
|
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
module Gempilot
|
|
2
|
+
## A class or module constant resolved within a gem's namespace.
|
|
3
|
+
##
|
|
4
|
+
## Wraps raw user input (a bare suffix like +Services::Auth+ or a
|
|
5
|
+
## fully-qualified +MyGem::Services::Auth+) together with the gem's module
|
|
6
|
+
## and require path, and derives the qualified constant, file paths, and
|
|
7
|
+
## namespace pieces from a single parse.
|
|
8
|
+
##
|
|
9
|
+
## Every constant is rooted at the gem's module: bare input is prefixed with
|
|
10
|
+
## it, while input already starting with the gem's root segment is left as
|
|
11
|
+
## is. Rooting is matched on the first segment only, so an extension gem
|
|
12
|
+
## whose module is +My::Gem+ accepts any +My::...+ constant.
|
|
13
|
+
GemConstant = Data.define(:input, :gem_module, :require_path) do
|
|
14
|
+
using String::Inflectable
|
|
15
|
+
|
|
16
|
+
## The fully-qualified constant, rooted at the gem module.
|
|
17
|
+
def qualified
|
|
18
|
+
input.start_with?("#{root_segment}::") ? input : "#{gem_module}::#{input}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
## Namespace segments preceding the final constant name.
|
|
22
|
+
def namespaces
|
|
23
|
+
parts[0...-1]
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
## The final class or module name.
|
|
27
|
+
def name
|
|
28
|
+
parts.last
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
## Path to the constant's source file, e.g. +lib/my_gem/services/auth.rb+.
|
|
32
|
+
def lib_path
|
|
33
|
+
"#{File.join("lib", *path_segments)}.rb"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
## Path to the constant's test file for +framework+ (+:rspec+ or
|
|
37
|
+
## +:minitest+); correct for multi-segment (hyphenated) gem modules.
|
|
38
|
+
def test_path(framework)
|
|
39
|
+
rest = path_segments.drop(require_path.split("/").length)
|
|
40
|
+
if framework == :rspec
|
|
41
|
+
"#{File.join("spec", require_path, *rest)}_spec.rb"
|
|
42
|
+
else
|
|
43
|
+
"#{File.join("test", require_path, *rest)}_test.rb"
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
private
|
|
48
|
+
|
|
49
|
+
def root_segment
|
|
50
|
+
gem_module.split("::").first
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def parts
|
|
54
|
+
qualified.split("::")
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def path_segments
|
|
58
|
+
parts.map(&:underscore)
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
end
|
data/lib/gempilot/project.rb
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
require "pathname"
|
|
2
1
|
require "warning"
|
|
3
2
|
|
|
4
3
|
module Gempilot
|
|
5
4
|
## Introspects a gem project to discover its name, module, and version.
|
|
5
|
+
## Works for both regular gems (+lib/my_gem.rb+) and extension gems whose
|
|
6
|
+
## entry point nests deeper (+lib/my_gem/extension.rb+ for +my_gem-extension+).
|
|
6
7
|
class Project
|
|
7
8
|
class ProjectIntrospectionError < StandardError; end
|
|
8
9
|
|
|
@@ -29,11 +30,11 @@ module Gempilot
|
|
|
29
30
|
end
|
|
30
31
|
|
|
31
32
|
def name
|
|
32
|
-
|
|
33
|
+
project_segments.join("-")
|
|
33
34
|
end
|
|
34
35
|
|
|
35
36
|
def klass
|
|
36
|
-
Object.const_get(
|
|
37
|
+
Object.const_get(project_segments.map(&:camelize).join("::"))
|
|
37
38
|
end
|
|
38
39
|
|
|
39
40
|
def version
|
|
@@ -67,6 +68,10 @@ module Gempilot
|
|
|
67
68
|
|
|
68
69
|
private
|
|
69
70
|
|
|
71
|
+
def project_segments
|
|
72
|
+
lib_project.relative_path_from(lib).each_filename.to_a
|
|
73
|
+
end
|
|
74
|
+
|
|
70
75
|
def with_version_file
|
|
71
76
|
version.path.open(File::RDWR, 0o644) do |f|
|
|
72
77
|
f.flock File::LOCK_EX
|
|
@@ -76,8 +81,7 @@ module Gempilot
|
|
|
76
81
|
end
|
|
77
82
|
|
|
78
83
|
def fetch_lib_project
|
|
79
|
-
|
|
80
|
-
dirs = files.map { it.sub_ext("") }.select(&:directory?)
|
|
84
|
+
dirs = shallowest_entry_dirs
|
|
81
85
|
case dirs.count
|
|
82
86
|
in 0 then raise ProjectIntrospectionError, "Could not identify project dir"
|
|
83
87
|
in (2..)
|
|
@@ -87,6 +91,22 @@ module Gempilot
|
|
|
87
91
|
end
|
|
88
92
|
end
|
|
89
93
|
|
|
94
|
+
def shallowest_entry_dirs
|
|
95
|
+
entry_dirs.group_by { depth_below_lib(it) }
|
|
96
|
+
.min_by(&:first)
|
|
97
|
+
&.last || []
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def entry_dirs
|
|
101
|
+
lib.glob("**/*.rb")
|
|
102
|
+
.map { it.sub_ext("") }
|
|
103
|
+
.select(&:directory?)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
def depth_below_lib(path)
|
|
107
|
+
path.relative_path_from(lib).each_filename.count
|
|
108
|
+
end
|
|
109
|
+
|
|
90
110
|
def fetch_version
|
|
91
111
|
Warning.ignore(REDEFINITION_WARNING)
|
|
92
112
|
Warning.ignore(REINITIALIZATION_WARNING)
|
data/lib/gempilot/version.rb
CHANGED
data/lib/gempilot/version_tag.rb
CHANGED
|
@@ -5,6 +5,10 @@ module Gempilot
|
|
|
5
5
|
class VersionTag
|
|
6
6
|
include StrictShell
|
|
7
7
|
|
|
8
|
+
## Commit-message prefix written for a version bump; the guard below
|
|
9
|
+
## matches on it, so the two must stay in sync.
|
|
10
|
+
BUMP_MESSAGE_PREFIX = "Bump version to ".freeze
|
|
11
|
+
|
|
8
12
|
attr_reader :version
|
|
9
13
|
|
|
10
14
|
def initialize(version)
|
|
@@ -16,7 +20,7 @@ module Gempilot
|
|
|
16
20
|
raise "Cannot proceed, staging area must be clean" unless status.success?
|
|
17
21
|
|
|
18
22
|
sh "git", "add", version.path.to_s
|
|
19
|
-
sh "git", "commit", "-m", "
|
|
23
|
+
sh "git", "commit", "-m", "#{BUMP_MESSAGE_PREFIX}#{version.value}"
|
|
20
24
|
end
|
|
21
25
|
|
|
22
26
|
def tag
|
|
@@ -47,7 +51,7 @@ module Gempilot
|
|
|
47
51
|
raise "Failed to read last commit message" unless status.success?
|
|
48
52
|
|
|
49
53
|
message.strip!
|
|
50
|
-
|
|
54
|
+
raise "Last commit does not appear to be a version bump." unless message.start_with?(BUMP_MESSAGE_PREFIX)
|
|
51
55
|
end
|
|
52
56
|
end
|
|
53
57
|
end
|
|
Binary file
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: gempilot
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.2.
|
|
4
|
+
version: 0.2.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- David Gillis
|
|
@@ -106,15 +106,6 @@ files:
|
|
|
106
106
|
- data/templates/gem/test/zeitwerk_test.rb.erb
|
|
107
107
|
- data/templates/new/.keep
|
|
108
108
|
- data/templates/new/command.rb.erb
|
|
109
|
-
- docs/command_kit_comparison.md
|
|
110
|
-
- docs/command_kit_reference.md
|
|
111
|
-
- docs/plans/2026-02-18-gempilot-add-command.md
|
|
112
|
-
- docs/superpowers/plans/2026-04-01-rubocop-new-config.md
|
|
113
|
-
- docs/superpowers/plans/2026-04-06-dogfood-inflectable.md
|
|
114
|
-
- docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md
|
|
115
|
-
- docs/superpowers/plans/2026-04-06-integrate-version-tools.md
|
|
116
|
-
- docs/superpowers/plans/2026-04-06-new-readme.md
|
|
117
|
-
- docs/version-management-redesign.md
|
|
118
109
|
- exe/gempilot
|
|
119
110
|
- issues.rec
|
|
120
111
|
- lib/core_ext/string/inflection_methods.rb
|
|
@@ -131,6 +122,7 @@ files:
|
|
|
131
122
|
- lib/gempilot/cli/gem_builder.rb
|
|
132
123
|
- lib/gempilot/cli/gem_context.rb
|
|
133
124
|
- lib/gempilot/cli/generator.rb
|
|
125
|
+
- lib/gempilot/gem_constant.rb
|
|
134
126
|
- lib/gempilot/github_release.rb
|
|
135
127
|
- lib/gempilot/project.rb
|
|
136
128
|
- lib/gempilot/project/version.rb
|
|
@@ -138,7 +130,7 @@ files:
|
|
|
138
130
|
- lib/gempilot/version.rb
|
|
139
131
|
- lib/gempilot/version_tag.rb
|
|
140
132
|
- lib/gempilot/version_task.rb
|
|
141
|
-
-
|
|
133
|
+
- vendor/vendored.gemv
|
|
142
134
|
homepage: https://github.com/gillisd/gempilot
|
|
143
135
|
licenses:
|
|
144
136
|
- MIT
|
|
@@ -152,14 +144,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
|
152
144
|
requirements:
|
|
153
145
|
- - ">="
|
|
154
146
|
- !ruby/object:Gem::Version
|
|
155
|
-
version: '
|
|
147
|
+
version: '4.0'
|
|
156
148
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
157
149
|
requirements:
|
|
158
150
|
- - ">="
|
|
159
151
|
- !ruby/object:Gem::Version
|
|
160
152
|
version: '0'
|
|
161
153
|
requirements: []
|
|
162
|
-
rubygems_version: 4.0.
|
|
154
|
+
rubygems_version: 4.0.15
|
|
163
155
|
specification_version: 4
|
|
164
156
|
summary: A toolkit for creating, managing, and releasing your own rubygems
|
|
165
157
|
test_files: []
|