Class: Ligarb::GithubReview

Inherits:
Object
  • Object
show all
Defined in:
lib/ligarb/github_review.rb

Overview

Sets up the GitHub-based review scaffolding (.github/ + SETUP.md) in a project — the ‘ligarb setup-github-review` command. This is pure file copying: ligarb never calls Claude or GitHub at runtime. The templates are classified into layers so a future option could split them apart:

- generic layer  : works without Claude (Pages deploy, build check, forms)
- claude layer    : opt-in Claude integration (issue/PR handlers, SETUP.md)

Re-running OVERWRITES the generated files so a project can follow upstream template changes (e.g. after a ligarb upgrade). The user’s own book.yml is never overwritten. Since projects are git repos, ‘git diff` is the safety net for reviewing/reverting changes after a re-sync.

Defined Under Namespace

Classes: Result

Constant Summary collapse

TEMPLATE_DIR =
File.expand_path("../../templates/github_review", __dir__)
GENERIC_FILES =

Generic layer: no Claude dependency.

%w[
  .github/workflows/deploy-book.yml
  .github/workflows/build-check.yml
  .github/ISSUE_TEMPLATE/book-feedback.yml
  .github/ISSUE_TEMPLATE/config.yml
].freeze
CLAUDE_FILES =

Claude integration layer: opt-in.

%w[
  .github/workflows/claude-feedback.yml
  .github/workflows/claude-pr-mention.yml
  SETUP.md
  SETUP.sh
].freeze
TEMPLATE_FILES =
(GENERIC_FILES + CLAUDE_FILES).freeze

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(target, owner: nil) ⇒ GithubReview

Returns a new instance of GithubReview.



66
67
68
69
# File 'lib/ligarb/github_review.rb', line 66

def initialize(target, owner: nil)
  @target = File.expand_path(target)
  @owner = owner
end

Class Method Details

.run(directory = nil, owner: nil) ⇒ Object

‘ligarb setup-github-review [DIR]` entry point. Sets up the scaffolding in an existing ligarb project (book.yml must exist), enables the reader feedback UI in book.yml, and prints the remaining manual-setup steps. Safe to re-run to pull in updated templates (generated files are overwritten; book.yml is not).



46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
# File 'lib/ligarb/github_review.rb', line 46

def self.run(directory = nil, owner: nil)
  target = File.expand_path(directory || ".")
  unless File.exist?(File.join(target, "book.yml"))
    $stderr.puts "Error: book.yml not found in #{target}"
    $stderr.puts "Run 'ligarb init' or 'ligarb write' first, then set up the GitHub review scaffolding."
    exit 1
  end

  reviewer = new(target, owner: owner)
  # book.yml edits must run BEFORE generate so the templates (SETUP.sh,
  # issue forms, README) are substituted with the resolved repository.
  repository = reviewer.ensure_repository_in_book_yml
  site_url = reviewer.ensure_site_url_in_book_yml
  enabled = reviewer.enable_in_book_yml
  readme = reviewer.create_readme_if_absent
  result = reviewer.generate
  reviewer.print_notice(result, repository: repository, site_url: site_url,
                        enabled: enabled, readme: readme)
end

Instance Method Details

#create_readme_if_absentObject

Creates a project README.md that links to the published GitHub Pages site, but only when one does not already exist (the reader’s own README is never overwritten). Returns :created or :present.



177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
# File 'lib/ligarb/github_review.rb', line 177

def create_readme_if_absent
  readme = File.join(@target, "README.md")
  return :present if File.exist?(readme)

  owner, repo = extract_owner_repo
  pages  = owner && repo ? pages_url(owner, repo) : "https://__OWNER__.github.io/__REPO__/"
  issues = owner && repo ? "https://github.com/#{owner}/#{repo}/issues/new?template=book-feedback.yml" \
                         : "https://github.com/__OWNER__/__REPO__/issues/new?template=book-feedback.yml"
  title = book_title.to_s.empty? ? "Book" : book_title

  File.write(readme, <<~MD)
    # #{title}

    📖 **公開版(GitHub Pages)**: #{pages}

    この本は [ligarb](https://github.com/ko1/ligarb) で生成しています。

    ## フィードバック

    本文の誤り・わかりにくい点・疑問は [Issue](#{issues}) からどうぞ。
    公開ページでは本文を選択して「Report as issue」からも送れます。

    ## ローカルでビルド

    ```bash
    ligarb build   # build/index.html を生成
    ligarb serve   # ローカルプレビュー
    ```

    セットアップ手順は [SETUP.md](SETUP.md) を参照してください。
  MD
  :created
end

#enable_in_book_ymlObject

Ensures ‘github_review.enabled: true` is present in book.yml so the reader feedback UI activates (once `repository` is also set). Appends the key only when absent, preserving existing formatting/comments. Returns :added, :present, or :unsupported (translations hub / unparsable).



104
105
106
107
108
109
110
111
112
113
# File 'lib/ligarb/github_review.rb', line 104

def enable_in_book_yml
  book_yml = File.join(@target, "book.yml")
  data = YAML.safe_load_file(book_yml)
  return :unsupported unless data.is_a?(Hash)
  return :present if data.key?("github_review")

  content = File.read(book_yml).rstrip
  File.write(book_yml, "#{content}\n\ngithub_review:\n  enabled: true\n")
  :added
end

#ensure_repository_in_book_ymlObject

Seeds a default ‘repository:` in book.yml when it has none, guessing github.com/<owner>/<dir-name>. <owner> is the –owner/–user flag when given, else $USER. This drives __OWNER__/__REPO__ substitution and the GH Pages link; the user edits it if the guess is wrong. Returns :added, :present, or :unsupported. (@default_repository is set to the guessed URL when :added, for the notice.)

Aborts if –owner was given but book.yml already has a ‘repository:`, since we never rewrite an existing repository — the user edits it themselves.



124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
# File 'lib/ligarb/github_review.rb', line 124

def ensure_repository_in_book_yml
  book_yml = File.join(@target, "book.yml")
  data = YAML.safe_load_file(book_yml)
  return :unsupported unless data.is_a?(Hash)
  if data.key?("repository")
    if @owner
      abort "Error: --owner was given but book.yml already has 'repository: #{data["repository"]}'.\n" \
            "Edit 'repository:' in book.yml directly to change the owner, then re-run setup-github-review."
    end
    return :present
  end

  owner = @owner || ENV["USER"] || ENV["USERNAME"] || "your-github-account"
  @default_repository = "https://github.com/#{owner}/#{File.basename(@target)}"
  content = File.read(book_yml).rstrip
  File.write(book_yml, %(#{content}\n\nrepository: "#{@default_repository}"\n))
  :added
end

#ensure_site_url_in_book_ymlObject

Seeds a default ‘site_url:` in book.yml when it has none, deriving the GitHub Pages URL from `repository`. Drives og:url / canonical in the build output; the user edits it for custom domains or other hosting. Returns :added, :present, or :skipped (when no GitHub repository to derive from). (@default_site_url is set to the derived URL when :added, for the notice.)



148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/ligarb/github_review.rb', line 148

def ensure_site_url_in_book_yml
  book_yml = File.join(@target, "book.yml")
  data = YAML.safe_load_file(book_yml)
  return :skipped unless data.is_a?(Hash)
  return :present if data.key?("site_url")

  owner, repo = extract_owner_repo
  return :skipped unless owner && repo

  @default_site_url = pages_url(owner, repo)
  content = File.read(book_yml).rstrip
  File.write(book_yml, %(#{content}\n\nsite_url: "#{@default_site_url}"\n))
  :added
end

#generateObject

Writes all template files into the project, substituting __OWNER__ / __REPO__ from book.yml’s ‘repository:`. Existing files are OVERWRITTEN so a project can follow upstream template changes; only files whose content is already identical are left untouched. Returns a Result listing created/updated/unchanged files.



76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/ligarb/github_review.rb', line 76

def generate
  owner, repo = extract_owner_repo
  created = []
  updated = []
  unchanged = []

  TEMPLATE_FILES.each do |rel|
    dest = File.join(@target, rel)
    content = render(rel, File.read(File.join(TEMPLATE_DIR, rel)), owner, repo)

    if !File.exist?(dest)
      write_file(dest, content)
      created << rel
    elsif File.read(dest) == content
      unchanged << rel
    else
      write_file(dest, content)
      updated << rel
    end
  end

  Result.new(created: created, updated: updated, unchanged: unchanged)
end

#pages_url(owner, repo) ⇒ Object

GitHub Pages URL for a repo. A repository named “<owner>.github.io” is the owner’s user/org site served at the domain root; everything else is a project site served under /<repo>/.



166
167
168
169
170
171
172
# File 'lib/ligarb/github_review.rb', line 166

def pages_url(owner, repo)
  if repo.downcase == "#{owner.downcase}.github.io"
    "https://#{owner}.github.io/"
  else
    "https://#{owner}.github.io/#{repo}/"
  end
end


211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
# File 'lib/ligarb/github_review.rb', line 211

def print_notice(result, repository:, site_url:, enabled:, readme:)
  puts "Set up GitHub review scaffolding in #{@target}:"
  result.created.each { |path| puts "  created    #{path}" }
  result.updated.each { |path| puts "  updated    #{path}" }
  puts "  created    README.md (with the GitHub Pages link)" if readme == :created
  case repository
  when :added then puts "  updated    book.yml (repository: #{@default_repository})"
  end
  case site_url
  when :added then puts "  updated    book.yml (site_url: #{@default_site_url})"
  end
  case enabled
  when :added   then puts "  updated    book.yml (github_review.enabled: true)"
  when :present then puts "  kept       book.yml github_review setting"
  end
  unless result.unchanged.empty?
    puts "  unchanged  #{result.unchanged.size} file(s) already up to date"
  end
  if result.updated.any?
    puts
    puts "Note: existing scaffolding files were overwritten with the latest"
    puts "templates. Review with 'git diff' and revert any local edits you"
    puts "want to keep."
  end
  puts
  puts "Next: edit 'repository:' in book.yml if the guess is wrong, then run"
  puts "the gh CLI quickstart:"
  puts "  bash SETUP.sh    # repo create + secret + Pages + permissions + labels"
  puts
  puts "It still needs a token (see SETUP.md): generate one with"
  puts "'claude setup-token' before running SETUP.sh (it prompts for it)."
end