Skip to content

Commit

Permalink
Release and publish scripts v2 (#925)
Browse files Browse the repository at this point in the history
Working with bash is not the ideal dev experience, so I'm moving our scripts to `thor`! Since `thor` scripts are written with Ruby (which most of our team is experienced with), iterating over them should be easier.

I'm also adding new features to the scripts:

1. `release` will create the PR for you
2. `publish` will create the release for you

Note: It's really hard to test this since I don't want to create unnecessary releases, but I'm highly confident in the code. Worst-case scenario we can fix bugs while doing the next release
  • Loading branch information
manuelpuyol committed Dec 1, 2021
1 parent f5930a3 commit bc7abe0
Show file tree
Hide file tree
Showing 2 changed files with 185 additions and 180 deletions.
45 changes: 40 additions & 5 deletions script/publish
@@ -1,7 +1,42 @@
#!/usr/bin/env bash
#!/usr/bin/env ruby
# frozen_string_literal: true

# Publish gem
bundle exec rake release
require "thor"
require_relative "../lib/primer/view_components/version"

# Publish NPM
npm publish --access public
# Publishes `primer_view_components` gem and npm package, and creates a new GitHub release.
#
# Usage:
#
# script/publish
class PublishCLI < Thor::Group
include Thor::Actions

def release_gem
run("bundle exec release")
end

def release_npm
run("npm publish --access public")
end

def github_release
run("gh release create v#{version} -n \"#{release_notes}\"")
end

private

def release_notes
changelog.gsub(/.*## #{version}/m, "").gsub(/^## .*/m, "").strip
end

def version
Primer::ViewComponents::VERSION::STRING
end

def changelog
File.read("CHANGELOG.md")
end
end

PublishCLI.start(ARGV)
320 changes: 145 additions & 175 deletions script/release
@@ -1,176 +1,146 @@
#!/usr/bin/env bash

if [ $(uname -s) = "Darwin" ]; then
SED_ARGS="-i ''"
else
SED_ARGS="-i"
fi

fetch() {
git fetch --all
}

branch_name() {
git symbolic-ref --short HEAD
}

local_history_is_clean() {
history=$(git rev-list --count --right-only @{u}...HEAD)
[ "$history" == "0" ]
}

remote_history_is_clean() {
history=$(git rev-list --count --left-only @{u}...HEAD)
[ "$history" == "0" ]
}

tag_exists_on_remote() {
git rev-parse --quiet --verify refs/tags/v$1.$2.$3 > /dev/null
}

working_tree_is_clean() {
status=$(git status --porcelain)
[ "$status" == "" ]
}

create_release_branch() {
git switch -c release-v$1-$2-$3
}

update_readme() {
sed $SED_ARGS "/## main/ {a\
\\
\\
## $1.$2.$3
}" CHANGELOG.md
}

update_ruby_version() {
# Update version file
sed -E $SED_ARGS \
-e "s/MAJOR = [0-9]+/MAJOR = $1/g" \
-e "s/MINOR = [0-9]+/MINOR = $2/g" \
-e "s/PATCH = [0-9]+/PATCH = $3/g" \
#!/usr/bin/env ruby
# frozen_string_literal: true

require "thor"
require_relative "../lib/primer/view_components/version"

# Prepares a release PR for `primer_view_components`.
#
# Usage:
#
# script/release for a patch release
# script/release --major for a major release
# script/release --minor for a minor release
class ReleaseCLI < Thor::Group
include Thor::Actions

class_option :major, type: :boolean, desc: "Use this option to bump a major version."
class_option :minor, type: :boolean, desc: "Use this option to bump a minor version."
class_option :patch, type: :boolean, desc: "Use this option to bump a patch version."

def self.exit_on_failure?
false
end

def working_tree_is_clean
tree_status = run("git status --porcelain", capture: true).strip
raise "Tree is not clean" unless tree_status.empty?
end

def must_be_in_main
raise "Must be in the main branch to release" unless current_branch == "main"
end

def git_fetch
run("git fetch --all")
end

def remote_history_is_clean
remote_history = run("git rev-list --count --left-only @{u}...HEAD", capture: true).strip
raise "Changes exist on origin not pulled into this branch. Please pull" unless remote_history == "0"
end

def local_history_is_clean
remote_history = run("git rev-list --count --right-only @{u}...HEAD", capture: true).strip
raise "Changes exist that haven't been pushed to origin. Please pull" unless remote_history == "0"
end

def tag_exists_on_remote
raise "Tag exists on remote" unless run("git rev-parse --quiet --verify refs/tags/v#{new_version}", capture: true).strip.empty?
end

def create_release_branch
run("git switch -c #{release_branch}")
end

def update_readme
gsub_file("CHANGELOG.md", "## main", "## main\n\n## #{new_version}")
end

def update_ruby_version
major, minor, patch = new_version.split(".")

gsub_file("lib/primer/view_components/version.rb", /MAJOR = [0-9]+/, "MAJOR = #{major}")
gsub_file("lib/primer/view_components/version.rb", /MINOR = [0-9]+/, "MINOR = #{minor}")
gsub_file("lib/primer/view_components/version.rb", /PATCH = [0-9]+/, "PATCH = #{patch}")
end

def update_gemfiles
run("bundle")
run("pushd demo")
run("bundle")
run("popd")
end

def update_npm
run("npm version --no-git-tag-version \"#{new_version}\"")
run("yarn")
end

def add_changed_files
files_to_add = %w[
CHANGELOG.md
Gemfile.lock
demo/Gemfile.lock
lib/primer/view_components/version.rb
}

update_gemfiles() {
# Update Gemfile.lock
bundle
pushd demo
bundle
popd
}


update_npm() {
npm version --no-git-tag-version "$1.$2.$3"
yarn
}

add_changed_files() {
git add \
CHANGELOG.md \
Gemfile.lock \
demo/Gemfile.lock \
lib/primer/view_components/version.rb \
app/assets/javascripts \
package.json \
yarn.lock
}

commit() {
git commit -m "release $1.$2.$3"
}

push() {
git push origin release-v$1-$2-$3
}

main() {
version=$(ruby ./lib/primer/view_components/version.rb)
version=(${version//./ })
major=${version[0]}
minor=${version[1]}
patch=${version[2]}

echo "==================="
echo "Prerequisite Checks"
echo "==================="

if ! working_tree_is_clean; then
echo "Error: unclean working tree"
exit 1
fi

if [[ "$(branch_name)" != "main" ]]; then
echo "Error: can only make a release on the main branch"
exit 1
fi

fetch
if ! remote_history_is_clean; then
echo "Error: changes exist on origin not pulled into this branch. Please pull"
exit 1
fi

if ! local_history_is_clean; then
echo "Error: changes exist that haven't been pushed to origin. Please pull"
exit 1
fi

echo "Type the number of an option to bump, or pick Manual to enter a version number"
select bump in Major Minor Patch Manual
do
if [ "$bump" == "Major" ]; then
major=$((major + 1))
minor=0
patch=0
elif [ "$bump" == "Minor" ]; then
minor=$((minor + 1))
patch=0
elif [ "$bump" == "Patch" ]; then
patch=$((patch + 1))
else
read -p "What version? (Currently $major.$minor.$patch): " new_version
if [ "$new_version" == "$major.$minor.$patch" ]; then
echo "Error: Cannot be the same version"
exit 1
fi

new_version=(${new_version//./ })

major=${new_version[0]}
minor=${new_version[1]}
patch=${new_version[2]}
fi

if tag_exists_on_remote $major $minor $patch; then
echo "Error: tag exists on remote"
exit 1
fi

echo "==============================="
echo "Creating release for $major.$minor.$patch"
echo "==============================="

create_release_branch $major $minor $patch
update_readme $major $minor $patch
update_ruby_version $major $minor $patch
update_gemfiles $major $minor $patch
update_npm $major $minor $patch
add_changed_files $major $minor $patch
commit $major $minor $patch
push $major $minor $patch

echo "####################################################"
echo "Now, open a PR with this branch and merge it to main."
echo "When the PR has been merged, run script/publish to publish the gem."
echo "Finally, create a GitHub release https://github.com/primer/view_components/releases/new with the changes from CHANGELOG"
echo "####################################################"

break
done
}

main
app/assets/javascripts
package.json
yarn.lock
]

run("git add #{files_to_add.join(' ')}")
end

def commit_files
run("git commit -m \"release #{new_version}\"")
end

def push_files
run("git push origin #{release_branch}")
end

def create_pull_request
@pr_url = run("gh pr create -b "" -t \"Release v#{new_version}\"", capture: true)
end

def final_message
puts "####################################################"
puts "PR link: #{@pr_url}"
puts "When the PR has been merged, run script/publish to publish the gem."
puts "####################################################"
end

private

def current_branch
@current_branch ||= run("git symbolic-ref --short HEAD", capture: true).strip
end

def current_version
Primer::ViewComponents::VERSION::STRING
end

def new_version
@new_version ||= case bump
when :major
"#{Primer::ViewComponents::VERSION::MAJOR + 1}.0.0"
when :minor
"#{Primer::ViewComponents::VERSION::MAJOR}.#{Primer::ViewComponents::VERSION::MINOR + 1}.0"
else
"#{Primer::ViewComponents::VERSION::MAJOR}.#{Primer::ViewComponents::VERSION::MINOR}.#{Primer::ViewComponents::VERSION::PATCH + 1}"
end
end

def bump
return :major if options[:major]
return :minor if options[:minor]

:patch
end

def release_branch
"release-v#{new_version}"
end
end

ReleaseCLI.start(ARGV)

1 comment on commit bc7abe0

@vercel
Copy link

@vercel vercel bot commented on bc7abe0 Dec 1, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.