bonchi 0.6.0.rc3 → 0.7.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 +4 -4
- data/lib/bonchi/cli.rb +13 -8
- data/lib/bonchi/config.rb +13 -5
- data/lib/bonchi/git.rb +20 -3
- data/lib/bonchi/setup.rb +61 -37
- data/lib/bonchi/version.rb +1 -1
- metadata +7 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2af66b530d16c1004fe1d290edf3a28ea189999df19561fbd70f0d2779f31510
|
|
4
|
+
data.tar.gz: 99edcbb95cbb4cdd4aa6d752b6266e3392f69833654fece86f8cef8918c5441c
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e58e2d259376816627e4905885d19a5785ca525c1fda135af38c6a849726f37e088c5244b03e46789b164a088a8ece8d9c3153377d3a8476c0f851555b576ed4
|
|
7
|
+
data.tar.gz: e4d9300d822379d8423fc1f55b6501a6abae8c0441be618ca9651d6af2197b12556de05e4eee96b94f946d51b5f1069e1ed379c6924f69154c4d105071203d68
|
data/lib/bonchi/cli.rb
CHANGED
|
@@ -97,6 +97,7 @@ module Bonchi
|
|
|
97
97
|
|
|
98
98
|
Git.fetch_pr(pr_number)
|
|
99
99
|
Git.worktree_add(path, branch)
|
|
100
|
+
Git.set_pr_upstream(pr_number)
|
|
100
101
|
puts "PR ##{pr_number} checked out at: #{path}"
|
|
101
102
|
|
|
102
103
|
signal_cd(path)
|
|
@@ -281,17 +282,21 @@ module Bonchi
|
|
|
281
282
|
# ports:
|
|
282
283
|
# - PORT
|
|
283
284
|
|
|
284
|
-
#
|
|
285
|
-
#
|
|
286
|
-
#
|
|
285
|
+
# Edits applied to files. Env vars ($VAR) are expanded.
|
|
286
|
+
# Three actions: regex replace, append, upsert (replace-or-append).
|
|
287
|
+
# edit:
|
|
287
288
|
# .env.local:
|
|
289
|
+
# # Replace — short form
|
|
288
290
|
# - "^PORT=.*": "PORT=$PORT"
|
|
289
|
-
#
|
|
290
|
-
#
|
|
291
|
-
#
|
|
292
|
-
# - match: "^PORT=.*"
|
|
293
|
-
# with: "PORT=$PORT"
|
|
291
|
+
# # Replace — full form (with optional missing: warn, default: halt)
|
|
292
|
+
# - match: "^DATABASE_URL=.*"
|
|
293
|
+
# with: "DATABASE_URL=postgres:///myapp_$WORKTREE_BRANCH_SLUG"
|
|
294
294
|
# missing: warn
|
|
295
|
+
# # Append a line unconditionally
|
|
296
|
+
# - append: "FOO=bar"
|
|
297
|
+
# # Replace if pattern matches, otherwise append
|
|
298
|
+
# - upsert: "^DEBUG="
|
|
299
|
+
# with: "DEBUG=1"
|
|
295
300
|
|
|
296
301
|
# Commands to run before the setup command (port env vars are available).
|
|
297
302
|
# pre_setup:
|
data/lib/bonchi/config.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Bonchi
|
|
|
4
4
|
class Config
|
|
5
5
|
include Colors
|
|
6
6
|
|
|
7
|
-
KNOWN_KEYS = %w[min_version copy link ports replace pre_setup setup].freeze
|
|
7
|
+
KNOWN_KEYS = %w[min_version copy link ports replace edit pre_setup setup].freeze
|
|
8
8
|
|
|
9
9
|
attr_reader :copy, :link, :ports, :replace, :pre_setup, :setup
|
|
10
10
|
|
|
@@ -16,10 +16,14 @@ module Bonchi
|
|
|
16
16
|
|
|
17
17
|
check_min_version!(data["min_version"]) if data["min_version"]
|
|
18
18
|
|
|
19
|
+
if data.key?("replace") && data.key?("edit")
|
|
20
|
+
abort "#{color(:red)}Error:#{reset} both 'edit' and 'replace' set in .worktree.yml — use 'edit' (preferred)"
|
|
21
|
+
end
|
|
22
|
+
|
|
19
23
|
@copy = Array(data["copy"])
|
|
20
24
|
@link = Array(data["link"])
|
|
21
25
|
@ports = Array(data["ports"])
|
|
22
|
-
@replace = data["replace"] || {}
|
|
26
|
+
@replace = data["edit"] || data["replace"] || {}
|
|
23
27
|
@pre_setup = Array(data["pre_setup"])
|
|
24
28
|
@setup = data["setup"] || "bin/setup"
|
|
25
29
|
|
|
@@ -49,17 +53,21 @@ module Bonchi
|
|
|
49
53
|
|
|
50
54
|
def validate!
|
|
51
55
|
unless @replace.is_a?(Hash)
|
|
52
|
-
abort "#{color(:red)}Error:#{reset} '
|
|
56
|
+
abort "#{color(:red)}Error:#{reset} 'edit' must be a mapping of filename to list of edits"
|
|
53
57
|
end
|
|
54
58
|
|
|
55
59
|
@replace.each do |file, entries|
|
|
56
60
|
unless entries.is_a?(Array)
|
|
57
|
-
abort "#{color(:red)}Error:#{reset} '
|
|
61
|
+
abort "#{color(:red)}Error:#{reset} 'edit.#{file}' must be a list of edits"
|
|
58
62
|
end
|
|
59
63
|
|
|
60
64
|
entries.each do |entry|
|
|
61
65
|
unless entry.is_a?(Hash)
|
|
62
|
-
abort "#{color(:red)}Error:#{reset} each
|
|
66
|
+
abort "#{color(:red)}Error:#{reset} each edit in 'edit.#{file}' must be a mapping"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
if entry.key?("upsert") && !entry.key?("with")
|
|
70
|
+
abort "#{color(:red)}Error:#{reset} 'upsert' in 'edit.#{file}' requires a 'with' value"
|
|
63
71
|
end
|
|
64
72
|
end
|
|
65
73
|
end
|
data/lib/bonchi/git.rb
CHANGED
|
@@ -66,12 +66,20 @@ module Bonchi
|
|
|
66
66
|
end
|
|
67
67
|
|
|
68
68
|
def merged?(branch, into: default_base_branch)
|
|
69
|
-
|
|
70
|
-
|
|
69
|
+
locally_merged?(branch, into: into) ||
|
|
70
|
+
remotely_merged?(branch, into: into)
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def locally_merged?(branch, into: default_base_branch)
|
|
74
|
+
system("git", "merge-base", "--is-ancestor", branch, into)
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def remotely_merged?(branch, into: default_base_branch)
|
|
78
|
+
system("git", "merge-base", "--is-ancestor", branch, "origin/#{into}")
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
def delete_branch(branch, force: false)
|
|
74
|
-
flag = force ? "-D" : "-d"
|
|
82
|
+
flag = (force || !locally_merged?(branch)) ? "-D" : "-d"
|
|
75
83
|
system("git", "branch", flag, branch) || abort("Failed to delete branch: #{branch}")
|
|
76
84
|
end
|
|
77
85
|
|
|
@@ -79,6 +87,15 @@ module Bonchi
|
|
|
79
87
|
system("git", "fetch", "origin", "pull/#{pr_number}/head:pr-#{pr_number}")
|
|
80
88
|
end
|
|
81
89
|
|
|
90
|
+
def set_pr_upstream(pr_number)
|
|
91
|
+
head_ref = `gh pr view #{pr_number} --json headRefName -q .headRefName`.strip
|
|
92
|
+
return if head_ref.empty?
|
|
93
|
+
|
|
94
|
+
if system("git", "branch", "--set-upstream-to", "origin/#{head_ref}", "pr-#{pr_number}")
|
|
95
|
+
puts "Upstream set to origin/#{head_ref}"
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
|
|
82
99
|
def worktree_dir(branch)
|
|
83
100
|
File.join(GlobalConfig.new.worktree_root, repo_name, branch)
|
|
84
101
|
end
|
data/lib/bonchi/setup.rb
CHANGED
|
@@ -21,8 +21,13 @@ module Bonchi
|
|
|
21
21
|
abort "#{color(:red)}Error:#{reset} already in the main worktree"
|
|
22
22
|
end
|
|
23
23
|
|
|
24
|
-
config = Config.
|
|
25
|
-
|
|
24
|
+
config = Config.from_worktree(@worktree)
|
|
25
|
+
if config
|
|
26
|
+
puts "Using .worktree.yml from linked worktree"
|
|
27
|
+
else
|
|
28
|
+
config = Config.from_main_worktree
|
|
29
|
+
abort "#{color(:red)}Error:#{reset} .worktree.yml not found in main worktree" unless config
|
|
30
|
+
end
|
|
26
31
|
|
|
27
32
|
last_step = upto || STEPS.last
|
|
28
33
|
run_steps = STEPS[0..STEPS.index(last_step)]
|
|
@@ -37,14 +42,6 @@ module Bonchi
|
|
|
37
42
|
|
|
38
43
|
copy_files(config.copy) if run_steps.include?("copy")
|
|
39
44
|
link_files(config.link) if run_steps.include?("link")
|
|
40
|
-
|
|
41
|
-
# Prefer linked worktree's .worktree.yml if it was copied or already exists
|
|
42
|
-
linked_config = Config.from_worktree(@worktree)
|
|
43
|
-
if linked_config
|
|
44
|
-
puts "Using .worktree.yml from linked worktree"
|
|
45
|
-
config = linked_config
|
|
46
|
-
end
|
|
47
|
-
|
|
48
45
|
allocate_ports(config.ports) if run_steps.include?("ports") && config.ports.any?
|
|
49
46
|
replace_in_files(config.replace) if run_steps.include?("replace") && config.replace.any?
|
|
50
47
|
run_pre_setup(config.pre_setup) if run_steps.include?("pre_setup")
|
|
@@ -96,37 +93,64 @@ module Bonchi
|
|
|
96
93
|
abort "#{color(:red)}Error:#{reset} #{file} not found" unless File.exist?(path)
|
|
97
94
|
|
|
98
95
|
content = File.read(path)
|
|
99
|
-
entries.each
|
|
100
|
-
if entry.is_a?(Hash) && entry.key?("match")
|
|
101
|
-
pattern = entry["match"]
|
|
102
|
-
replacement = entry["with"]
|
|
103
|
-
missing = entry["missing"] || "halt"
|
|
104
|
-
elsif entry.is_a?(Hash)
|
|
105
|
-
pattern, replacement = entry.first
|
|
106
|
-
missing = "halt"
|
|
107
|
-
else
|
|
108
|
-
abort "#{color(:red)}Error:#{reset} invalid replace entry in #{file}: #{entry.inspect}"
|
|
109
|
-
end
|
|
110
|
-
|
|
111
|
-
expanded = replacement.gsub(/\$(\w+)/) { ENV[$1] || abort("#{color(:red)}Error:#{reset} $#{$1} not set") }
|
|
112
|
-
regex = Regexp.new(pattern)
|
|
113
|
-
|
|
114
|
-
unless content.match?(regex)
|
|
115
|
-
if missing == "warn"
|
|
116
|
-
puts "#{color(:yellow)}Warning:#{reset} pattern #{pattern} not found in #{file}, skipping"
|
|
117
|
-
next
|
|
118
|
-
else
|
|
119
|
-
abort "#{color(:red)}Error:#{reset} pattern #{pattern} not found in #{file}"
|
|
120
|
-
end
|
|
121
|
-
end
|
|
122
|
-
|
|
123
|
-
content = content.gsub(regex, expanded)
|
|
124
|
-
puts "Replaced #{pattern} in #{file}"
|
|
125
|
-
end
|
|
96
|
+
entries.each { |entry| content = apply_edit(entry, content, file) }
|
|
126
97
|
File.write(path, content)
|
|
127
98
|
end
|
|
128
99
|
end
|
|
129
100
|
|
|
101
|
+
def apply_edit(entry, content, file)
|
|
102
|
+
unless entry.is_a?(Hash)
|
|
103
|
+
abort "#{color(:red)}Error:#{reset} invalid edit entry in #{file}: #{entry.inspect}"
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
if entry.key?("append")
|
|
107
|
+
line = expand(entry["append"])
|
|
108
|
+
puts "Appended to #{file}"
|
|
109
|
+
ensure_trailing_newline(content) + line + "\n"
|
|
110
|
+
elsif entry.key?("upsert")
|
|
111
|
+
unless entry.key?("with")
|
|
112
|
+
abort "#{color(:red)}Error:#{reset} 'upsert' requires 'with' in #{file}"
|
|
113
|
+
end
|
|
114
|
+
pattern = entry["upsert"]
|
|
115
|
+
replacement = expand(entry["with"])
|
|
116
|
+
regex = Regexp.new(pattern)
|
|
117
|
+
if content.match?(regex)
|
|
118
|
+
puts "Upserted #{pattern} in #{file} (matched)"
|
|
119
|
+
content.gsub(regex, replacement)
|
|
120
|
+
else
|
|
121
|
+
puts "Upserted #{pattern} in #{file} (appended)"
|
|
122
|
+
ensure_trailing_newline(content) + replacement + "\n"
|
|
123
|
+
end
|
|
124
|
+
elsif entry.key?("match")
|
|
125
|
+
replace(content, entry["match"], expand(entry["with"]), entry["missing"] || "halt", file)
|
|
126
|
+
else
|
|
127
|
+
pattern, replacement = entry.first
|
|
128
|
+
replace(content, pattern, expand(replacement), "halt", file)
|
|
129
|
+
end
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def replace(content, pattern, replacement, missing, file)
|
|
133
|
+
regex = Regexp.new(pattern)
|
|
134
|
+
unless content.match?(regex)
|
|
135
|
+
if missing == "warn"
|
|
136
|
+
puts "#{color(:yellow)}Warning:#{reset} pattern #{pattern} not found in #{file}, skipping"
|
|
137
|
+
return content
|
|
138
|
+
else
|
|
139
|
+
abort "#{color(:red)}Error:#{reset} pattern #{pattern} not found in #{file}"
|
|
140
|
+
end
|
|
141
|
+
end
|
|
142
|
+
puts "Replaced #{pattern} in #{file}"
|
|
143
|
+
content.gsub(regex, replacement)
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def expand(value)
|
|
147
|
+
value.to_s.gsub(/\$(\w+)/) { ENV[$1] || abort("#{color(:red)}Error:#{reset} $#{$1} not set") }
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def ensure_trailing_newline(content)
|
|
151
|
+
content.empty? || content.end_with?("\n") ? content : content + "\n"
|
|
152
|
+
end
|
|
153
|
+
|
|
130
154
|
def run_pre_setup(commands)
|
|
131
155
|
commands.each do |cmd|
|
|
132
156
|
puts "Running: #{cmd}"
|
data/lib/bonchi/version.rb
CHANGED
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: bonchi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.7.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Gert Goet
|
|
@@ -41,10 +41,14 @@ files:
|
|
|
41
41
|
- lib/bonchi/port_pool.rb
|
|
42
42
|
- lib/bonchi/setup.rb
|
|
43
43
|
- lib/bonchi/version.rb
|
|
44
|
-
homepage: https://github.com/
|
|
44
|
+
homepage: https://github.com/eval/bonchi
|
|
45
45
|
licenses:
|
|
46
46
|
- MIT
|
|
47
|
-
metadata:
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://github.com/eval/bonchi
|
|
49
|
+
source_code_uri: https://github.com/eval/bonchi
|
|
50
|
+
changelog_uri: https://github.com/eval/bonchi/blob/main/CHANGELOG.md
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
48
52
|
rdoc_options: []
|
|
49
53
|
require_paths:
|
|
50
54
|
- lib
|