738f8ed462
* Start using `yarn` for vendor assets
Until this commit, Gollum has included JavaScript assets by committing
the source code to the `lib/gollum/public/gollum/javascript/` directory
and including them for compilation in the Sprockets asset manifest file
at `lib/gollum/public/gollum/javascript/app.js`. This has been a
reasonable way to deal with third-party JavaScript, but there are a few
downsides:
- It's a burden to find out if JavaScript dependencies have been
updated and require updating in Gollum.
- It doesn't give us good visibility into the JavaScript dependencies
required by our dependencies.
- It forces us to commit external code to our repository, which can
make our developer tools more difficult to configure or use. For
example: when I search for key words in the repository using
Ripgrep, I often get "garbage" results from minified JavaScript.
Managing JavaScript dependencies via a JS package manager can resolve
all of these issues.
This commit allows us to manage JavaScript dependencies using the Yarn
package manager for Node JS modules[1]. I chose Yarn over NPM for one
reason: Yarn is the JavaScript package manager that Rails uses by
default. So many Ruby developers will already be familiar with Yarn.
To demonstrate how this can change how we manage JS assets in Gollum,
I've configured Yarn and started to manage the `mousetrap` dependency
with it. I chose `mousetrap` to start because it's a smaller, mostly
uncomplicated dependency. I was easily able to manually test that
`mousetrap` is still working after re-compiling the assets.
Hopefully this gives anyone reading enough context to jump in and start
moving our third-party JS assets out of the codebase.
[1]: https://yarnpkg.com/
* Recompile assets
* Add dev environment setup info to CONTRIBUTING
Now that we require additional tooling to manage JavaScript
dependencies, it seemed reasonable to add more documentation around
setting up one's development environment.
* Don't compile assets without `yarn`-managed ones
If a developer compiled assets without first running `yarn install`, we
would get incomplete collection of up-to-date assets.
Let's ensure that the developer has all the required assets by enforcing
this in the precompile task they should be using to compile production
assets to begin with.
261 lines
6.7 KiB
Ruby
261 lines
6.7 KiB
Ruby
require 'rubygems'
|
|
require 'rake'
|
|
require 'date'
|
|
require 'tempfile'
|
|
|
|
#############################################################################
|
|
#
|
|
# Helper functions
|
|
#
|
|
#############################################################################
|
|
|
|
def date
|
|
Time.now.strftime("%Y-%m-%d")
|
|
end
|
|
|
|
def name
|
|
@name ||= Dir['*.gemspec'].first.split('.').first
|
|
end
|
|
|
|
def version
|
|
line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
|
|
line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
|
|
end
|
|
|
|
def latest_changes_file
|
|
'LATEST_CHANGES.md'
|
|
end
|
|
|
|
def history_file
|
|
'HISTORY.md'
|
|
end
|
|
|
|
# assumes x.y.z all digit version
|
|
def next_version
|
|
# x.y.z
|
|
v = version.split '.'
|
|
# bump z
|
|
v[-1] = v[-1].to_i + 1
|
|
v.join '.'
|
|
end
|
|
|
|
def bump_version
|
|
old_file = File.read("lib/#{name}.rb")
|
|
old_version_line = old_file[/^\s*VERSION\s*=\s*.*/]
|
|
new_version = next_version
|
|
# replace first match of old vesion with new version
|
|
old_file.sub!(old_version_line, " VERSION = '#{new_version}'")
|
|
|
|
File.write("lib/#{name}.rb", old_file)
|
|
|
|
new_version
|
|
end
|
|
|
|
def gemspec_file
|
|
"#{name}.gemspec"
|
|
end
|
|
|
|
def gem_file
|
|
"#{name}-#{version}.gem"
|
|
end
|
|
|
|
def replace_header(head, header_name)
|
|
head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
|
|
end
|
|
|
|
#############################################################################
|
|
#
|
|
# Standard tasks
|
|
#
|
|
#############################################################################
|
|
|
|
task :default => :test
|
|
|
|
require 'rake/testtask'
|
|
Rake::TestTask.new(:test) do |test|
|
|
test.libs << 'lib' << 'test' << '.'
|
|
test.pattern = 'test/**/test_*.rb'
|
|
test.verbose = true
|
|
test.warning = false
|
|
end
|
|
|
|
desc "Generate RCov test coverage and open in your browser"
|
|
task :coverage do
|
|
require 'rcov'
|
|
sh "rm -fr coverage"
|
|
sh "rcov test/test_*.rb"
|
|
sh "open coverage/index.html"
|
|
end
|
|
|
|
desc "Open an irb session preloaded with this library"
|
|
task :console do
|
|
sh "irb -rubygems -r ./lib/#{name}.rb"
|
|
end
|
|
|
|
#############################################################################
|
|
#
|
|
# Custom tasks (add your own tasks here)
|
|
#
|
|
#############################################################################
|
|
|
|
desc "Update version number and gemspec"
|
|
task :bump do
|
|
puts "Updated version to #{bump_version}"
|
|
# Execute does not invoke dependencies.
|
|
# Manually invoke gemspec then validate.
|
|
Rake::Task[:gemspec].execute
|
|
Rake::Task[:validate].execute
|
|
end
|
|
|
|
#############################################################################
|
|
#
|
|
# Packaging tasks
|
|
#
|
|
#############################################################################
|
|
|
|
desc 'Create a release build and push to rubygems'
|
|
task :release => :build do
|
|
unless `git branch` =~ /^\* master$/
|
|
puts "You must be on the master branch to release!"
|
|
exit!
|
|
end
|
|
Rake::Task[:changelog].execute
|
|
sh "git commit --allow-empty -a -m 'Release #{version}'"
|
|
sh "git pull --rebase origin master"
|
|
sh "git tag v#{version}"
|
|
sh "git push origin master"
|
|
sh "git push origin v#{version}"
|
|
sh "gem push pkg/#{name}-#{version}.gem"
|
|
end
|
|
|
|
desc 'Publish to rubygems. Same as release'
|
|
task :publish => :release
|
|
|
|
desc 'Build gem'
|
|
task :build => :gemspec do
|
|
sh "mkdir -p pkg"
|
|
sh "gem build #{gemspec_file}"
|
|
sh "mv #{gem_file} pkg"
|
|
end
|
|
|
|
desc "Build and install"
|
|
task :install => :build do
|
|
sh "gem install --local --no-document pkg/#{name}-#{version}.gem"
|
|
end
|
|
|
|
desc 'Update gemspec'
|
|
task :gemspec => :validate do
|
|
# read spec file and split out manifest section
|
|
spec = File.read(gemspec_file)
|
|
head, manifest, tail = spec.split(" # = MANIFEST =\n")
|
|
|
|
# replace name and version
|
|
replace_header(head, :name)
|
|
replace_header(head, :version)
|
|
|
|
# determine file list from git ls-files
|
|
files = `git ls-files`.
|
|
split("\n").
|
|
sort.
|
|
reject { |file| file =~ /^\./ }.
|
|
reject { |file| file =~ /^(rdoc|pkg|test|Home\.md|\.gitattributes)/ }.
|
|
map { |file| " #{file}" }.
|
|
join("\n")
|
|
|
|
# piece file back together and write
|
|
manifest = " s.files = %w[\n#{files}\n ]\n"
|
|
spec = [head, manifest, tail].join(" # = MANIFEST =\n")
|
|
File.open(gemspec_file, 'w') { |io| io.write(spec) }
|
|
puts "Updated #{gemspec_file}"
|
|
end
|
|
|
|
desc 'Validate lib files and version file'
|
|
task :validate do
|
|
libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
|
|
unless libfiles.empty?
|
|
puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
|
|
exit!
|
|
end
|
|
unless Dir['VERSION*'].empty?
|
|
puts "A `VERSION` file at root level violates Gem best practices."
|
|
exit!
|
|
end
|
|
end
|
|
|
|
desc 'Build changlog'
|
|
task :changelog do
|
|
[latest_changes_file, history_file].each do |f|
|
|
unless File.exists?(f)
|
|
puts "#{f} does not exist but is required to build a new release."
|
|
exit!
|
|
end
|
|
end
|
|
|
|
latest_changes = File.open(latest_changes_file)
|
|
version_pattern = "# #{version}"
|
|
|
|
if !`grep "#{version_pattern}" #{history_file}`.empty?
|
|
puts "#{version} is already described in #{history_file}"
|
|
exit!
|
|
end
|
|
|
|
begin
|
|
unless latest_changes.readline.chomp! =~ %r{#{version_pattern}}
|
|
puts "#{latest_changes_file} should begin with '#{version_pattern}'"
|
|
exit!
|
|
end
|
|
rescue EOFError
|
|
puts "#{latest_changes_file} is empty!"
|
|
exit!
|
|
end
|
|
|
|
body = latest_changes.read
|
|
body.scan(/\s*#\s+\d\.\d.*/) do |match|
|
|
puts "#{latest_changes_file} may not contain multiple markdown headers!"
|
|
exit!
|
|
end
|
|
|
|
temp = Tempfile.new
|
|
temp.puts("#{version_pattern} / #{date}\n#{body}\n")
|
|
temp.close
|
|
`cat #{history_file} >> #{temp.path}`
|
|
`cat #{temp.path} > #{history_file}`
|
|
end
|
|
|
|
desc 'Precompile assets'
|
|
task :precompile do
|
|
# Attempt to install JavaScript dependencies managed by Yarn via the
|
|
# `package.json` file in Gollum's project root. If it fails, raise an error
|
|
# and exit the task early.
|
|
puts "\n Installing `yarn`-managed JavaScript dependencies... \n\n"
|
|
system "yarn install"
|
|
unless $?.success?
|
|
raise "This task tried to run `yarn install` to get up-to-date " \
|
|
"JavaScript dependencies before precompilation. But it failed. Please " \
|
|
"run `yarn install` manually from your shell and resolve any issues. " \
|
|
"It's possible that you just need to install `yarn` on your system."
|
|
end
|
|
|
|
require './lib/gollum/app.rb'
|
|
|
|
# Next, configure the Sprockets asset pipeline and precompile production-
|
|
# ready assets.
|
|
Precious::App.set(:environment, :production)
|
|
|
|
env = Precious::Assets.sprockets
|
|
path = ENV.fetch 'GOLLUM_ASSETS_PATH',
|
|
File.join(File.dirname(__FILE__), 'lib/gollum/public/assets')
|
|
manifest = Sprockets::Manifest.new(env, path)
|
|
|
|
Sprockets::Helpers.configure do |config|
|
|
config.environment = env
|
|
config.prefix = Precious::Assets::ASSET_URL
|
|
config.digest = true
|
|
config.public_path = path
|
|
config.manifest = manifest
|
|
end
|
|
|
|
puts "\n Precompiling assets to #{path}... \n\n"
|
|
manifest.compile(Precious::Assets::MANIFEST)
|
|
end
|