first commit
This commit is contained in:
@@ -0,0 +1,295 @@
|
||||
#!/usr/bin/env ruby
|
||||
|
||||
class DevHelper
|
||||
# Support dashes in command names
|
||||
COMMAND_TO_METHOD = {
|
||||
"ts-node" => :ts_node,
|
||||
"check-types" => :check_types,
|
||||
"bash-completions" => :bash_completions,
|
||||
"plantuml-to-png" => :plantuml_to_png,
|
||||
}
|
||||
METHOD_TO_COMMAND = COMMAND_TO_METHOD.invert
|
||||
|
||||
REPLACE_PROCESS = "replace_process"
|
||||
WAIT_FOR_PROCESS = "wait_for_process"
|
||||
|
||||
# External Interface
|
||||
def self.call(*args)
|
||||
new.call(*args)
|
||||
end
|
||||
|
||||
# Core logic
|
||||
def call(*args, **kwargs)
|
||||
command = args[0]
|
||||
method = COMMAND_TO_METHOD.fetch(command, command)
|
||||
if args.length.positive? && respond_to?(method)
|
||||
public_send(method, *args.drop(1), **kwargs)
|
||||
else
|
||||
compose(*args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def compose(*args, **kwargs)
|
||||
command = compose_command(*args, **kwargs)
|
||||
puts "Running: #{command}" unless kwargs[:slient]
|
||||
|
||||
case kwargs[:execution_mode]
|
||||
when WAIT_FOR_PROCESS
|
||||
wait_for_process_with_logging(command)
|
||||
else
|
||||
exec(command)
|
||||
end
|
||||
end
|
||||
|
||||
# Primary command wrappers
|
||||
def build(*args, **kwargs)
|
||||
compose(%w[build], *args, **kwargs)
|
||||
end
|
||||
|
||||
def compile(*args, **kwargs)
|
||||
run(*%w[api npm run build], execution_mode: WAIT_FOR_PROCESS)
|
||||
exit($?.exitstatus) unless $?.success?
|
||||
run(*%w[web npm run build])
|
||||
end
|
||||
|
||||
def up(*args, **kwargs)
|
||||
compose(*%w[up --remove-orphans], *args, **kwargs)
|
||||
end
|
||||
|
||||
def down(*args, **kwargs)
|
||||
compose(*%w[down --remove-orphans], *args, **kwargs)
|
||||
end
|
||||
|
||||
def logs(*args, **kwargs)
|
||||
compose(*%w[logs -f], *args, **kwargs)
|
||||
end
|
||||
|
||||
def run(*args, **kwargs)
|
||||
compose(*%w[run --rm], *args, **kwargs)
|
||||
end
|
||||
|
||||
def ps(*args, **kwargs)
|
||||
compose(*%w[ps], *args, **kwargs)
|
||||
end
|
||||
|
||||
# Custom helpers
|
||||
def api(*args, **kwargs)
|
||||
run(*%w[api], *args, **kwargs)
|
||||
end
|
||||
|
||||
def web(*args, **kwargs)
|
||||
run(*%w[web], *args, **kwargs)
|
||||
end
|
||||
|
||||
def check_types(*args, **kwargs)
|
||||
run(*%w[api npm run check-types], *args, **kwargs)
|
||||
end
|
||||
|
||||
def test(*args, **kwargs)
|
||||
service = args[0]
|
||||
if service == "api"
|
||||
test_api(*args.drop(1), **kwargs)
|
||||
elsif service == "web"
|
||||
test_web(*args.drop(1), **kwargs)
|
||||
else
|
||||
test_api(*args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def test_api(*args, **kwargs)
|
||||
reformat_project_relative_path_filter_for_vitest!(args, "api/")
|
||||
run(*%w[test_api npm run test], *args, **kwargs)
|
||||
end
|
||||
|
||||
def test_web(*args, **kwargs)
|
||||
reformat_project_relative_path_filter_for_vitest!(args, "web/")
|
||||
run(*%w[test_web npm run test], *args, **kwargs)
|
||||
end
|
||||
|
||||
def sqlcmd(*args, **kwargs)
|
||||
db_host = ENV.fetch('DB_HOST', 'localhost')
|
||||
db_user = ENV.fetch('DB_USER', 'sa')
|
||||
db_pass = ENV.fetch('DB_PASS', '1m5ecure!')
|
||||
db_name = ENV.fetch('DB_NAME', 'YHSI')
|
||||
compose(
|
||||
*%w[exec db /opt/mssql-tools/bin/sqlcmd],
|
||||
*%W[-U #{db_user}],
|
||||
*%W[-P #{db_pass}],
|
||||
*%W[-H #{db_host}],
|
||||
*%W[-d #{db_name}],
|
||||
'-I', # enable quoted identifiers, e.g. "table"."column"
|
||||
*args,
|
||||
**kwargs
|
||||
)
|
||||
end
|
||||
|
||||
def db(*args, **kwargs)
|
||||
compose(*%w[exec db], *args, **kwargs)
|
||||
end
|
||||
|
||||
def debug
|
||||
api_container_id = container_id("api")
|
||||
puts "Waiting for breakpoint to trigger..."
|
||||
puts "'ctrl-c' to exit."
|
||||
command = "docker attach --detach-keys ctrl-c #{api_container_id}"
|
||||
puts "Running: #{command}"
|
||||
exec(command)
|
||||
exit 0
|
||||
end
|
||||
|
||||
def npm(*args, **kwargs)
|
||||
run(*%w[api npm], *args, **kwargs)
|
||||
end
|
||||
|
||||
def ts_node(*args, **kwargs)
|
||||
run(*%w[api npm run ts-node], *args, **kwargs)
|
||||
end
|
||||
|
||||
def knex(*args, **kwargs)
|
||||
if RUBY_PLATFORM =~ /linux/
|
||||
run(*%w[api npm run knex], *args, execution_mode: WAIT_FOR_PROCESS, **kwargs)
|
||||
|
||||
file_or_directory = "#{project_root}/api/src/db/migrations"
|
||||
exit(0) unless take_over_needed?(file_or_directory)
|
||||
|
||||
ownit file_or_directory
|
||||
else
|
||||
run(*%w[api npm run knex], *args, **kwargs)
|
||||
end
|
||||
end
|
||||
|
||||
def migrate(*args, **kwargs)
|
||||
action = args[0]
|
||||
knex("migrate:#{action}", *args.drop(1), **kwargs)
|
||||
end
|
||||
|
||||
def seed(*args, **kwargs)
|
||||
action = args[0]
|
||||
knex("seed:#{action}", *args.drop(1), **kwargs)
|
||||
end
|
||||
|
||||
def ownit(*args, **kwargs)
|
||||
file_or_directory = args[0]
|
||||
raise ScriptError, "Must provide a file or directory path." if file_or_directory.nil?
|
||||
|
||||
if RUBY_PLATFORM =~ /linux/
|
||||
puts "Take ownership of the file or directory? #{file_or_directory}"
|
||||
exec("sudo chown -R #{user_id}:#{group_id} #{file_or_directory}")
|
||||
else
|
||||
raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}"
|
||||
end
|
||||
end
|
||||
|
||||
def plantuml_to_png(*args, **kwargs)
|
||||
file_path = args.pop
|
||||
raise ScriptError, "Must provide a file path." if file_path.nil?
|
||||
|
||||
png_path = file_path.gsub(/\.(wsd|pu|puml|plantuml|uml)$/, ".png")
|
||||
|
||||
command = <<~BASH
|
||||
curl #{args.join(" ")} \
|
||||
--data-binary @'#{file_path}' \
|
||||
http://localhost:9999/png > '#{png_path}'
|
||||
BASH
|
||||
|
||||
puts "Running: #{command}"
|
||||
exec(command)
|
||||
end
|
||||
|
||||
def bash_completions
|
||||
completions =
|
||||
public_methods(false)
|
||||
.reject { |word| %i[call].include?(word) }
|
||||
.map { |word| METHOD_TO_COMMAND.fetch(word, word) }
|
||||
puts completions
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def wait_for_process_with_logging(command)
|
||||
IO.popen("#{command} 2>&1") do |io|
|
||||
until io.eof?
|
||||
line = io.gets
|
||||
puts line
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def container_id(container_name, *args, **kwargs)
|
||||
command = compose_command(*%w[ps -q], container_name, *args, **kwargs)
|
||||
puts "Running: #{command}"
|
||||
id_of_container = `#{command}`.chomp
|
||||
puts "Container id is: #{id_of_container}"
|
||||
id_of_container
|
||||
end
|
||||
|
||||
def service_running?(container_name)
|
||||
ps(*%w[-q --status=running], execution_mode: WAIT_FOR_PROCESS, slient: true) != ""
|
||||
end
|
||||
|
||||
def compose_command(*args, **kwargs)
|
||||
environment = kwargs.fetch(:environment, "development")
|
||||
"cd #{project_root} && docker compose -f docker-compose.#{environment}.yml #{args.join(" ")}"
|
||||
end
|
||||
|
||||
def project_root
|
||||
@project_root ||= File.absolute_path("#{__dir__}/..")
|
||||
end
|
||||
|
||||
def take_over_needed?(file_or_directory)
|
||||
files_owned_by_others =
|
||||
system("find #{file_or_directory} -not -user #{user_id} -print -quit | grep -q .")
|
||||
files_owned_by_others
|
||||
end
|
||||
|
||||
def user_id
|
||||
unless RUBY_PLATFORM =~ /linux/
|
||||
raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}"
|
||||
end
|
||||
|
||||
`id -u`.strip
|
||||
end
|
||||
|
||||
def group_id
|
||||
unless RUBY_PLATFORM =~ /linux/
|
||||
raise NotImplementedError, "Not implement for platform #{RUBY_PLATFORM}"
|
||||
end
|
||||
|
||||
`id -g`.strip
|
||||
end
|
||||
|
||||
def reformat_project_relative_path_filter_for_vitest!(args, prefix)
|
||||
if args.length.positive? && args[0].start_with?(prefix)
|
||||
src_path_prefix = "#{prefix}src/"
|
||||
test_path_regex = Regexp.escape(prefix)
|
||||
src_path_regex = Regexp.escape(src_path_prefix)
|
||||
|
||||
if args[0].start_with?(src_path_prefix)
|
||||
# TODO: handle other file types
|
||||
args[0] = args[0].gsub(/^#{src_path_regex}/, "tests/").gsub(/\.ts$/, ".test.ts")
|
||||
else
|
||||
args[0] = args[0].gsub(/^#{test_path_regex}/, "")
|
||||
end
|
||||
|
||||
puts "Reformatted path filter from project relative to service relative for vitest."
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
# Only execute main function when file is executed
|
||||
DevHelper.call(*ARGV) if $PROGRAM_NAME == __FILE__
|
||||
|
||||
## Dev completions
|
||||
# https://iridakos.com/programming/2018/03/01/bash-programmable-completion-tutorial
|
||||
# _dev_completions () {
|
||||
# local dev_command_path="$(which dev)"
|
||||
# local dev_function_names
|
||||
# dev_function_names="$(ruby "$dev_command_path" bash_completions)"
|
||||
# # COMP_WORDS: an array of all the words typed after the name of the program the compspec belongs to
|
||||
# # COMP_CWORD: an index of the COMP_WORDS array pointing to the word the current cursor is at - in other words, the index of the word the cursor was when the tab key was pressed
|
||||
# # COMP_LINE: the current command line
|
||||
# COMPREPLY=($(compgen -W "$dev_function_names" "${COMP_WORDS[$COMP_CWORD]}"))
|
||||
# }
|
||||
|
||||
# complete -F _dev_completions dev
|
||||
# complete -W "allow" direnv
|
||||
Reference in New Issue
Block a user