-
Notifications
You must be signed in to change notification settings - Fork 208
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
New amber watch config #865
Conversation
This PR is based on |
…/amber into fa/new-amber-watch-config
- "crystal build -o bin/<%= @name %> src/<%= @name %>.cr -p --no-color" | ||
- "bin/<%= @name %>" | ||
|
||
client: # required: these files changes trigger browser reloading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
should this be required? I generally do almost no front end code, what if I don't want watch to do anything?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@robacarp You're right, actually this field can be optional, this implementation works without a client
field
This new config works pretty nice and fix many issues, although, Does someone have any idea how to add specs for this? 😅 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There are a lot of special cases in here for the server and client watcher, but it seems like that shouldn't be necessary. The one switch that I can see which needs to be made is simply: run a command on the server, or send a message down a channel. What about changing the way the configuration is written to be more generic?
watch:
meaningless_name:
files:
- src/**/*.cr
- # etc
notify_client: true # sends a message through the channel
commands:
- shards build app # every command treated the same, and run in parallel
Would it clean up the watcher code and config?
loop do | ||
scan_files | ||
sleep 1 | ||
if watch_object = CLI.config.watch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is watch_config
a better name than watch_object
?
if watch_object = CLI.config.watch | ||
run_watcher(watch_object) | ||
else | ||
error "Can't find watch settings, please check your .amber.yml file" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can this error message be more helpful? Better yet, can this be a warning and then some sane default watch run anyway?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
log "Compile time errors detected. Shutting down..." | ||
exit 1 | ||
private def run_watcher(watch_object) | ||
watch_object.each do |key, value| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure it makes sense to iterate here. Why not just do something like:
server_config = watch_object["server"]
spawn watcher("server", server_config["files"], server_config["commands"]
debug "Watching #{file_counter} #{key} files" | ||
kill_processes(key) | ||
commands.each do |command| | ||
if key == "server" && command == commands.first? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
this logic isn't obvious to me, why is the first command different from the rest?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This means that other tasks are blocked until your amber app compiles,
Should we change that behaivior? 😅
I think your idea of running task on parallel would be nice 👍
@processes.each do |process| | ||
process.kill unless process.terminated? | ||
private def check_directories | ||
Dir.mkdir_p("bin") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why does this create bin/
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because bin/
dir is needed to store the generated binary
Useful when your tasks are like crystal build src/myapp.cr -o bin/myapp
, so you don't need to execute mkdir -p
first because amber watch
already does this, just like shards build
process = Helpers.run(command, error: error_io) | ||
PROCESSES << {process, "server"} | ||
loop do | ||
if process.terminated? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this loop contains far too much logic all in one place. It may help to refactor to use early-exits instead, but I don't know. It's hard to even see what's going on here, but something like this might be possible:
loop do
sleep 1
next if process.terminated?
next if error_io.empty?
# etc
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I added a new method handle_terminaded_process
:
Now the loop
looks like this:
loop do
if process.terminated?
handle_terminaded_process(process, error_io)
break
end
sleep 1
end
- "crystal build -o bin/<%= @name %> src/<%= @name %>.cr -p --no-color" | ||
- "bin/<%= @name %>" | ||
|
||
client: # optional: these files changes trigger browser reloading |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
# Optional: File changes triggers the browser to reload.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done! 👍
- "public/**/*" | ||
commands: [] | ||
|
||
webpack: # optional: compiles assets using webpack |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought the new Amber watch was going to decouple webpack meaning you can choose which ever front end builder you want? Or is this a simple task?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is just a simple task, Webpack removal is addressed here: #866
File.info(file).modification_time.to_s("%Y%m%d%H%M%S") | ||
private def handle_error(error_output) | ||
kill_processes("server") | ||
puts error_output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
User CLI::Logger
@@ -2,3 +2,27 @@ type: app | |||
database: <%= @database %> | |||
language: <%= @language %> | |||
model: <%= @model %> | |||
watch: | |||
server: # required: the first command for this task is blocking |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What does this mean required: the first command for this task is blockin
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That means that other tasks are blocked until your amber app compiles
Maybe we should change the message, WDYT? 😅
@robacarp Nice idea, let me try 👍 |
@robacarp I have a comment about your idea: watch:
meaningless_name:
files:
- src/**/*.cr
- # etc
notify_client: true # This is already done by "client" field
commands:
- shards build app # Should we run this on parallel? what about next command
- bin/app # This requires app build first, WDYT? |
I got it!, I think I can implement this by appending all fields ( |
commands:
- shards build app # Should we run this on parallel? what about next command
- bin/app # This requires app build first, WDYT? If we use parallel tasks, then I guess we should require something like this: commands:
- shards build app && bin/app WDYT? |
@faustinoaq I would say not to mess with the feature too much, and iterate i will open issues with additional thoughts so we can merge this |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left some comments. I feel that the ProcessRunner can bit split into smaller structs/classes its doing too much and know too much about Watcher.
@@ -55,7 +55,7 @@ module Amber::CLI | |||
when "create" | |||
Micrate.logger.info create_database | |||
when "seed" | |||
Helpers.run("crystal db/seeds.cr", wait: true, shell: true) | |||
Helpers.run("crystal db/seeds.cr", shell: true).wait |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Helpers is a module and the .wait
call makes it look like is an instance of an object and the method .run
is deceiving since its just creating an instance of Process. I like that this method has some helpful arguments defaults and in that case I recommend to use class CLIProcess < Process
and overwrite the run method with defaults and call super on it.
require "process"
class CLIProcess
CLI_IO = Process::Redirect::Inherit
def self.instance(command, shell = true, input = CLI_IO, output = CLI_IO, error = CLI_IO)
Process.new(command, shell: shell, input: input, output: output, error: error)
end
end
CLIProcess.instance("crystal db/seeds.cr", shell: true).wait
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks cleaner, Nice idea! 👍
"bin/#{app_name}" | ||
] | ||
} | ||
} | ||
|
||
def initialize |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can remove this initializer since is not doing anything
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remove the initialize then I can't use Config.new
because it complains about no matching parameters 😅
Let me add a comment about this 👍
error "Error in watch configuration. #{ex.message}" | ||
exit 1 | ||
end | ||
watch_config.each do |key, value| |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be its own method and be able to loop to all config keys including watch_config["server"]
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied @robacarp suggestion here, although, the new refactoring won't require this 😅
private def run_watcher(watch_config) | ||
begin | ||
server_config = watch_config["server"] | ||
spawn watcher("server", server_config["files"], server_config["commands"]) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is server is being spawn separately and not just loop over each key as you doing below? Does the order matter? if yes, this should be moved to its own method run_server_watcher
The watch_config
should be an instance variable!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I applied @robacarp suggestion here, although, the new refactoring won't require this 😅
elsif !@app_running | ||
log "Compile time errors detected. Shutting down..." | ||
exit 1 | ||
if watch_config = CLI.config.watch |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This should be moved to initializer as a guard clause. watch
def initialize
raise AMBER_YAML_ERROR unless CLI.config.amber_yml_exists?
@amber_yaml = CLI.config.amber_yaml
end
Actually the loading and check of this should be in Amber::CLI module and not here.
And have an instance of ProcessRunner in Amber::CLI
module Amber::CLI
def self.process_runner
ProcessRunner.new if amber_yml_loaded?
end
def amber_yaml_loaded?
raise AMBER_YAML_ERROR unless CLI.config.amber_yml_exists?
end
end
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nice suggestion, let me apply this refactoring as well 👍
private def build_app_process | ||
log "Building project #{project_name}..." | ||
Amber::CLI::Helpers.run(@build_command) | ||
private def handle_terminaded_process(process, error_io) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why not a TerminatedProcessHandler classs?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this useless now, I gonna use parallel processes (like @robacarp suggested), no channel nor concurrency management would be required 👍
end | ||
end | ||
|
||
private def error_server(error_output) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The body of this method should be moved to a ErrorServer class/module
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I agree, let me split this file 👍
|
||
private def handle_error(error_output) | ||
kill_processes("server") | ||
puts error_output |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
CLI::Logger?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I forgot this one 😅
@notify_counter = 0 | ||
@notify_counter_channel = Channel(Int32).new | ||
@notify_channel = Channel(Nil).new | ||
@host = Helpers.settings.host |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
How come we are getting settings information from a Helper module and not Amber::CLI environment settings?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, this is using include Enviroment
, I know this is a bad practice, because even I was confused about this when I tried to figure out where Helpers.settings
is defined, So, I gonna use Amber::CLI
👍
Amber::CLI::Helpers.run("npm install --loglevel=error && npm run watch", wait: false) | ||
node_log "Watching public directory" | ||
private def run_command(command, key) | ||
if key == "server" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is "server" special?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, I did such a complex stuff here, I'm already fixing this 👍
@robacarp Why was this closed? I'm confused as to the state of this issue. |
Why was this closed? Looks a little complex, but there's some good stuff in here. @faustinoaq Will there be another PR with some of this? Also will autoreload be added back soon? |
@elorest @drujensen This was closed for cleanup -- @faustinoaq did some great work, but doesn't have time these days to contribute as much. If someone wants to pick up where he left of, 👍 |
Description of the Change
This PR depends on #864 and #857
This PR adds new
amber watch
config using.amber.yml
fileAlternate Designs
Keep current
process_runner.cr
and force npm/webpack commands 😅Benefits
sentry.cr
(dead code)Possible Drawbacks
All tasks require
files
andcommands
fields, for one-time tasks (without files) you can use an empty arrayfiles: []
No cli flags, so, things like
amber watch -w "./config/**/*.cr"
are not supported /cc @damianhamSee: https://github.com/damianham/amber_render_module#usage