Something went wrong. Try again.
javascript for 🐜's, a tiny runtime with big ambitions antjs.org
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985#include "watch.h"#include "ascii.h"#include "messages.h"#include "utils.h"#include "esm/loader.h"#include "esm/remote.h"
#include <uv.h>#include <ctype.h>#include <stdio.h>#include <signal.h>#include <stdint.h>#include <stdlib.h>#include <string.h>#include <stdbool.h>#include <limits.h>#include <sys/stat.h>#include <time.h>#include <crprintf.h>
#ifdef _WIN32#include <direct.h>#endif
#ifndef SIGKILL#define SIGKILL SIGTERM#endif
#define WATCH_RESTART_DEBOUNCE_MS 75#define WATCH_CHILD_KILL_GRACE_MS 1000
typedef struct watch_state watch_state_t;
typedef struct watch_item { uv_fs_event_t event; watch_state_t *state; char *path; uint64_t size; time_t mtime_sec; long mtime_nsec; bool stat_valid; bool closing; struct watch_item *next;} watch_item_t;
typedef struct watch_path_list { char **items; size_t len; size_t cap;} watch_path_list_t;
struct watch_state { uv_loop_t loop; watch_item_t *watchers; uv_signal_t sigint; uv_signal_t sigterm; uv_timer_t debounce_timer; uv_timer_t kill_timer; uv_process_t *child; char **child_argv; char *watch_path; bool no_clear_screen; bool stop_requested; bool restart_pending; bool child_running; bool sigint_inited; bool sigterm_inited; bool debounce_timer_inited; bool debounce_timer_running; bool kill_timer_inited; bool kill_timer_running; int final_exit_code;};
static void watch_clear_screen(void) { fputs("\033[3J\033[2J\033[H", stdout); fflush(stdout);}
char *ant_watch_resolve_path(const char *path) {#ifdef _WIN32 char *resolved = _fullpath(NULL, path, 0); if (resolved) return resolved; return _strdup(path);#else char abs_path[PATH_MAX]; const char *resolved = realpath(path, abs_path); return strdup(resolved ? resolved : path);#endif}
int ant_watch_start( uv_loop_t *loop, uv_fs_event_t *event, const char *path, uv_fs_event_cb callback, void *data, unsigned int flags, char **resolved_path_out) { char *watch_path = NULL; int rc = 0;
if (!loop || !event || !path || !callback) return UV_EINVAL;
watch_path = ant_watch_resolve_path(path); if (!watch_path) return UV_ENOMEM;
rc = uv_fs_event_init(loop, event); if (rc != 0) goto cleanup;
event->data = data; rc = uv_fs_event_start(event, callback, watch_path, flags); if (rc != 0) goto cleanup;
if (resolved_path_out) *resolved_path_out = watch_path; else free(watch_path); return 0;
cleanup: free(watch_path); return rc;}
void ant_watch_stop(uv_fs_event_t *event) { if (!event) return; uv_fs_event_stop(event);}
static bool watch_arg_takes_value(const char *arg) { return strcmp(arg, "-e") == 0 || strcmp(arg, "--eval") == 0 || strcmp(arg, "--localstorage-file") == 0;}
static bool watch_is_control_arg(const char *arg) { return strcmp(arg, "-w") == 0 || strcmp(arg, "--watch") == 0 || strcmp(arg, "--no-clear-screen") == 0;}
static char **watch_build_child_argv(int argc, char **argv) { char **child_argv = calloc((size_t)argc + 1, sizeof(char *)); if (!child_argv) return NULL;
int out = 0; bool after_script_start = false;
for (int i = 0; i < argc; i++) { const char *arg = argv[i];
if (i == 0) { child_argv[out++] = argv[i]; continue; }
if (!after_script_start && strcmp(arg, "--") == 0) { after_script_start = true; child_argv[out++] = argv[i]; continue; }
if (!after_script_start && watch_is_control_arg(arg)) continue;
child_argv[out++] = argv[i];
if (after_script_start) continue;
if (arg[0] != '-') { after_script_start = true; continue; }
if (watch_arg_takes_value(arg) && i + 1 < argc) { child_argv[out++] = argv[++i]; } }
child_argv[out] = NULL; if (out == 0) { free(child_argv); return NULL; }
return child_argv;}
static void watch_path_list_free(watch_path_list_t *list) { if (!list) return; for (size_t i = 0; i < list->len; i++) free(list->items[i]); free(list->items); *list = (watch_path_list_t){0};}
static bool watch_path_list_contains(watch_path_list_t *list, const char *path) { if (!list || !path) return false; for (size_t i = 0; i < list->len; i++) { if (strcmp(list->items[i], path) == 0) return true; } return false;}
static int watch_path_list_push_owned(watch_path_list_t *list, char *path) { if (!list || !path) return UV_EINVAL; if (watch_path_list_contains(list, path)) { free(path); return 0; }
if (list->len == list->cap) { size_t next_cap = list->cap ? list->cap * 2 : 8; char **next = realloc(list->items, next_cap * sizeof(*next)); if (!next) { free(path); return UV_ENOMEM; } list->items = next; list->cap = next_cap; }
list->items[list->len++] = path; return 0;}
static bool watch_has_suffix(const char *path, const char *suffix) { size_t path_len = strlen(path); size_t suffix_len = strlen(suffix); return path_len >= suffix_len && strcmp(path + path_len - suffix_len, suffix) == 0;}
static bool watch_is_graph_source_file(const char *path) { if (!path) return false; if (watch_has_suffix(path, ".json") || watch_has_suffix(path, ".node")) return false;
for (const char *const *ext = module_resolve_extensions; *ext; ext++) { if (strcmp(*ext, ".json") == 0 || strcmp(*ext, ".node") == 0) continue; if (watch_has_suffix(path, *ext)) return true; } return false;}
static bool watch_is_local_specifier(const char *specifier) { if (!specifier || !specifier[0]) return false; return specifier[0] == '/' || strcmp(specifier, ".") == 0 || strcmp(specifier, "..") == 0 || strncmp(specifier, "./", 2) == 0 || strncmp(specifier, "../", 3) == 0 || strncmp(specifier, ".\\", 2) == 0 || strncmp(specifier, "..\\", 3) == 0;}
static char *watch_read_file(const char *path, size_t *out_len) { if (out_len) *out_len = 0; FILE *fp = fopen(path, "rb"); if (!fp) return NULL;
if (fseek(fp, 0, SEEK_END) != 0) { fclose(fp); return NULL; }
long size = ftell(fp); if (size < 0) { fclose(fp); return NULL; }
rewind(fp); char *buffer = malloc((size_t)size + 1); if (!buffer) { fclose(fp); return NULL; }
size_t read_len = fread(buffer, 1, (size_t)size, fp); fclose(fp); buffer[read_len] = '\0'; if (out_len) *out_len = read_len; return buffer;}
static bool watch_is_ident_char(unsigned char ch) { return ant_ascii_is_alnum(ch) || ch == '_' || ch == '$';}
static bool watch_word_equals(const char *src, size_t start, size_t end, const char *word) { size_t len = strlen(word); return end - start == len && memcmp(src + start, word, len) == 0;}
static void watch_skip_line_comment(const char *src, size_t len, size_t *i) { *i += 2; while (*i < len && src[*i] != '\n' && src[*i] != '\r') (*i)++;}
static void watch_skip_block_comment(const char *src, size_t len, size_t *i) { *i += 2; while (*i + 1 < len) { if (src[*i] == '*' && src[*i + 1] == '/') { *i += 2; return; } (*i)++; } *i = len;}
static void watch_skip_ws_comments(const char *src, size_t len, size_t *i) { while (*i < len) { unsigned char ch = (unsigned char)src[*i]; if (isspace(ch)) { (*i)++; continue; } if (*i + 1 < len && src[*i] == '/' && src[*i + 1] == '/') { watch_skip_line_comment(src, len, i); continue; } if (*i + 1 < len && src[*i] == '/' && src[*i + 1] == '*') { watch_skip_block_comment(src, len, i); continue; } break; }}
static char *watch_parse_string_literal(const char *src, size_t len, size_t *i) { if (*i >= len) return NULL;
char quote = src[*i]; if (quote != '\'' && quote != '"' && quote != '`') return NULL; (*i)++;
char *out = malloc(len - *i + 1); if (!out) return NULL;
size_t out_len = 0; while (*i < len) { char ch = src[*i]; if (ch == quote) { (*i)++; out[out_len] = '\0'; return out; }
if (ch == '\\' && *i + 1 < len) { (*i)++; out[out_len++] = src[*i]; (*i)++; continue; }
out[out_len++] = ch; (*i)++; }
free(out); return NULL;}
static void watch_skip_string_literal(const char *src, size_t len, size_t *i) { char *ignored = watch_parse_string_literal(src, len, i); free(ignored);}
typedef void (*watch_specifier_cb)(const char *specifier, bool prefer_require, void *data);
static void watch_emit_string_specifier( const char *src, size_t len, size_t *i, bool prefer_require, watch_specifier_cb cb, void *data) { char *specifier = watch_parse_string_literal(src, len, i); if (!specifier) return; cb(specifier, prefer_require, data); free(specifier);}
static void watch_scan_import_clause( const char *src, size_t len, size_t *i, watch_specifier_cb cb, void *data) { watch_skip_ws_comments(src, len, i); if (*i >= len) return;
if (src[*i] == '(') { (*i)++; watch_skip_ws_comments(src, len, i); watch_emit_string_specifier(src, len, i, false, cb, data); return; }
if (src[*i] == '\'' || src[*i] == '"' || src[*i] == '`') { watch_emit_string_specifier(src, len, i, false, cb, data); return; }
while (*i < len) { watch_skip_ws_comments(src, len, i); if (*i >= len || src[*i] == ';') return;
if (src[*i] == '\'' || src[*i] == '"' || src[*i] == '`') { watch_skip_string_literal(src, len, i); continue; }
if (watch_is_ident_char((unsigned char)src[*i])) { size_t start = *i; while (*i < len && watch_is_ident_char((unsigned char)src[*i])) (*i)++; if (watch_word_equals(src, start, *i, "from")) { size_t after_from = *i; watch_skip_ws_comments(src, len, i); if (*i < len && (src[*i] == '\'' || src[*i] == '"' || src[*i] == '`')) { watch_emit_string_specifier(src, len, i, false, cb, data); return; } *i = after_from; } continue; }
(*i)++; }}
static void watch_scan_export_clause( const char *src, size_t len, size_t *i, watch_specifier_cb cb, void *data) { while (*i < len) { watch_skip_ws_comments(src, len, i); if (*i >= len || src[*i] == ';') return;
if (src[*i] == '\'' || src[*i] == '"' || src[*i] == '`') { watch_skip_string_literal(src, len, i); continue; }
if (watch_is_ident_char((unsigned char)src[*i])) { size_t start = *i; while (*i < len && watch_is_ident_char((unsigned char)src[*i])) (*i)++; if (watch_word_equals(src, start, *i, "from")) { size_t after_from = *i; watch_skip_ws_comments(src, len, i); if (*i < len && (src[*i] == '\'' || src[*i] == '"' || src[*i] == '`')) { watch_emit_string_specifier(src, len, i, false, cb, data); return; } *i = after_from; } continue; }
(*i)++; }}
static void watch_scan_require_call( const char *src, size_t len, size_t *i, watch_specifier_cb cb, void *data) { watch_skip_ws_comments(src, len, i); if (*i >= len || src[*i] != '(') return;
(*i)++; watch_skip_ws_comments(src, len, i); watch_emit_string_specifier(src, len, i, true, cb, data);}
typedef struct watch_graph_context { watch_path_list_t *paths; const char *base_path;} watch_graph_context_t;
static void watch_graph_add_specifier(const char *specifier, bool prefer_require, void *data) { watch_graph_context_t *ctx = (watch_graph_context_t *)data; if (!watch_is_local_specifier(specifier)) return;
char *resolved = js_esm_resolve_path_for_watch(NULL, specifier, ctx->base_path, prefer_require); if (!resolved) return;
if (esm_is_url(resolved)) { free(resolved); return; }
struct stat st; if (stat(resolved, &st) != 0 || !S_ISREG(st.st_mode)) { free(resolved); return; }
watch_path_list_push_owned(ctx->paths, resolved);}
static void watch_scan_module_file( const char *path, watch_path_list_t *paths) { if (!watch_is_graph_source_file(path)) return;
size_t len = 0; char *src = watch_read_file(path, &len); if (!src) return;
watch_graph_context_t ctx = { paths, path };
for (size_t i = 0; i < len;) { if (src[i] == '\'' || src[i] == '"' || src[i] == '`') { watch_skip_string_literal(src, len, &i); continue; }
if (i + 1 < len && src[i] == '/' && src[i + 1] == '/') { watch_skip_line_comment(src, len, &i); continue; }
if (i + 1 < len && src[i] == '/' && src[i + 1] == '*') { watch_skip_block_comment(src, len, &i); continue; }
if (!watch_is_ident_char((unsigned char)src[i])) { i++; continue; }
size_t start = i; while (i < len && watch_is_ident_char((unsigned char)src[i])) i++;
if (watch_word_equals(src, start, i, "import")) { watch_scan_import_clause(src, len, &i, watch_graph_add_specifier, &ctx); } else if (watch_word_equals(src, start, i, "export")) { watch_scan_export_clause(src, len, &i, watch_graph_add_specifier, &ctx); } else if (watch_word_equals(src, start, i, "require")) { watch_scan_require_call(src, len, &i, watch_graph_add_specifier, &ctx); } }
free(src);}
static int watch_build_graph(const char *entry_file, watch_path_list_t *paths) { char *entry_copy = strdup(entry_file); if (!entry_copy) return UV_ENOMEM;
int rc = watch_path_list_push_owned(paths, entry_copy); if (rc != 0) return rc;
for (size_t i = 0; i < paths->len; i++) { watch_scan_module_file(paths->items[i], paths); }
return 0;}
static void watch_on_watcher_closed(uv_handle_t *handle) { watch_item_t *item = (watch_item_t *)handle->data; if (!item) return; free(item->path); free(item);}
static long watch_stat_mtime_nsec(const struct stat *st) {#ifdef _WIN32 (void)st; return 0;#elif defined(__APPLE__) return st->st_mtimespec.tv_nsec;#else return st->st_mtim.tv_nsec;#endif}
static bool watch_item_refresh_stat(watch_item_t *item) { if (!item || !item->path) return true;
struct stat st; if (stat(item->path, &st) != 0) { bool changed = item->stat_valid; item->stat_valid = false; item->size = 0; item->mtime_sec = 0; item->mtime_nsec = 0; return changed; }
uint64_t size = (uint64_t)st.st_size; time_t mtime_sec = st.st_mtime; long mtime_nsec = watch_stat_mtime_nsec(&st); bool changed = !item->stat_valid || item->size != size || item->mtime_sec != mtime_sec || item->mtime_nsec != mtime_nsec;
item->stat_valid = true; item->size = size; item->mtime_sec = mtime_sec; item->mtime_nsec = mtime_nsec; return changed;}
static void watch_request_shutdown(watch_state_t *state, int exit_code);static void watch_try_spawn_child(watch_state_t *state);static void watch_request_restart_now(watch_state_t *state);static void watch_on_debounce_timer(uv_timer_t *timer);
static void watch_on_fs_event(uv_fs_event_t *handle, const char *filename, int events, int status) { (void)filename; watch_item_t *item = (watch_item_t *)handle->data; watch_state_t *state = item ? item->state : NULL; if (!state) return; if (!item || item->closing) return;
if (status < 0) { crfprintf(stderr, msg.watch_warn_normal, item && item->path ? item->path : state->watch_path, uv_strerror(status)); return; }
if ((events & (UV_CHANGE | UV_RENAME)) == 0) return; if (!watch_item_refresh_stat(item)) return; if (state->restart_pending) return;
if (!state->debounce_timer_inited) { watch_request_restart_now(state); return; }
if (state->debounce_timer_running) uv_timer_stop(&state->debounce_timer);
int rc = uv_timer_start( &state->debounce_timer, watch_on_debounce_timer, WATCH_RESTART_DEBOUNCE_MS, 0 );
if (rc == 0) { state->debounce_timer_running = true; } else { watch_request_restart_now(state); }}
static int watch_add_watcher(watch_state_t *state, const char *path) { watch_item_t *item = calloc(1, sizeof(*item)); if (!item) return UV_ENOMEM;
item->state = state; int rc = ant_watch_start( &state->loop, &item->event, path, watch_on_fs_event, item, 0, &item->path );
if (rc != 0) { free(item); return rc; }
watch_item_refresh_stat(item); item->next = state->watchers; state->watchers = item; return 0;}
static void watch_close_watchers(watch_state_t *state) { watch_item_t *item = state->watchers; state->watchers = NULL;
while (item) { watch_item_t *next = item->next; item->closing = true; uv_fs_event_stop(&item->event); if (!uv_is_closing((uv_handle_t *)&item->event)) { uv_close((uv_handle_t *)&item->event, watch_on_watcher_closed); } item = next; }}
static int watch_refresh_watchers(watch_state_t *state) { watch_path_list_t paths = {0}; int rc = watch_build_graph(state->watch_path, &paths); if (rc != 0) { watch_path_list_free(&paths); return rc; }
watch_close_watchers(state);
for (size_t i = 0; i < paths.len; i++) { rc = watch_add_watcher(state, paths.items[i]); if (rc != 0) { if (i == 0) { crfprintf(stderr, msg.watch_file_failed, paths.items[i], uv_strerror(rc)); watch_path_list_free(&paths); return rc; } crfprintf(stderr, msg.watch_file_warn, paths.items[i], uv_strerror(rc)); } }
watch_path_list_free(&paths); return 0;}
static void watch_stop_kill_timer(watch_state_t *state) { if (!state->kill_timer_inited || !state->kill_timer_running) return; uv_timer_stop(&state->kill_timer); state->kill_timer_running = false;}
static void watch_stop_debounce_timer(watch_state_t *state) { if (!state->debounce_timer_inited || !state->debounce_timer_running) return; uv_timer_stop(&state->debounce_timer); state->debounce_timer_running = false;}
static void watch_on_child_closed(uv_handle_t *handle) { free(handle); }
static void watch_on_kill_timer(uv_timer_t *timer) { watch_state_t *state = (watch_state_t *)timer->data; state->kill_timer_running = false;
if (state->child_running && state->child) uv_process_kill(state->child, SIGKILL);}
static void watch_on_child_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { watch_state_t *state = (watch_state_t *)proc->data; state->child_running = false; state->child = NULL; watch_stop_kill_timer(state);
if (state->stop_requested) { if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) uv_close((uv_handle_t *)&state->kill_timer, NULL); } else if (state->restart_pending) { watch_try_spawn_child(state); } else if (term_signal > 0) { state->final_exit_code = 128 + term_signal; } else { state->final_exit_code = (int)exit_status; }
uv_close((uv_handle_t *)proc, watch_on_child_closed);}
static int watch_spawn_child(watch_state_t *state) { uv_process_t *proc = calloc(1, sizeof(*proc)); if (!proc) return UV_ENOMEM;
uv_stdio_container_t stdio[3]; stdio[0].flags = UV_INHERIT_FD; stdio[0].data.fd = 0; stdio[1].flags = UV_INHERIT_FD; stdio[1].data.fd = 1; stdio[2].flags = UV_INHERIT_FD; stdio[2].data.fd = 2;
uv_process_options_t options = {0}; options.file = state->child_argv[0]; options.args = state->child_argv; options.exit_cb = watch_on_child_exit; options.stdio_count = 3; options.stdio = stdio;
int rc = uv_spawn(&state->loop, proc, &options); if (rc != 0) { free(proc); return rc; }
proc->data = state; state->child = proc; state->child_running = true; state->restart_pending = false; return 0;}
static void watch_try_spawn_child(watch_state_t *state) { if (state->stop_requested) return;
bool restarting = state->restart_pending; if (!state->no_clear_screen && restarting) watch_clear_screen();
int rc = watch_refresh_watchers(state); if (rc != 0) { watch_request_shutdown(state, EXIT_FAILURE); return; }
rc = watch_spawn_child(state); if (rc != 0) { crfprintf(stderr, msg.watch_spawn_failed, uv_strerror(rc)); watch_request_shutdown(state, EXIT_FAILURE); }}
static void watch_start_kill_timer(watch_state_t *state, uint64_t timeout_ms) { if (!state->kill_timer_inited || state->kill_timer_running) return; int rc = uv_timer_start(&state->kill_timer, watch_on_kill_timer, timeout_ms, 0); if (rc == 0) state->kill_timer_running = true;}
static void watch_close_signals_timers_and_watchers(watch_state_t *state) { watch_close_watchers(state); watch_stop_debounce_timer(state);
if (state->sigint_inited && !uv_is_closing((uv_handle_t *)&state->sigint)) { uv_signal_stop(&state->sigint); uv_close((uv_handle_t *)&state->sigint, NULL); }
if (state->sigterm_inited && !uv_is_closing((uv_handle_t *)&state->sigterm)) { uv_signal_stop(&state->sigterm); uv_close((uv_handle_t *)&state->sigterm, NULL); }
if (state->debounce_timer_inited && !uv_is_closing((uv_handle_t *)&state->debounce_timer)) { uv_close((uv_handle_t *)&state->debounce_timer, NULL); }}
static void watch_request_shutdown(watch_state_t *state, int exit_code) { if (state->stop_requested) return;
state->stop_requested = true; state->restart_pending = false; state->final_exit_code = exit_code;
watch_close_signals_timers_and_watchers(state);
if (state->child_running && state->child) { int rc = uv_process_kill(state->child, SIGTERM); if (rc != 0 && rc != UV_ESRCH) { crfprintf(stderr, msg.watch_graceful_term, uv_strerror(rc)); } watch_start_kill_timer(state, WATCH_CHILD_KILL_GRACE_MS); } else if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) { uv_close((uv_handle_t *)&state->kill_timer, NULL); }}
static void watch_request_restart_now(watch_state_t *state) { if (state->debounce_timer_inited) state->debounce_timer_running = false; if (state->stop_requested) return;
state->restart_pending = true;
if (state->child_running && state->child) { if (!state->kill_timer_running) { int rc = uv_process_kill(state->child, SIGTERM); if (rc != 0 && rc != UV_ESRCH) { crfprintf(stderr, msg.watch_child_error, uv_strerror(rc)); watch_request_shutdown(state, EXIT_FAILURE); return; } watch_start_kill_timer(state, WATCH_CHILD_KILL_GRACE_MS); } } else watch_try_spawn_child(state);}
static void watch_on_debounce_timer(uv_timer_t *timer) { watch_state_t *state = (watch_state_t *)timer->data; state->debounce_timer_running = false; watch_request_restart_now(state);}
static void watch_on_signal(uv_signal_t *handle, int signum) { watch_state_t *state = (watch_state_t *)handle->data; watch_request_shutdown(state, 128 + signum);}
int ant_watch_run(int argc, char **argv, const char *entry_file, bool no_clear_screen) { if (!entry_file || !*entry_file) { crfprintf(stderr, msg.watch_entrypoint_missing); return EXIT_FAILURE; }
watch_state_t state = {0}; state.child_argv = watch_build_child_argv(argc, argv); state.watch_path = ant_watch_resolve_path(entry_file); state.no_clear_screen = no_clear_screen; state.final_exit_code = EXIT_SUCCESS;
if (!state.child_argv || !state.watch_path) { free(state.child_argv); free(state.watch_path); crfprintf(stderr, msg.watch_start_fatal); return EXIT_FAILURE; }
int rc = uv_loop_init(&state.loop); if (rc != 0) { crfprintf(stderr, msg.watch_loop_fatal, uv_strerror(rc)); free(state.child_argv); free(state.watch_path); return EXIT_FAILURE; }
rc = uv_timer_init(&state.loop, &state.debounce_timer); if (rc == 0) { state.debounce_timer_inited = true; state.debounce_timer.data = &state; }
if (rc == 0) { rc = uv_timer_init(&state.loop, &state.kill_timer); if (rc == 0) { state.kill_timer_inited = true; state.kill_timer.data = &state; } }
if (rc == 0) { rc = watch_refresh_watchers(&state); }
if (rc == 0) { rc = uv_signal_init(&state.loop, &state.sigint); if (rc == 0) state.sigint_inited = true; if (rc == 0) { state.sigint.data = &state; rc = uv_signal_start(&state.sigint, watch_on_signal, SIGINT); } }
if (rc == 0) { rc = uv_signal_init(&state.loop, &state.sigterm); if (rc == 0) state.sigterm_inited = true; if (rc == 0) { state.sigterm.data = &state; int sigterm_rc = uv_signal_start(&state.sigterm, watch_on_signal, SIGTERM); if (sigterm_rc != 0 && sigterm_rc != UV_ENOSYS && sigterm_rc != UV_EINVAL) { rc = sigterm_rc; } else if (sigterm_rc != 0) { uv_signal_stop(&state.sigterm); uv_close((uv_handle_t *)&state.sigterm, NULL); state.sigterm_inited = false; } } }
if (rc != 0) { crfprintf(stderr, msg.watch_loop_handles_fatal, uv_strerror(rc)); watch_request_shutdown(&state, EXIT_FAILURE); }
if (!state.stop_requested) watch_spawn_child(&state); while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
watch_close_signals_timers_and_watchers(&state); if (state.kill_timer_inited && !uv_is_closing((uv_handle_t *)&state.kill_timer)) { uv_close((uv_handle_t *)&state.kill_timer, NULL); } while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {}
rc = uv_loop_close(&state.loop); if (rc != 0) crfprintf(stderr, msg.watch_loop_cleanup, uv_strerror(rc));
free(state.child_argv); free(state.watch_path); return state.final_exit_code;}