gempilot 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 +7 -0
- data/.claude/skills/using-command-kit/SKILL.md +119 -0
- data/.claude/skills/using-command-kit/cli-example.rb +84 -0
- data/.claude/skills/using-command-kit/generator-pattern.rb +62 -0
- data/.rspec +3 -0
- data/.rubocop.yml +281 -0
- data/.ruby-version +1 -0
- data/CLAUDE.md +45 -0
- data/LICENSE.txt +21 -0
- data/README.md +140 -0
- data/Rakefile +44 -0
- data/data/templates/gem/Gemfile.erb +23 -0
- data/data/templates/gem/LICENSE.txt.erb +21 -0
- data/data/templates/gem/README.md.erb +25 -0
- data/data/templates/gem/Rakefile.erb +36 -0
- data/data/templates/gem/bin/console.erb +7 -0
- data/data/templates/gem/bin/setup.erb +5 -0
- data/data/templates/gem/dotfiles/github/workflows/ci.yml.erb +33 -0
- data/data/templates/gem/dotfiles/gitignore +11 -0
- data/data/templates/gem/dotfiles/rubocop.yml.erb +209 -0
- data/data/templates/gem/dotfiles/ruby-version.erb +1 -0
- data/data/templates/gem/exe/gem_name.erb +3 -0
- data/data/templates/gem/gemspec.erb +27 -0
- data/data/templates/gem/lib/gem_name/version.rb.erb +7 -0
- data/data/templates/gem/lib/gem_name.rb.erb +16 -0
- data/data/templates/gem/lib/gem_name_extension.rb.erb +20 -0
- data/data/templates/gem/rspec.erb +3 -0
- data/data/templates/gem/spec/gem_name_spec.rb.erb +5 -0
- data/data/templates/gem/spec/spec_helper.rb.erb +10 -0
- data/data/templates/gem/spec/zeitwerk_spec.rb.erb +5 -0
- data/data/templates/gem/test/gem_name_test.rb.erb +7 -0
- data/data/templates/gem/test/test_helper.rb.erb +7 -0
- data/data/templates/gem/test/zeitwerk_test.rb.erb +9 -0
- data/data/templates/new/.keep +0 -0
- data/data/templates/new/command.rb.erb +15 -0
- data/docs/command_kit_comparison.md +249 -0
- data/docs/command_kit_reference.md +517 -0
- data/docs/plans/2026-02-18-gempilot-add-command.md +718 -0
- data/docs/superpowers/plans/2026-04-01-rubocop-new-config.md +838 -0
- data/docs/superpowers/plans/2026-04-06-dogfood-inflectable.md +659 -0
- data/docs/superpowers/plans/2026-04-06-inflection-tests-and-erb-rename.md +166 -0
- data/docs/superpowers/plans/2026-04-06-integrate-version-tools.md +162 -0
- data/docs/superpowers/plans/2026-04-06-new-readme.md +185 -0
- data/docs/version-management-redesign.md +44 -0
- data/exe/gempilot +12 -0
- data/issues.rec +77 -0
- data/lib/core_ext/string/inflection_methods.rb +68 -0
- data/lib/core_ext/string/refinements/inflectable.rb +15 -0
- data/lib/gempilot/cli/command.rb +17 -0
- data/lib/gempilot/cli/commands/bump.rb +49 -0
- data/lib/gempilot/cli/commands/console.rb +38 -0
- data/lib/gempilot/cli/commands/create.rb +183 -0
- data/lib/gempilot/cli/commands/destroy.rb +136 -0
- data/lib/gempilot/cli/commands/new.rb +226 -0
- data/lib/gempilot/cli/commands/release.rb +40 -0
- data/lib/gempilot/cli/gem_builder.rb +105 -0
- data/lib/gempilot/cli/gem_context.rb +40 -0
- data/lib/gempilot/cli/generator.rb +90 -0
- data/lib/gempilot/cli.rb +19 -0
- data/lib/gempilot/github_release.rb +30 -0
- data/lib/gempilot/project/version.rb +39 -0
- data/lib/gempilot/project.rb +111 -0
- data/lib/gempilot/strict_shell.rb +18 -0
- data/lib/gempilot/version.rb +3 -0
- data/lib/gempilot/version_tag.rb +53 -0
- data/lib/gempilot/version_task.rb +108 -0
- data/lib/gempilot.rb +17 -0
- data/notes.md +31 -0
- metadata +165 -0
|
@@ -0,0 +1,659 @@
|
|
|
1
|
+
# Dogfood String::Inflectable Implementation Plan
|
|
2
|
+
|
|
3
|
+
> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.
|
|
4
|
+
|
|
5
|
+
**Goal:** Replace `CommandKit::Inflector` with gempilot's own `String::Inflectable` refinement, and wire the template generator to copy all version management domain objects into generated gems.
|
|
6
|
+
|
|
7
|
+
**Architecture:** Create `String::InflectionMethods` (class methods) and `String::Inflectable` (refinement) under `lib/core_ext/`. Switch all 6 call sites from `CommandKit::Inflector.camelize/underscore` to the refinement. Copy the same files into the template directory. Update `gem_builder.rb` to copy all rakelib support files + core_ext into generated gems. Rewrite `version.rake.erb` as thin domain-object wiring.
|
|
8
|
+
|
|
9
|
+
**Tech Stack:** Ruby refinements, Zeitwerk, ERB templates
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## File Structure
|
|
14
|
+
|
|
15
|
+
### New in gempilot
|
|
16
|
+
| Path | Responsibility |
|
|
17
|
+
|------|----------------|
|
|
18
|
+
| `lib/core_ext/string/inflection_methods.rb` | `dasherize`, `underscore`, `camelize` as module methods |
|
|
19
|
+
| `lib/core_ext/string/refinements/inflectable.rb` | Refinement wrapping InflectionMethods onto String |
|
|
20
|
+
|
|
21
|
+
### New in templates
|
|
22
|
+
| Path | Responsibility |
|
|
23
|
+
|------|----------------|
|
|
24
|
+
| `data/templates/gem/lib/core_ext/string/inflection_methods.rb` | Same — shipped to generated gems |
|
|
25
|
+
| `data/templates/gem/lib/core_ext/string/refinements/inflectable.rb` | Same — shipped to generated gems |
|
|
26
|
+
| `data/templates/gem/rakelib/project.rb` | Project introspection for generated gems |
|
|
27
|
+
|
|
28
|
+
### Modify
|
|
29
|
+
| Path | Change |
|
|
30
|
+
|------|--------|
|
|
31
|
+
| `lib/gempilot.rb:7` | Add `LOADER.ignore` for core_ext |
|
|
32
|
+
| `rakelib/project.rb:1-3,36` | Switch to refinement |
|
|
33
|
+
| `lib/gempilot/cli/gem_context.rb:1,19,27` | Switch to refinement |
|
|
34
|
+
| `lib/gempilot/cli/commands/create.rb:4,102` | Switch to refinement |
|
|
35
|
+
| `lib/gempilot/cli/commands/new.rb:4,120-121` | Switch to refinement |
|
|
36
|
+
| `lib/gempilot/cli/commands/destroy.rb:3,95` | Switch to refinement |
|
|
37
|
+
| `lib/gempilot/cli/gem_builder.rb:49-52` | Copy all rakelib + core_ext files |
|
|
38
|
+
| `data/templates/gem/rakelib/version.rake.erb` | Rewrite to use domain objects |
|
|
39
|
+
| `data/templates/gem/lib/gem_name.rb.erb:9` | Add `LOADER.ignore` for core_ext |
|
|
40
|
+
| `data/templates/gem/Gemfile.erb` | Add `warning` gem |
|
|
41
|
+
|
|
42
|
+
---
|
|
43
|
+
|
|
44
|
+
### Task 1: Create inflection files in gempilot
|
|
45
|
+
|
|
46
|
+
**Files:**
|
|
47
|
+
- Create: `lib/core_ext/string/inflection_methods.rb`
|
|
48
|
+
- Create: `lib/core_ext/string/refinements/inflectable.rb`
|
|
49
|
+
- Modify: `lib/gempilot.rb`
|
|
50
|
+
|
|
51
|
+
- [ ] **Step 1: Create `lib/core_ext/string/inflection_methods.rb`**
|
|
52
|
+
|
|
53
|
+
```ruby
|
|
54
|
+
require "strscan"
|
|
55
|
+
|
|
56
|
+
module String::InflectionMethods
|
|
57
|
+
def dasherize(name) = name.to_s.tr("_", "-")
|
|
58
|
+
|
|
59
|
+
def underscore(name)
|
|
60
|
+
scanner = StringScanner.new(name.to_s)
|
|
61
|
+
new_string = String.new
|
|
62
|
+
until scanner.eos?
|
|
63
|
+
if (separator = scanner.scan(/[_-]+/))
|
|
64
|
+
new_string << ("_" * separator.length)
|
|
65
|
+
else
|
|
66
|
+
if (capitalized = scanner.scan(/[A-Z][a-z\d]+/))
|
|
67
|
+
new_string << capitalized
|
|
68
|
+
elsif (uppercase = scanner.scan(/[A-Z][A-Z\d]*(?=[A-Z_-]|$)/))
|
|
69
|
+
new_string << uppercase
|
|
70
|
+
elsif (lowercase = scanner.scan(/[a-z][a-z\d]*/))
|
|
71
|
+
new_string << lowercase
|
|
72
|
+
else
|
|
73
|
+
raise(ArgumentError, "cannot convert string to underscored: #{scanner.string.inspect}")
|
|
74
|
+
end
|
|
75
|
+
if (separator = scanner.scan(/[_-]+/))
|
|
76
|
+
new_string << ("_" * separator.length)
|
|
77
|
+
elsif !scanner.eos?
|
|
78
|
+
new_string << "_"
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
new_string.downcase!
|
|
83
|
+
new_string
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def camelize(name)
|
|
87
|
+
scanner = StringScanner.new(name.to_s)
|
|
88
|
+
new_string = String.new
|
|
89
|
+
until scanner.eos?
|
|
90
|
+
if (word = scanner.scan(/[A-Za-z\d]+/))
|
|
91
|
+
word.capitalize!
|
|
92
|
+
new_string << word
|
|
93
|
+
elsif (numbers = scanner.scan(/[_-]\d+/))
|
|
94
|
+
new_string << "_#{numbers[1..]}"
|
|
95
|
+
elsif scanner.scan(/[_-]+/)
|
|
96
|
+
elsif scanner.scan(%r{/})
|
|
97
|
+
new_string << "::"
|
|
98
|
+
else
|
|
99
|
+
raise(ArgumentError, "cannot convert string to CamelCase: #{scanner.string.inspect}")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
new_string
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
- [ ] **Step 2: Create `lib/core_ext/string/refinements/inflectable.rb`**
|
|
108
|
+
|
|
109
|
+
```ruby
|
|
110
|
+
require_relative "../inflection_methods"
|
|
111
|
+
|
|
112
|
+
module String::Inflectable
|
|
113
|
+
refine String.singleton_class do
|
|
114
|
+
import_methods String::InflectionMethods
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
refine String do
|
|
118
|
+
def dasherize
|
|
119
|
+
self.class.dasherize(self)
|
|
120
|
+
end
|
|
121
|
+
|
|
122
|
+
def underscore
|
|
123
|
+
self.class.underscore(self)
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def camelize
|
|
127
|
+
self.class.camelize(self)
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
- [ ] **Step 3: Add `LOADER.ignore` in `lib/gempilot.rb`**
|
|
134
|
+
|
|
135
|
+
In `lib/gempilot.rb`, add the ignore line after `LOADER = Zeitwerk::Loader.for_gem`:
|
|
136
|
+
|
|
137
|
+
```ruby
|
|
138
|
+
module Gempilot
|
|
139
|
+
LOADER = Zeitwerk::Loader.for_gem
|
|
140
|
+
LOADER.inflector.inflect("cli" => "CLI")
|
|
141
|
+
LOADER.ignore("#{__dir__}/core_ext")
|
|
142
|
+
LOADER.setup
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
- [ ] **Step 4: Verify the refinement works**
|
|
146
|
+
|
|
147
|
+
Run: `ruby -e 'require_relative "lib/core_ext/string/refinements/inflectable"; using String::Inflectable; puts "my_gem".camelize'`
|
|
148
|
+
Expected: `MyGem`
|
|
149
|
+
|
|
150
|
+
- [ ] **Step 5: Verify Zeitwerk still loads cleanly**
|
|
151
|
+
|
|
152
|
+
Run: `bundle exec ruby -e 'require "gempilot"; Gempilot::LOADER.eager_load(force: true); puts "OK"'`
|
|
153
|
+
Expected: `OK`
|
|
154
|
+
|
|
155
|
+
- [ ] **Step 6: Commit**
|
|
156
|
+
|
|
157
|
+
```bash
|
|
158
|
+
git add lib/core_ext/ lib/gempilot.rb
|
|
159
|
+
git commit -m "Add String::Inflectable refinement for inflection"
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
---
|
|
163
|
+
|
|
164
|
+
### Task 2: Switch gempilot from CommandKit::Inflector to String::Inflectable
|
|
165
|
+
|
|
166
|
+
**Files:**
|
|
167
|
+
- Modify: `rakelib/project.rb`
|
|
168
|
+
- Modify: `lib/gempilot/cli/gem_context.rb`
|
|
169
|
+
- Modify: `lib/gempilot/cli/commands/create.rb`
|
|
170
|
+
- Modify: `lib/gempilot/cli/commands/new.rb`
|
|
171
|
+
- Modify: `lib/gempilot/cli/commands/destroy.rb`
|
|
172
|
+
|
|
173
|
+
- [ ] **Step 1: Switch `rakelib/project.rb`**
|
|
174
|
+
|
|
175
|
+
Replace line 3 (`require "command_kit/inflector"`) with:
|
|
176
|
+
```ruby
|
|
177
|
+
require_relative "../lib/core_ext/string/refinements/inflectable"
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Add `using String::Inflectable` inside the class body (after `class Project`).
|
|
181
|
+
|
|
182
|
+
Replace line 36 (`Object.const_get(CommandKit::Inflector.camelize(name))`) with:
|
|
183
|
+
```ruby
|
|
184
|
+
Object.const_get(name.camelize)
|
|
185
|
+
```
|
|
186
|
+
|
|
187
|
+
- [ ] **Step 2: Switch `lib/gempilot/cli/gem_context.rb`**
|
|
188
|
+
|
|
189
|
+
Replace line 1 (`require "command_kit/inflector"`) with:
|
|
190
|
+
```ruby
|
|
191
|
+
require_relative "../../core_ext/string/refinements/inflectable"
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
Add `using String::Inflectable` as the first line inside `module GemContext`.
|
|
195
|
+
|
|
196
|
+
Replace line 19 (`CommandKit::Inflector.camelize(@require_path)`) with:
|
|
197
|
+
```ruby
|
|
198
|
+
@require_path.camelize
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Replace line 27 (`CommandKit::Inflector.underscore(p)`) with:
|
|
202
|
+
```ruby
|
|
203
|
+
p.underscore
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
- [ ] **Step 3: Switch `lib/gempilot/cli/commands/create.rb`**
|
|
207
|
+
|
|
208
|
+
Replace line 4 (`require "command_kit/inflector"`) with:
|
|
209
|
+
```ruby
|
|
210
|
+
require_relative "../../../core_ext/string/refinements/inflectable"
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Add `using String::Inflectable` inside the `Create` class body.
|
|
214
|
+
|
|
215
|
+
Replace line 102 (`CommandKit::Inflector.camelize(@require_path)`) with:
|
|
216
|
+
```ruby
|
|
217
|
+
@require_path.camelize
|
|
218
|
+
```
|
|
219
|
+
|
|
220
|
+
- [ ] **Step 4: Switch `lib/gempilot/cli/commands/new.rb`**
|
|
221
|
+
|
|
222
|
+
Replace line 4 (`require "command_kit/inflector"`) with:
|
|
223
|
+
```ruby
|
|
224
|
+
require_relative "../../../core_ext/string/refinements/inflectable"
|
|
225
|
+
```
|
|
226
|
+
|
|
227
|
+
Add `using String::Inflectable` inside the `New` class body.
|
|
228
|
+
|
|
229
|
+
Replace line 120 (`CommandKit::Inflector.underscore(name)`) with:
|
|
230
|
+
```ruby
|
|
231
|
+
name.underscore
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
Replace line 121 (`CommandKit::Inflector.camelize(name)`) with:
|
|
235
|
+
```ruby
|
|
236
|
+
name.camelize
|
|
237
|
+
```
|
|
238
|
+
|
|
239
|
+
- [ ] **Step 5: Switch `lib/gempilot/cli/commands/destroy.rb`**
|
|
240
|
+
|
|
241
|
+
Replace line 3 (`require "command_kit/inflector"`) with:
|
|
242
|
+
```ruby
|
|
243
|
+
require_relative "../../../core_ext/string/refinements/inflectable"
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Add `using String::Inflectable` inside the `Destroy` class body.
|
|
247
|
+
|
|
248
|
+
Replace line 95 (`CommandKit::Inflector.underscore(name)`) with:
|
|
249
|
+
```ruby
|
|
250
|
+
name.underscore
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
- [ ] **Step 6: Run full test suite**
|
|
254
|
+
|
|
255
|
+
Run: `bundle exec rake test spec`
|
|
256
|
+
Expected: All pass
|
|
257
|
+
|
|
258
|
+
- [ ] **Step 7: Run rubocop**
|
|
259
|
+
|
|
260
|
+
Run: `bundle exec rubocop`
|
|
261
|
+
Expected: No offenses
|
|
262
|
+
|
|
263
|
+
- [ ] **Step 8: Commit**
|
|
264
|
+
|
|
265
|
+
```bash
|
|
266
|
+
git add rakelib/project.rb lib/gempilot/cli/gem_context.rb lib/gempilot/cli/commands/create.rb lib/gempilot/cli/commands/new.rb lib/gempilot/cli/commands/destroy.rb
|
|
267
|
+
git commit -m "Switch from CommandKit::Inflector to String::Inflectable"
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
### Task 3: Add inflection + project templates for generated gems
|
|
273
|
+
|
|
274
|
+
**Files:**
|
|
275
|
+
- Create: `data/templates/gem/lib/core_ext/string/inflection_methods.rb`
|
|
276
|
+
- Create: `data/templates/gem/lib/core_ext/string/refinements/inflectable.rb`
|
|
277
|
+
- Create: `data/templates/gem/rakelib/project.rb`
|
|
278
|
+
|
|
279
|
+
- [ ] **Step 1: Copy inflection files to template directory**
|
|
280
|
+
|
|
281
|
+
```bash
|
|
282
|
+
mkdir -p data/templates/gem/lib/core_ext/string/refinements
|
|
283
|
+
cp lib/core_ext/string/inflection_methods.rb data/templates/gem/lib/core_ext/string/inflection_methods.rb
|
|
284
|
+
cp lib/core_ext/string/refinements/inflectable.rb data/templates/gem/lib/core_ext/string/refinements/inflectable.rb
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
- [ ] **Step 2: Create `data/templates/gem/rakelib/project.rb`**
|
|
288
|
+
|
|
289
|
+
This is gempilot's own `rakelib/project.rb` but with the require path adjusted (rakelib is at gem root, so `../lib/` is correct — same as gempilot's own path):
|
|
290
|
+
|
|
291
|
+
```ruby
|
|
292
|
+
require "pathname"
|
|
293
|
+
require "warning"
|
|
294
|
+
require_relative "../lib/core_ext/string/refinements/inflectable"
|
|
295
|
+
require_relative "project_version"
|
|
296
|
+
|
|
297
|
+
# Introspects a gem project directory to discover its name, module,
|
|
298
|
+
# and version. Used by rake tasks to drive version lifecycle operations.
|
|
299
|
+
class Project
|
|
300
|
+
class ProjectIntrospectionError < StandardError; end
|
|
301
|
+
|
|
302
|
+
REDEFINITION_WARNING = /previous definition of VERSION was here/
|
|
303
|
+
REINITIALIZATION_WARNING = /already initialized constant [^\s]+::VERSION/
|
|
304
|
+
private_constant :REDEFINITION_WARNING, :REINITIALIZATION_WARNING
|
|
305
|
+
|
|
306
|
+
using String::Inflectable
|
|
307
|
+
|
|
308
|
+
attr_reader :root
|
|
309
|
+
|
|
310
|
+
def initialize(root = __dir__)
|
|
311
|
+
@root = Pathname(root)
|
|
312
|
+
@verifications = Set.new
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def lib
|
|
316
|
+
root.join("lib")
|
|
317
|
+
.tap { verify_existence! it }
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def lib_project
|
|
321
|
+
@lib_project ||= fetch_lib_project
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def name
|
|
325
|
+
lib_project.basename.to_s
|
|
326
|
+
end
|
|
327
|
+
|
|
328
|
+
def klass
|
|
329
|
+
Object.const_get(name.camelize)
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def version
|
|
333
|
+
@version ||= fetch_version
|
|
334
|
+
end
|
|
335
|
+
|
|
336
|
+
def refresh_version!
|
|
337
|
+
@version = fetch_version
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def increment_version
|
|
341
|
+
version.next_version
|
|
342
|
+
end
|
|
343
|
+
|
|
344
|
+
def version_tag = version.tag
|
|
345
|
+
|
|
346
|
+
def version_value = version.value
|
|
347
|
+
|
|
348
|
+
def write_version!(old_version, new_version)
|
|
349
|
+
with_version_file do |f|
|
|
350
|
+
source = f.read
|
|
351
|
+
|
|
352
|
+
unless source.match?(Regexp.escape(old_version.value))
|
|
353
|
+
abort "Expected to find #{old_version.value} in #{f.path} but did not"
|
|
354
|
+
end
|
|
355
|
+
|
|
356
|
+
f.rewind
|
|
357
|
+
f.write source.gsub(old_version.value, new_version.value)
|
|
358
|
+
end
|
|
359
|
+
end
|
|
360
|
+
|
|
361
|
+
private
|
|
362
|
+
|
|
363
|
+
def with_version_file
|
|
364
|
+
version.path.open(File::RDWR, 0o644) do |f|
|
|
365
|
+
f.flock File::LOCK_EX
|
|
366
|
+
yield f
|
|
367
|
+
f.truncate(f.pos)
|
|
368
|
+
end
|
|
369
|
+
end
|
|
370
|
+
|
|
371
|
+
def fetch_lib_project
|
|
372
|
+
files = lib.glob("*.rb")
|
|
373
|
+
dirs = files.map { it.sub_ext("") }.select(&:directory?)
|
|
374
|
+
case dirs.count
|
|
375
|
+
in 0 then raise ProjectIntrospectionError, "Could not identify project dir"
|
|
376
|
+
in (2..)
|
|
377
|
+
msg = "Found more than one possible project name:\n - #{dirs.join("\n - ")}"
|
|
378
|
+
raise ProjectIntrospectionError, msg
|
|
379
|
+
in 1 then dirs.first
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
|
|
383
|
+
def fetch_version
|
|
384
|
+
Warning.ignore(REDEFINITION_WARNING)
|
|
385
|
+
Warning.ignore(REINITIALIZATION_WARNING)
|
|
386
|
+
path = lib_project
|
|
387
|
+
.join("version.rb")
|
|
388
|
+
.tap { verify_existence! it }
|
|
389
|
+
.tap { load it }
|
|
390
|
+
|
|
391
|
+
value = klass.const_get(:VERSION)
|
|
392
|
+
|
|
393
|
+
Version.new(path:, value:)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def verify_existence!(path)
|
|
397
|
+
return true if @verifications.member? path
|
|
398
|
+
|
|
399
|
+
raise ProjectIntrospectionError, "Expected #{path} to exist but does not" unless path.exist?
|
|
400
|
+
|
|
401
|
+
@verifications.add path
|
|
402
|
+
end
|
|
403
|
+
end
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
- [ ] **Step 3: Commit**
|
|
407
|
+
|
|
408
|
+
```bash
|
|
409
|
+
git add data/templates/gem/lib/core_ext/ data/templates/gem/rakelib/project.rb
|
|
410
|
+
git commit -m "Add inflection and Project templates for generated gems"
|
|
411
|
+
```
|
|
412
|
+
|
|
413
|
+
---
|
|
414
|
+
|
|
415
|
+
### Task 4: Rewrite version.rake.erb to use domain objects
|
|
416
|
+
|
|
417
|
+
**Files:**
|
|
418
|
+
- Rewrite: `data/templates/gem/rakelib/version.rake.erb`
|
|
419
|
+
|
|
420
|
+
- [ ] **Step 1: Rewrite `data/templates/gem/rakelib/version.rake.erb`**
|
|
421
|
+
|
|
422
|
+
Replace the entire file with:
|
|
423
|
+
|
|
424
|
+
```ruby
|
|
425
|
+
require_relative "project"
|
|
426
|
+
require_relative "version_tag"
|
|
427
|
+
require_relative "github_release"
|
|
428
|
+
|
|
429
|
+
root_path = File.expand_path("../", __dir__)
|
|
430
|
+
project = Project.new(root_path)
|
|
431
|
+
|
|
432
|
+
namespace :version do
|
|
433
|
+
desc "Display the current version"
|
|
434
|
+
task(:current) { puts "Current version: #{project.version_value}" }
|
|
435
|
+
|
|
436
|
+
desc "Bump the patch version"
|
|
437
|
+
task :bump do
|
|
438
|
+
old_version = project.version
|
|
439
|
+
new_version = project.increment_version
|
|
440
|
+
project.write_version!(old_version, new_version)
|
|
441
|
+
project.refresh_version!
|
|
442
|
+
puts "Version bumped from #{old_version.value} to #{project.version_value}"
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
desc "Commit the version change"
|
|
446
|
+
task(:commit) { VersionTag.new(project.version).create }
|
|
447
|
+
|
|
448
|
+
desc "Tag the current version"
|
|
449
|
+
task(:tag) { VersionTag.new(project.version).tag }
|
|
450
|
+
|
|
451
|
+
desc "Untag the current version"
|
|
452
|
+
task(:untag) { VersionTag.new(project.version).untag }
|
|
453
|
+
|
|
454
|
+
desc "Reset the last version bump commit"
|
|
455
|
+
task :reset do
|
|
456
|
+
VersionTag.new(project.version).reset
|
|
457
|
+
project.refresh_version!
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
desc "Revert the last version bump commit"
|
|
461
|
+
task :revert do
|
|
462
|
+
VersionTag.new(project.version).revert
|
|
463
|
+
project.refresh_version!
|
|
464
|
+
end
|
|
465
|
+
|
|
466
|
+
desc "Bump version, commit, and tag"
|
|
467
|
+
task release: ["version:bump", "version:commit", "version:tag"]
|
|
468
|
+
|
|
469
|
+
desc "Untag and reset version"
|
|
470
|
+
task unrelease: ["version:untag", "version:reset"]
|
|
471
|
+
|
|
472
|
+
namespace :github do
|
|
473
|
+
desc "Create a GitHub release for the current version"
|
|
474
|
+
task(:release) { GithubRelease.new(project.version_tag).create }
|
|
475
|
+
|
|
476
|
+
desc "Delete the GitHub release for the current version"
|
|
477
|
+
task(:unrelease) { GithubRelease.new(project.version_tag).destroy }
|
|
478
|
+
|
|
479
|
+
desc "List GitHub releases"
|
|
480
|
+
task(:list) { GithubRelease.new(project.version_tag).list }
|
|
481
|
+
end
|
|
482
|
+
end
|
|
483
|
+
```
|
|
484
|
+
|
|
485
|
+
Note: This file no longer needs ERB — it has zero interpolations. Rename it from `.erb` to plain `.rake` if desired, but the generator's `erb` method handles non-ERB content fine (ERB passes through plain text unchanged). Keeping the `.erb` extension avoids changing the `erb` call in `gem_builder.rb`.
|
|
486
|
+
|
|
487
|
+
- [ ] **Step 2: Commit**
|
|
488
|
+
|
|
489
|
+
```bash
|
|
490
|
+
git add data/templates/gem/rakelib/version.rake.erb
|
|
491
|
+
git commit -m "Rewrite version.rake.erb to use domain objects"
|
|
492
|
+
```
|
|
493
|
+
|
|
494
|
+
---
|
|
495
|
+
|
|
496
|
+
### Task 5: Update gem_builder.rb to copy all support files
|
|
497
|
+
|
|
498
|
+
**Files:**
|
|
499
|
+
- Modify: `lib/gempilot/cli/gem_builder.rb:49-52`
|
|
500
|
+
|
|
501
|
+
- [ ] **Step 1: Expand `render_version_rake` and add `render_core_ext`**
|
|
502
|
+
|
|
503
|
+
Replace the `render_version_rake` method (lines 49-52) with:
|
|
504
|
+
|
|
505
|
+
```ruby
|
|
506
|
+
def render_version_rake
|
|
507
|
+
mkdir "#{@gem_name}/rakelib"
|
|
508
|
+
erb "rakelib/version.rake.erb", "#{@gem_name}/rakelib/version.rake"
|
|
509
|
+
cp "rakelib/project.rb", "#{@gem_name}/rakelib/project.rb"
|
|
510
|
+
cp "rakelib/project_version.rb", "#{@gem_name}/rakelib/project_version.rb"
|
|
511
|
+
cp "rakelib/version_tag.rb", "#{@gem_name}/rakelib/version_tag.rb"
|
|
512
|
+
cp "rakelib/github_release.rb", "#{@gem_name}/rakelib/github_release.rb"
|
|
513
|
+
cp "rakelib/strict_shell.rb", "#{@gem_name}/rakelib/strict_shell.rb"
|
|
514
|
+
end
|
|
515
|
+
```
|
|
516
|
+
|
|
517
|
+
Add a new method and call it from `render_core_templates` (add the call after line 46, `render_version_rake`):
|
|
518
|
+
|
|
519
|
+
```ruby
|
|
520
|
+
def render_core_ext
|
|
521
|
+
mkdir "#{@gem_name}/lib/core_ext"
|
|
522
|
+
mkdir "#{@gem_name}/lib/core_ext/string"
|
|
523
|
+
mkdir "#{@gem_name}/lib/core_ext/string/refinements"
|
|
524
|
+
cp "lib/core_ext/string/inflection_methods.rb",
|
|
525
|
+
"#{@gem_name}/lib/core_ext/string/inflection_methods.rb"
|
|
526
|
+
cp "lib/core_ext/string/refinements/inflectable.rb",
|
|
527
|
+
"#{@gem_name}/lib/core_ext/string/refinements/inflectable.rb"
|
|
528
|
+
end
|
|
529
|
+
```
|
|
530
|
+
|
|
531
|
+
Add the call in `render_core_templates` after `render_version_rake`:
|
|
532
|
+
|
|
533
|
+
```ruby
|
|
534
|
+
render_version_rake
|
|
535
|
+
render_core_ext
|
|
536
|
+
```
|
|
537
|
+
|
|
538
|
+
- [ ] **Step 2: Run tests**
|
|
539
|
+
|
|
540
|
+
Run: `bundle exec rake test spec`
|
|
541
|
+
Expected: All pass
|
|
542
|
+
|
|
543
|
+
- [ ] **Step 3: Commit**
|
|
544
|
+
|
|
545
|
+
```bash
|
|
546
|
+
git add lib/gempilot/cli/gem_builder.rb
|
|
547
|
+
git commit -m "Wire generator to copy rakelib support files and core_ext"
|
|
548
|
+
```
|
|
549
|
+
|
|
550
|
+
---
|
|
551
|
+
|
|
552
|
+
### Task 6: Update generated gem Zeitwerk config and Gemfile
|
|
553
|
+
|
|
554
|
+
**Files:**
|
|
555
|
+
- Modify: `data/templates/gem/lib/gem_name.rb.erb`
|
|
556
|
+
- Modify: `data/templates/gem/Gemfile.erb`
|
|
557
|
+
|
|
558
|
+
- [ ] **Step 1: Add `LOADER.ignore` to `data/templates/gem/lib/gem_name.rb.erb`**
|
|
559
|
+
|
|
560
|
+
Change the non-hyphenated branch from:
|
|
561
|
+
```erb
|
|
562
|
+
module <%= @module_name %>
|
|
563
|
+
LOADER = Zeitwerk::Loader.for_gem
|
|
564
|
+
LOADER.setup
|
|
565
|
+
```
|
|
566
|
+
|
|
567
|
+
To:
|
|
568
|
+
```erb
|
|
569
|
+
module <%= @module_name %>
|
|
570
|
+
LOADER = Zeitwerk::Loader.for_gem
|
|
571
|
+
LOADER.ignore("#{__dir__}/core_ext")
|
|
572
|
+
LOADER.setup
|
|
573
|
+
```
|
|
574
|
+
|
|
575
|
+
- [ ] **Step 2: Add `warning` gem to `data/templates/gem/Gemfile.erb`**
|
|
576
|
+
|
|
577
|
+
Add `gem "warning"` after the rubocop gems (before the closing of the file). Insert after the last `<% end -%>` on line 22:
|
|
578
|
+
|
|
579
|
+
```erb
|
|
580
|
+
<% if @test_framework == :rspec -%>
|
|
581
|
+
gem "rubocop-rspec"
|
|
582
|
+
<% end -%>
|
|
583
|
+
gem "warning"
|
|
584
|
+
```
|
|
585
|
+
|
|
586
|
+
- [ ] **Step 3: Commit**
|
|
587
|
+
|
|
588
|
+
```bash
|
|
589
|
+
git add data/templates/gem/lib/gem_name.rb.erb data/templates/gem/Gemfile.erb
|
|
590
|
+
git commit -m "Add Zeitwerk core_ext ignore and warning gem to generated gems"
|
|
591
|
+
```
|
|
592
|
+
|
|
593
|
+
---
|
|
594
|
+
|
|
595
|
+
### Task 7: Integration test
|
|
596
|
+
|
|
597
|
+
- [ ] **Step 1: Generate a test gem**
|
|
598
|
+
|
|
599
|
+
```bash
|
|
600
|
+
cd /var/tmp
|
|
601
|
+
bundle exec ruby -I /workspace/gempilot/lib /workspace/gempilot/exe/gempilot create test_gem --test minitest --no-exe --no-git --summary "Test" --author "Test" --email "test@test.com"
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
- [ ] **Step 2: Verify generated files exist**
|
|
605
|
+
|
|
606
|
+
```bash
|
|
607
|
+
ls test_gem/rakelib/
|
|
608
|
+
```
|
|
609
|
+
|
|
610
|
+
Expected: `github_release.rb project.rb project_version.rb strict_shell.rb version.rake version_tag.rb`
|
|
611
|
+
|
|
612
|
+
```bash
|
|
613
|
+
ls test_gem/lib/core_ext/string/refinements/
|
|
614
|
+
```
|
|
615
|
+
|
|
616
|
+
Expected: `inflectable.rb`
|
|
617
|
+
|
|
618
|
+
- [ ] **Step 3: Verify rake tasks work**
|
|
619
|
+
|
|
620
|
+
```bash
|
|
621
|
+
cd test_gem && bundle install && bundle exec rake -T version
|
|
622
|
+
```
|
|
623
|
+
|
|
624
|
+
Expected: Lists 12 version tasks
|
|
625
|
+
|
|
626
|
+
```bash
|
|
627
|
+
bundle exec rake version:current
|
|
628
|
+
```
|
|
629
|
+
|
|
630
|
+
Expected: `Current version: 0.1.0`
|
|
631
|
+
|
|
632
|
+
- [ ] **Step 4: Verify full suite passes**
|
|
633
|
+
|
|
634
|
+
```bash
|
|
635
|
+
bundle exec rake
|
|
636
|
+
```
|
|
637
|
+
|
|
638
|
+
Expected: Tests + rubocop pass
|
|
639
|
+
|
|
640
|
+
- [ ] **Step 5: Clean up**
|
|
641
|
+
|
|
642
|
+
```bash
|
|
643
|
+
cd /workspace/gempilot
|
|
644
|
+
rm -rf /var/tmp/test_gem
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
- [ ] **Step 6: Run gempilot's own full suite**
|
|
648
|
+
|
|
649
|
+
Run: `bundle exec rake test spec`
|
|
650
|
+
Expected: All pass
|
|
651
|
+
|
|
652
|
+
Run: `bundle exec rubocop`
|
|
653
|
+
Expected: No offenses
|
|
654
|
+
|
|
655
|
+
- [ ] **Step 7: Commit any fixes needed**
|
|
656
|
+
|
|
657
|
+
```bash
|
|
658
|
+
git add -A && git commit -m "Fix integration issues" # only if fixes were needed
|
|
659
|
+
```
|