New amber watch config#865
Conversation
|
This PR is based on |
| - "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.
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.
@robacarp You're right, actually this field can be optional, this implementation works without a client field
…er into fa/new-exception-page
|
This new config works pretty nice and fix many issues, although, Does someone have any idea how to add specs for this? 😅 |
robacarp
left a comment
There was a problem hiding this comment.
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.
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.
Can this error message be more helpful? Better yet, can this be a warning and then some sane default watch run anyway?
| 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.
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.
this logic isn't obvious to me, why is the first command different from the rest?
There was a problem hiding this comment.
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.
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.
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
endThere was a problem hiding this comment.
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.
# Optional: File changes triggers the browser to reload.
| - "public/**/*" | ||
| commands: [] | ||
|
|
||
| webpack: # optional: compiles assets using webpack |
There was a problem hiding this comment.
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.
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 |
| language: <%= @language %> | ||
| model: <%= @model %> | ||
| watch: | ||
| server: # required: the first command for this task is blocking |
There was a problem hiding this comment.
What does this mean required: the first command for this task is blockin?
There was a problem hiding this comment.
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/appWDYT? |
|
@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 |
eliasjpr
left a comment
There was a problem hiding this comment.
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.
| 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.
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
endCLIProcess.instance("crystal db/seeds.cr", shell: true).waitThere was a problem hiding this comment.
Looks cleaner, Nice idea! 👍
| } | ||
| } | ||
|
|
||
| def initialize |
There was a problem hiding this comment.
You can remove this initializer since is not doing anything
There was a problem hiding this comment.
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.
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.
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.
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.
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.
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
endActually 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.
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.
Why not a TerminatedProcessHandler classs?
There was a problem hiding this comment.
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.
The body of this method should be moved to a ErrorServer class/module
There was a problem hiding this comment.
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.
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.
How come we are getting settings information from a Helper module and not Amber::CLI environment settings?
There was a problem hiding this comment.
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.
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 watchconfig using.amber.ymlfileAlternate Designs
Keep current
process_runner.crand force npm/webpack commands 😅Benefits
sentry.cr(dead code)Possible Drawbacks
All tasks require
filesandcommandsfields, 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