Files
jekyll/Rakefile
Matt Rogers 5cf05d7d09 feat!: Streamline the release process for Jekyll (#9760)
This is a 🙋 feature or enhancement. 
This is a 🔨 code refactoring. 

## Summary

Improve and streamline our release processes with some extra automation
and a bit more rigor around PRs/commits.

## Context

With Jekyll 4.4 released, and under the assumption that the next release
will indeed be 5.0, I think it makes a lot of sense to take some time
and evaluate our development practices and streamline the process of
shipping. We generally go a long time (four months between 4.3.4 and
4.4.0, two years between 4.3.0 and 4.4.0) between releases and this is
my attempt at trying to improve that. While this PR is currently
incomplete, if there's interest in going this direction, I'll make time
over the next few days to clean it up and get it ready to actually ship.

In order to do this, I'm relying on the
[`release-please`](https://github.com/googleapis/release-please-action)
action from Google to do the majority of the heavy lifting. Please read
the release-please README in order to learn how release-please works and
what it does. In order to make it easier to adopt release-please, I've
made two additional changes. ~~The first is to rename `History.markdown`
to `CHANGELOG.md` since that's what `release-please` works with out of
the box.~~ The other is to add two new github actions workflows to run
release-please and to enforce conventional commit conventions on PR
titles. Because we squash merge, the PR title becomes the commit message
and `release-please` uses the commit messages to know when to bump the
version number.

One potential caveat with this is that it may become harder to maintain
stable branches. My point of view on this is that we've done a
relatively poor job of maintaining them regardless and I think it's more
important to release often, even if we end up bumping major or minor
version numbers more frequently than before. My stance on this is that
version bumps have no inherent goodness or badness. They are a
communication mechanism. We should let go of having to wait a certain
amount of time to do major version bumps or avoid work because it would
cause a major version bump, for example.

### Process changes

The use of release-please means that we can stop using jekyllbot to do
the merges and update History.markdown for us, as release-please will
take care of that when we cut the release. We will also no longer need
labels on PRs as the use of conventional commits will explain exactly
what is changing.

The process for releasing becomes:
 - Merge the docs PR
 - Merge the automatically generated release-please PR, which will
trigger the workflows to do the tagging, releasing, gem publishing, etc.

### Remaining work to do:

- [x] Change the pull request settings to only allow squash merges, as
jekyllbot enforces this for us today.
- [x] ~Update the site publishing process to pull from `CHANGELOG.md`
instead of `History.markdown`~ No longer needed.
- [x] Integrate jekyllbot into release-please (the release-please PRs
will be made by jekyllbot). This allows actions to be triggered on the
release-please PRs.
 - [x] Test the workflows to make sure they generate a PR correctly.
 - [x] ~Integrate the release publishing workflow into release-please
when it creates a release.~ Happens automatically with the existing
workflows.
2026-04-17 13:38:58 -05:00

172 lines
4.3 KiB
Ruby

# frozen_string_literal: true
require "rubygems"
require "rake"
require "rdoc"
require "date"
require "yaml"
$LOAD_PATH.unshift File.expand_path("lib", __dir__)
require "jekyll/version"
require "bundler/gem_tasks"
Dir.glob("rake/**.rake").each { |f| import f }
#############################################################################
#
# Helper functions
#
#############################################################################
def name
"jekyll"
end
def version
Jekyll::VERSION
end
def docs_name
"#{name}-docs"
end
def docs_folder
"docs"
end
def gemspec_file
"#{name}.gemspec"
end
def gem_file
"#{name}-#{Gem::Version.new(version)}.gem"
end
def normalize_bullets(markdown)
# Normalize both old-style indented bullets (" *") and
# release-please-style non-indented bullets ("*") to "-"
markdown.gsub(%r!\n\s{0,2}\*{1}!, "\n-")
end
def linkify_prs(markdown)
# Match bare PR references like #1234 but skip those already linkified
# by release-please as [#1234](url)
markdown.gsub(%r{(?<!&)(?<!\[)#(\d+)}) do |word|
"[#{word}]({{ site.repository }}/issues/#{word.delete("#")})"
end
end
def linkify(markdown)
linkify_prs(markdown)
end
def liquid_escape(markdown)
markdown.gsub(%r!(`{[{%].+[}%]}`)!, "{% raw %}\\1{% endraw %}")
end
def custom_release_header_anchors(markdown)
# Match old format: "X.Y.Z / YYYY-MM-DD"
# Match new release-please format: "[X.Y.Z](compare-url) (YYYY-MM-DD)"
header_regexp = %r!^(?:\[?)(\d{1,2})\.(\d{1,2})\.(\d{1,2})(?:\]\([^)]*\))? [\(/]\s?\d{4}-\d{2}-\d{2}\)?!
section_regexp = %r!^### \w[\w ]*$!
markdown.split(%r!^##\s!).map do |release_notes|
_, major, minor, patch = *release_notes.match(header_regexp)
release_notes
.gsub(header_regexp, "\\0\n{: #v\\1-\\2-\\3}")
.gsub(section_regexp) { |section| "#{section}\n{: ##{slugify(section)}-v#{major}-#{minor}-#{patch}}" }
end.join("\n## ")
end
def slugify(header)
header.delete("#").strip.downcase.gsub(%r!\s+!, "-")
end
def remove_head_from_history(markdown)
# Match both old format "## X.Y.Z" and release-please format "## [X.Y.Z]"
index = markdown =~ %r!^##\s+\[?\d+\.\d+\.\d+!
markdown[index..-1]
end
def converted_history(markdown)
remove_head_from_history(
custom_release_header_anchors(
liquid_escape(
linkify(
normalize_bullets(markdown)
)
)
)
)
end
def siteify_file(file, overrides_front_matter = {})
abort "You seem to have misplaced your #{file} file. I can haz?" unless File.exist?(file)
title = begin
File.read(file).match(%r!\A# (.*)$!)[1]
rescue NoMethodError
File.basename(file, ".*").downcase.capitalize
end
slug = File.basename(file, ".markdown").downcase
front_matter = {
"title" => title,
"permalink" => "/docs/#{slug}/",
"note" => "This file is autogenerated. Edit /#{file} instead.",
}.merge(overrides_front_matter)
contents = "#{front_matter.to_yaml}---\n\n#{content_for(file)}"
File.write("#{docs_folder}/_docs/#{slug}.md", contents)
end
def content_for(file)
contents = File.read(file)
case file
when "History.markdown"
converted_history(contents)
else
contents.gsub(%r!\A# .*\n\n?!, "")
end
end
#############################################################################
#
# Standard tasks
#
#############################################################################
multitask :default => [:test, :features]
task :spec => :test
require "rake/testtask"
Rake::TestTask.new(:test) do |test|
test.libs << "lib" << "test"
test.pattern = "test/**/test_*.rb"
test.verbose = true
end
require "rdoc/task"
Rake::RDocTask.new do |rdoc|
rdoc.rdoc_dir = "rdoc"
rdoc.title = "#{name} #{version}"
rdoc.rdoc_files.include("README*")
rdoc.rdoc_files.include("lib/**/*.rb")
end
begin
require "cucumber/rake/task"
Cucumber::Rake::Task.new(:features) do |t|
t.profile = "travis"
end
Cucumber::Rake::Task.new(:"features:html", "Run Cucumber features and produce HTML output") do |t|
t.profile = "html_report"
end
rescue LoadError
desc "Cucumber rake task not available"
task :features do
abort "Cucumber rake task is not available. Be sure to install cucumber as a gem or plugin"
end
end
desc "Open an irb session preloaded with this library"
task :console do
sh "irb -r ./lib/#{name}.rb"
end