#!/usr/bin/env ruby unless File.respond_to? :realpath class File #:nodoc: def self.realpath path return realpath(File.readlink(path)) if symlink?(path) path end end end $: << File.expand_path(File.dirname(File.realpath(__FILE__)) + '/../lib') require 'hatchet' require 'thor' require 'threaded' require 'date' require 'yaml' class HatchetCLI < Thor desc "ci:install_heroku", "installs the `heroku` cli" define_method("ci:install_heroku") do if `which heroku` && $?.success? puts "The `heroku` command is already installed" return else puts "installing `heroku` command" end script = File.expand_path File.join(__dir__, "../etc/setup_heroku.sh") cmd(script) end desc "ci:setup", "sets up project to run on a linux CI environment" define_method("ci:setup") do script = File.expand_path File.join(__dir__, "../etc/ci_setup.rb") cmd(script) end desc "install", "installs repos defined in 'hatchet.json'" def install warn_dot_ignore! lock_hash = load_lockfile puts "Installing repos for hatchet" missing_commit = false dirs.map do |directory, git_repo| Threaded.later do commit = lock_hash[directory] directory = File.expand_path(directory) if !Dir[directory]&.empty? puts "== pulling '#{git_repo}' into '#{directory}'\n" pull(directory, git_repo) else puts "== cloning '#{git_repo}' into '#{directory}'\n" clone(directory, git_repo) end if commit checkout_commit(directory, commit) else missing_commit = true end end end.map(&:join) self.lock if missing_commit end desc "locks to specific git commits", "updates hatchet.lock" def lock lock_hash = {} lockfile_hash = load_lockfile(create_if_does_not_exist: true) dirs.map do |directory, git_repo| Threaded.later do puts "== locking #{directory}" unless Dir.exist?(directory) raise "Bad git repo #{git_repo.inspect}" if bad_repo?(git_repo) clone(directory, git_repo, quiet: false) end if lockfile_hash[directory] == "master" lock_hash[directory] = "master" elsif lockfile_hash[directory] == "main" lock_hash[directory] = "main" else commit = commit_at_directory(directory) lock_hash[directory] = commit end end end.map(&:join) lock_hash = lock_hash.sort File.open('hatchet.lock', 'w') {|f| f << lock_hash.to_yaml } puts "Done!" end desc "list", "lists all repos and their destination listed in hatchet.json" def list repos.each do |repo, directory| puts "#{repo}: #{directory}" end end desc "destroy [NAME]", "Deletes applications" option :all, :type => :boolean def destroy(name=nil) api_key = ENV['HEROKU_API_KEY'] || `heroku auth:token`.chomp platform_api = PlatformAPI.connect_oauth(api_key, cache: Moneta.new(:Null)) api_rate_limit = ApiRateLimit.new(platform_api) reaper = Hatchet::Reaper.new(api_rate_limit: api_rate_limit) if options[:all] reaper.destroy_all elsif !name.nil? reaper.destroy_by_name(name) else raise "Must provide an app name or --all" end end private def load_lockfile(create_if_does_not_exist: false) return YAML.safe_load(File.read('hatchet.lock')).to_h rescue Errno::ENOENT if create_if_does_not_exist FileUtils.touch('hatchet.lock') {} else raise "No such file found `hatchet.lock` please run `$ bundle exec hatchet lock`" end end def bad_repo?(url) `git ls-remote --exit-code -h "#{url}"` $? != 0 end def warn_dot_ignore! return false unless File.exist?('.gitignore') gitignore = File.open('.gitignore').read repo_path = config.repo_directory_path.gsub(/^\.\//, '') # remove ./ from front of dir return if gitignore.include?(repo_path) puts "WARNING: add #{File.join(repo_path, '*')} to your .gitignore file \n\n" end def config @config ||= Hatchet::Config.new end def repos config.repos end def dirs config.dirs end def checkout_commit(directory, commit) cmd("cd #{directory} && git fetch origin #{commit} && git checkout #{commit} && git checkout - && git reset --hard #{commit}") end def commit_at_directory(directory) cmd("cd #{directory} && git log -n1 --pretty=format:%H").strip end def pull(path, git_repo, commit: false) cmd("cd #{path} && git pull --rebase #{git_repo} --quiet") end def clone(path, git_repo, quiet: true) path = File.join(path, '..') # up one dir to prevent repos/codetriage/codetriage/#... FileUtils.mkdir_p(path) cmd("cd #{path} && git clone #{git_repo} --quiet", quiet: quiet) end def cmd(command, let_fail: false, stdin: nil, quiet: true) command = "printf '#{stdin}' | #{command}" if stdin puts "Running: #{command}" unless quiet result = `#{command}` return result if let_fail raise "Command #{command} failed:\n#{result}" unless $?.success? return result end end HatchetCLI.start(ARGV)