MIRROR: javascript for ๐Ÿœ's, a tiny runtime with big ambitions
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

add watch mode functionality and improve CLI error handling

+608 -103
+1 -1
include/ant.h
··· 190 190 jsoff_t js_next_prop(jsoff_t header); 191 191 jsoff_t js_loadoff(ant_t *js, jsoff_t off); 192 192 193 - #endif 193 + #endif
+3
include/cli/misc.h
··· 1 1 #ifndef MISC_H 2 2 #define MISC_H 3 + 3 4 #include <stdio.h> 5 + struct arg_end; 4 6 5 7 typedef struct { 6 8 const char *s; ··· 12 14 13 15 void print_flags_help(FILE *fp, void **argtable); 14 16 void print_flag(FILE *fp, flag_help_t f); 17 + void print_errors(FILE *fp, struct arg_end *end); 15 18 16 19 #endif
+2 -2
include/utils.h
··· 1 1 #ifndef ANT_UTILS_H 2 2 #define ANT_UTILS_H 3 - #define ARGTABLE_COUNT 8 3 + #define ARGTABLE_COUNT 10 4 4 5 5 #include <stdlib.h> 6 6 #include <stdint.h> ··· 36 36 #define CSTR_INIT(buf, src, len) \ 37 37 cstr_init(&(buf), buf##_stack, sizeof(buf##_stack), (src), (len)) 38 38 39 - #endif 39 + #endif
+11
include/watch.h
··· 1 + #ifndef ANT_WATCH_H 2 + #define ANT_WATCH_H 3 + 4 + #include <stdbool.h> 5 + 6 + int ant_watch_run( 7 + int argc, char **argv, 8 + const char *entry_file, bool no_clear_screen 9 + ); 10 + 11 + #endif
+10 -4
meson.build
··· 64 64 dependencies: ant_deps + [oxc_dep, pkg_dep] 65 65 ) 66 66 67 + messages_h = custom_target('messages_h', 68 + input: 'src/core/messages.toml', 69 + output: 'messages.h', 70 + command: [python, files('meson/messages.py'), '@INPUT@'], 71 + capture: true, 72 + ) 73 + 67 74 link_args = [] 68 75 if get_option('static_link') 69 76 link_args += ['-static'] ··· 71 78 72 79 executable( 73 80 'ant', 74 - files('src/main.c'), 75 - dependencies: libant_dep, 76 - link_args: link_args 77 - ) 81 + files('src/main.c', 'src/watch.c') + [messages_h], 82 + dependencies: libant_dep, link_args: link_args 83 + )
+20
meson/messages.py
··· 1 + import tomllib, sys 2 + 3 + with open(sys.argv[1], "rb") as f: 4 + messages = tomllib.load(f)["messages"] 5 + 6 + print("#ifndef MESSAGES_H") 7 + print("#define MESSAGES_H") 8 + print("") 9 + print("typedef struct {") 10 + for name in messages: 11 + print(f" const char *{name};") 12 + print("} ant_messages_t;") 13 + print("") 14 + print("static const ant_messages_t msg = {") 15 + for name, value in messages.items(): 16 + escaped = value.encode("unicode_escape").decode().replace('"', '\\"') 17 + print(f' .{name} = "{escaped}",') 18 + print("};") 19 + print("") 20 + print("#endif")
+2 -2
sources.json
··· 1 1 { 2 2 "engine": { 3 3 "patterns": ["src/*.c", "src/esm/*.c", "src/cli/*.c", "src/modules/*.c"], 4 - "exclude": ["src/main.c"] 4 + "exclude": ["src/main.c", "src/watch.c"] 5 5 }, 6 6 "library": { 7 7 "patterns": ["src/*.c", "src/esm/*.c", "src/modules/*.c"], 8 - "exclude": ["src/main.c"] 8 + "exclude": ["src/main.c", "src/watch.c"] 9 9 }, 10 10 "core": { 11 11 "patterns": ["src/core/**/*.ts"],
+20 -8
src/cli/misc.c
··· 1 1 #include "cli/misc.h" 2 + #include "messages.h" 2 3 3 4 #include <stdio.h> 4 5 #include <argtable3.h> ··· 26 27 void print_flags_help(FILE *fp, void **argtable) { 27 28 struct arg_hdr **table = (struct arg_hdr **)argtable; 28 29 for (int i = 0; !(table[i]->flag & ARG_TERMINATOR); i++) { 29 - struct arg_hdr *hdr = table[i]; 30 - if (!hdr->glossary) continue; 31 - print_flag(fp, (flag_help_t){ 32 - .s = hdr->shortopts, .l = hdr->longopts, 33 - .d = hdr->datatype, .g = hdr->glossary, 34 - .opt = hdr->flag & ARG_HASOPTVALUE, 35 - }); 36 - } 30 + struct arg_hdr *hdr = table[i]; 31 + if (!hdr->glossary) continue; 32 + print_flag(fp, (flag_help_t){ 33 + .s = hdr->shortopts, .l = hdr->longopts, 34 + .d = hdr->datatype, .g = hdr->glossary, 35 + .opt = hdr->flag & ARG_HASOPTVALUE, 36 + });} 37 + } 38 + 39 + void print_errors(FILE *fp, struct arg_end *end) { 40 + for (int i = 0; i < end->count; i++) { 41 + int error = end->error[i]; 42 + const char *argval = end->argval[i] ? end->argval[i] : ""; 43 + switch (error) { 44 + case ARG_ENOMATCH: { crfprintf(fp, msg.arg_unexpected, argval); break; } 45 + case ARG_EMISSARG: { crfprintf(fp, msg.arg_opt_needed, argval); break; } 46 + case ARG_ELONGOPT: { crfprintf(fp, msg.arg_invalid, argval); break; } 47 + default: { if (error > 0) crfprintf(fp, msg.opt_invalid, error); break; } 48 + }} fputc('\n', fp); 37 49 }
+44
src/core/messages.toml
··· 1 + [messages] 2 + ant_help_flags = "<bold>Flags:</>\n" 3 + ant_command_extra = "\n <pad=18><command> <bold+cyan>--help</></pad> Print help text for command.\n" 4 + 5 + snapshot_warn = "{warn}: <bold>Failed to load snapshot:</> %s\n" 6 + unknown_flag_warn = "{warn}: <bold>Unknown ANT_DEBUG flag: \"%s\"</>\n" 7 + 8 + failed_to_fetch = "{error}: <bold>Could not fetch \"%s\"</>: %s\n" 9 + module_not_found = "{error}: <bold>Module not found: \"%s\"</>\n" 10 + type_strip_failed = "{error}: <bold>Type stripping failed (%d)</>\n" 11 + watch_entrypoint_error = "{error}: <bold>--watch requires a local file entrypoint.</>\n" 12 + watch_module_error = "{error}: <bold>--watch only supports module file execution.</>\n" 13 + watch_subcommand_error = "{error}: <bold>--watch is only supported when executing a module file.</>\n" 14 + misuse_clear_screen = "{error}: <bold>--no-clear-screen can only be used with --watch.</>\n" 15 + 16 + ant_allocation_fatal = "{fatal}: Failed to allocate for Ant.</>\n" 17 + oom_fatal = "<bold+red>FATAL</bold>: Out of memory\n" 18 + argument_fatal = "{fatal}: <bold>Failed to resolve positional argument index.</>\n" 19 + 20 + arg_unexpected = "{error}: <bold>Unexpected argument <cyan>%s</>\n" 21 + arg_opt_needed = "{error}: <bold>Option <cyan>%s</> requires an argument\n" 22 + arg_invalid = "{error}: <bold>Invalid option <cyan>%s</>\n" 23 + opt_invalid = "{error}: <bold>Invalid option <cyan>-%c</>\n" 24 + 25 + ant_help_header = """\ 26 + {let h=bold, arg=cyan, name='Ant'}\ 27 + <$h+red>{name}</> is a tiny JavaScript runtime and package manager ({version})<br=2/>\ 28 + <$h>Usage: {~name} <yellow>[module.js]</yellow> <$arg>[...flags]<reset/><br/>\ 29 + <$h><gap=7/>{~name} <<command>><gap=3/><$arg>[...args]<reset/><br=2/>\ 30 + If no module file is specified, {name} starts in REPL mode.<br=2/>\ 31 + """ 32 + 33 + watch_spawn_failed = "{error}: <bold>Failed to spawn watched process:</> %s\n" 34 + watch_entrypoint_missing = "{error}: <bold>--watch requires a local file entrypoint.</>\n" 35 + watch_file_failed = "{error}: <bold>Failed to watch %s:</> %s\n" 36 + watch_child_error = "{error}: <bold>Failed to terminate child process:</> %s\n" 37 + 38 + watch_warn_normal = "{warn}: Watcher error on %s: %s\n" 39 + watch_loop_cleanup = "{warn}: Watch loop cleanup incomplete: %s\n" 40 + watch_graceful_term = "{warn}: Failed to terminate child process gracefully: %s\n" 41 + 42 + watch_start_fatal = "{fatal}: Failed to initialize watch mode.\n" 43 + watch_loop_fatal = "{fatal}: Failed to initialize watch loop: %s\n" 44 + watch_loop_handles_fatal = "{fatal}: Failed to initialize watch handles: %s\n"
+148 -82
src/main.c
··· 13 13 #include "ant.h" 14 14 #include "repl.h" 15 15 #include "utils.h" 16 + #include "watch.h" 16 17 #include "reactor.h" 17 18 #include "runtime.h" 18 19 #include "snapshot.h" 19 20 #include "esm/remote.h" 20 21 #include "internal.h" 22 + #include "messages.h" 21 23 22 24 #include "cli/pkg.h" 23 25 #include "cli/misc.h" ··· 87 89 if (strcmp(mode, "hex") == 0 || strcmp(mode, "all") == 0) crprintf_set_debug_hex(true); 88 90 } 89 91 90 - else crfprintf(stderr, "{warn}: <bold>Unknown ANT_DEBUG flag: \"%s\"</>\n", flag); 92 + else crfprintf(stderr, msg.unknown_flag_warn, flag); 91 93 } 92 94 93 95 static const subcommand_t *find_subcommand(const char *name) { ··· 103 105 for (const subcommand_t *cmd = subcommands; cmd->name; cmd++) { 104 106 crprintf(" <pad=18>%s</pad> %s\n", cmd->name, cmd->desc); 105 107 } 106 - crprintf("\n <pad=18><command> <bold+cyan>--help</></pad> Print help text for command.\n"); 108 + crprintf(msg.ant_command_extra); 107 109 printf("\n"); 108 110 } 109 111 112 + static void print_commands(void **argtable) { 113 + crprintf(msg.ant_help_header); 114 + 115 + print_subcommands(); 116 + crprintf(msg.ant_help_flags); 117 + 118 + print_flags_help(stdout, argtable); 119 + print_flag(stdout, (flag_help_t){ .l = "verbose", .g = "enable verbose output" }); 120 + print_flag(stdout, (flag_help_t){ .l = "no-color", .g = "disable colored output" }); 121 + } 122 + 123 + static int find_argv_token_index(int argc, char **argv, const char *token) { 124 + if (!token) return -1; 125 + for (int i = 1; i < argc; i++) if (argv[i] == token) return i; 126 + return -1; 127 + } 128 + 110 129 static char *read_stdin(size_t *len) { 111 130 size_t cap = 4096; 112 131 *len = 0; ··· 187 206 buffer = esm_fetch_url(filename, &len, &error); 188 207 189 208 if (!buffer) { 190 - crfprintf(stderr, "{error}: <bold>Could not fetch \"%s\"</>: %s\n", filename, error ? error : "unknown error"); 209 + crfprintf(stderr, msg.failed_to_fetch, filename, error ? error : "unknown error"); 191 210 free(error); return EXIT_FAILURE; 192 211 } 193 212 ··· 195 214 } else { 196 215 buffer = read_file(filename, &len); 197 216 if (!buffer) { 198 - crfprintf(stderr, "{error}: <bold>Module not found: \"%s\"</>\n", filename); 217 + crfprintf(stderr, msg.module_not_found, filename); 199 218 return EXIT_FAILURE; 200 219 } 201 220 ··· 213 232 if (is_typescript_file(filename)) { 214 233 int result = OXC_strip_types(buffer, filename, buffer, len + 1); 215 234 if (result < 0) { 216 - crfprintf(stderr, "{error}: <bold>Type stripping failed (%d)</>\n", result); 235 + crfprintf(stderr, msg.type_strip_failed, result); 217 236 free(buffer); 218 237 return EXIT_FAILURE; 219 238 } ··· 238 257 } 239 258 240 259 int main(int argc, char *argv[]) { 241 - int filtered_argc = 0; 260 + int filtered_argc = 0; int original_argc = argc; 261 + char **original_argv = argv; 242 262 243 263 const char *binary_name = strrchr(argv[0], '/'); 244 264 binary_name = binary_name ? binary_name + 1 : argv[0]; ··· 266 286 else filtered_argv[filtered_argc++] = argv[i]; 267 287 } 268 288 269 - if (filtered_argc >= 2 && filtered_argv[1][0] != '-') { 270 - const subcommand_t *cmd = find_subcommand(filtered_argv[1]); 271 - if (cmd) { 272 - int exitcode = cmd->fn(filtered_argc - 1, filtered_argv + 1); 273 - free(filtered_argv); 274 - return exitcode; 275 - } 276 - 277 - if (pkg_script_exists("package.json", filtered_argv[1])) { 278 - int exitcode = pkg_cmd_run(filtered_argc, filtered_argv); 279 - free(filtered_argv); 280 - return exitcode; 281 - } 282 - } 283 - 284 289 argc = filtered_argc; 285 290 argv = filtered_argv; 286 291 287 - struct arg_str *eval = arg_str0("e", "eval", "<script>", "evaluate script"); 288 - struct arg_lit *print = arg_lit0("p", "print", "evaluate script and print result"); 292 + #define ARG_ITEMS(X) \ 293 + X(struct arg_str *, eval, arg_str0("e", "eval", "<script>", "evaluate script")) \ 294 + X(struct arg_lit *, print, arg_lit0("p", "print", "evaluate script and print result")) \ 295 + X(struct arg_lit *, watch, arg_lit0("w", "watch", "restart process when entry file changes")) \ 296 + X(struct arg_lit *, no_clear_screen, arg_lit0(NULL, "no-clear-screen", "keep output when restarting in watch mode")) \ 297 + X(struct arg_file *, localstorage_file, arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence")) \ 298 + X(struct arg_file *, file, arg_filen(NULL, NULL, NULL, 0, argc, NULL)) \ 299 + X(struct arg_lit *, version, arg_lit0("v", "version", "display version information and exit")) \ 300 + X(struct arg_lit *, version_raw, arg_lit0(NULL, "version-raw", "raw version number for scripts")) \ 301 + X(struct arg_lit *, help, arg_lit0("h", "help", "display this help and exit")) \ 302 + X(struct arg_end *, end, arg_end(20)) 289 303 290 - struct arg_file *file = arg_filen(NULL, NULL, NULL, 0, argc, NULL); 291 - struct arg_file *localstorage_file = arg_file0(NULL, "localstorage-file", "<path>", "file path for localStorage persistence"); 304 + #define DECL(t, n, init) t n = init; 305 + ARG_ITEMS(DECL) 306 + #undef DECL 292 307 293 - struct arg_lit *version = arg_lit0("v", "version", "display version information and exit"); 294 - struct arg_lit *version_raw = arg_lit0(NULL, "version-raw", "raw version number for scripts"); 308 + #define REF(t, n, init) n, 309 + void *argtable[] = { ARG_ITEMS(REF) }; 310 + int nerrors = arg_parse(argc, argv, argtable); 311 + #undef REF 295 312 296 - struct arg_lit *help = arg_lit0("h", "help", "display this help and exit"); 297 - struct arg_end *end = arg_end(20); 298 - 299 - void *argtable[] = { 300 - eval, print, 301 - localstorage_file, file, 302 - version, version_raw, 303 - help, end 304 - }; 305 - 306 - int nerrors = arg_parse(argc, argv, argtable); 313 + #define CLEANUP_ARGS_AND_ARGV() ({ \ 314 + arg_freetable(argtable, ARGTABLE_COUNT); \ 315 + free(filtered_argv); \ 316 + }) 307 317 308 318 if (help->count > 0) { 309 - crprintf( 310 - "{let h=bold, arg=cyan, name='Ant'}" 311 - "<$h+red>{name}</> is a tiny JavaScript runtime and package manager ({version})<br=2/>" 312 - "<$h>Usage: {~name} <yellow>[module.js]</yellow> <$arg>[...flags]<reset/><br/>" 313 - "<$h><gap=7/>{~name} <<command>><gap=3/><$arg>[...args]<reset/><br=2/>" 314 - "If no module file is specified, {name} starts in REPL mode.<br=2/>" 315 - ); 316 - 317 - print_subcommands(); 318 - crprintf("<bold>Flags:</>\n"); 319 - 320 - print_flags_help(stdout, argtable); 321 - print_flag(stdout, (flag_help_t){ .l = "verbose", .g = "enable verbose output" }); 322 - print_flag(stdout, (flag_help_t){ .l = "no-color", .g = "disable colored output" }); 323 - 324 - arg_freetable(argtable, ARGTABLE_COUNT); 325 - free(filtered_argv); 326 - 319 + print_commands(argtable); 320 + CLEANUP_ARGS_AND_ARGV(); 327 321 return EXIT_SUCCESS; 328 322 } 329 323 330 324 if (version_raw->count > 0) { 331 325 fputs(ANT_VERSION "\n", stdout); 332 - arg_freetable(argtable, ARGTABLE_COUNT); 333 - free(filtered_argv); return EXIT_SUCCESS; 326 + CLEANUP_ARGS_AND_ARGV(); 327 + return EXIT_SUCCESS; 334 328 } 335 329 336 330 if (version->count > 0) { ··· 338 332 free(filtered_argv); return res; 339 333 } 340 334 335 + 341 336 if (nerrors > 0) { 342 - arg_print_errors(stdout, end, "ant"); 343 - printf("Try 'ant --help' for more information.\n"); 344 - arg_freetable(argtable, ARGTABLE_COUNT); 345 - free(filtered_argv); return EXIT_FAILURE; 337 + print_errors(stdout, end); 338 + print_commands(argtable); 339 + CLEANUP_ARGS_AND_ARGV(); 340 + return EXIT_FAILURE; 341 + } 342 + 343 + if (no_clear_screen->count > 0 && watch->count == 0) { 344 + crfprintf(stderr, msg.misuse_clear_screen); 345 + CLEANUP_ARGS_AND_ARGV(); 346 + return EXIT_FAILURE; 347 + } 348 + 349 + if (eval->count == 0 && file->count > 0 && file->filename[0] != NULL) { 350 + const char *positional = file->filename[0]; 351 + int first_pos_idx = find_argv_token_index(argc, argv, positional); 352 + 353 + if (first_pos_idx <= 0 || first_pos_idx >= argc) { 354 + crfprintf(stderr, msg.argument_fatal); 355 + CLEANUP_ARGS_AND_ARGV(); 356 + return EXIT_FAILURE; 357 + } 358 + 359 + const subcommand_t *cmd = find_subcommand(positional); 360 + 361 + if (cmd) { 362 + if (watch->count > 0) { 363 + crfprintf(stderr, msg.watch_subcommand_error); 364 + CLEANUP_ARGS_AND_ARGV(); 365 + return EXIT_FAILURE; 366 + } 367 + 368 + int exitcode = cmd->fn( 369 + argc - first_pos_idx, 370 + argv + first_pos_idx 371 + ); 372 + 373 + CLEANUP_ARGS_AND_ARGV(); 374 + return exitcode; 375 + } 376 + 377 + if (pkg_script_exists("package.json", positional)) { 378 + if (watch->count > 0) { 379 + crfprintf(stderr, msg.watch_subcommand_error); 380 + CLEANUP_ARGS_AND_ARGV(); 381 + return EXIT_FAILURE; 382 + } 383 + 384 + int run_argc = argc - first_pos_idx + 1; 385 + char **run_argv = try_oom(sizeof(*run_argv) * (size_t)(run_argc + 1)); 386 + 387 + run_argv[0] = argv[0]; 388 + memcpy(run_argv + 1, argv + first_pos_idx, sizeof(*run_argv) * (size_t)(run_argc - 1)); 389 + 390 + run_argv[run_argc] = NULL; 391 + int exitcode = pkg_cmd_run(run_argc, run_argv); 392 + 393 + free(run_argv); 394 + CLEANUP_ARGS_AND_ARGV(); 395 + 396 + return exitcode; 397 + } 346 398 } 347 399 348 400 bool has_stdin = !isatty(STDIN_FILENO); 349 401 bool repl_mode = (file->count == 0 && eval->count == 0 && !has_stdin); 402 + bool stdin_mode = (has_stdin && file->count == 0); 403 + 404 + if (watch->count > 0 && (eval->count > 0 || repl_mode || stdin_mode)) { 405 + crfprintf(stderr, msg.watch_module_error); 406 + CLEANUP_ARGS_AND_ARGV(); 407 + return EXIT_FAILURE; 408 + } 350 409 351 410 const char *module_file = repl_mode 352 411 ? NULL 353 412 : (file->count > 0 ? file->filename[0] : NULL); 413 + 414 + if (watch->count > 0) { 415 + char *resolved_file = NULL; 416 + 417 + resolved_file = resolve_js_file(module_file); 418 + if (resolved_file) module_file = resolved_file; 419 + 420 + if (!module_file || esm_is_url(module_file)) { 421 + crfprintf(stderr, msg.watch_entrypoint_error); 422 + if (resolved_file) free(resolved_file); 423 + CLEANUP_ARGS_AND_ARGV(); 424 + return EXIT_FAILURE; 425 + } 426 + 427 + int res = ant_watch_run(original_argc, original_argv, module_file, no_clear_screen->count > 0); 428 + if (resolved_file) free(resolved_file); 429 + CLEANUP_ARGS_AND_ARGV(); 430 + return res; 431 + } 354 432 355 433 ant_t *js; 356 434 volatile char stack_base; 357 435 358 436 if (!(js = js_create_dynamic())) { 359 - crfprintf(stderr, "{fatal}: Failed to allocate for Ant.</>\n"); 360 - arg_freetable(argtable, ARGTABLE_COUNT); free(filtered_argv); 437 + crfprintf(stderr, msg.ant_allocation_fatal); 438 + CLEANUP_ARGS_AND_ARGV(); 361 439 return EXIT_FAILURE; 362 440 } 363 441 ··· 403 481 404 482 jsval_t snapshot_result = ant_load_snapshot(js); 405 483 if (vtype(snapshot_result) == T_ERR) { 406 - crfprintf(stderr, "{warn} <bold>Failed to load snapshot:</> %s\n", js_str(js, snapshot_result)); 484 + crfprintf(stderr, msg.snapshot_warn, js_str(js, snapshot_result)); 407 485 } 408 486 409 487 if (eval->count > 0) { ··· 415 493 ant_repl_run(); 416 494 } 417 495 418 - else if (has_stdin && file->count == 0) { 496 + else if (stdin_mode) { 419 497 size_t len = 0; char *buf = read_stdin(&len); 420 498 if (!buf) { 421 - crfprintf(stderr, "{fatal}: Failed to allocate for Ant.</>\n"); 499 + crfprintf(stderr, msg.ant_allocation_fatal); 422 500 js_result = EXIT_FAILURE; goto cleanup; 423 501 } 424 502 eval_code(js, buf, len, "[stdin]", print->count > 0); free(buf); 425 503 } 426 504 427 505 else { 428 - struct stat path_stat; 429 - char *resolved_file = NULL; 430 - 431 - resolved_file = resolve_js_file(module_file); 506 + char *resolved_file = resolve_js_file(module_file); 432 507 if (resolved_file) module_file = resolved_file; 433 508 434 - if (stat(module_file, &path_stat) == 0 && S_ISDIR(path_stat.st_mode)) { 435 - size_t len = strlen(module_file); 436 - int has_slash = (len > 0 && module_file[len - 1] == '/'); 437 - if (resolved_file) free(resolved_file); 438 - resolved_file = try_oom(len + 10 + (has_slash ? 0 : 1)); 439 - sprintf(resolved_file, "%s%sindex.js", module_file, has_slash ? "" : "/"); 440 - module_file = resolved_file; 441 - } 442 - 443 509 js_result = execute_module(js, module_file); 444 510 js_run_event_loop(js); 445 511 ··· 448 514 449 515 cleanup: { 450 516 js_destroy(js); 451 - arg_freetable(argtable, ARGTABLE_COUNT); 452 - free(filtered_argv); 517 + CLEANUP_ARGS_AND_ARGV(); 453 518 } 519 + #undef CLEANUP_ARGS_AND_ARGV 454 520 455 521 return js_result; 456 522 }
+13 -4
src/utils.c
··· 1 1 #include "utils.h" 2 + #include "messages.h" 2 3 3 4 #include <stdio.h> 4 5 #include <stdbool.h> ··· 68 69 69 70 char *resolve_js_file(const char *filename) { 70 71 extern bool esm_is_url(const char *path); 72 + if (!filename) return NULL; 71 73 if (esm_is_url(filename)) return strdup(filename); 72 74 73 75 struct stat st; 74 - if (stat(filename, &st) == 0 && S_ISREG(st.st_mode)) { 75 - return strdup(filename); 76 + if (stat(filename, &st) == 0) { 77 + if (S_ISREG(st.st_mode)) return strdup(filename); 78 + if (!S_ISDIR(st.st_mode)) return NULL; 79 + 80 + size_t len = strlen(filename); 81 + int has_slash = (len > 0 && filename[len - 1] == '/'); 82 + char *index_path = try_oom(len + 10 + (has_slash ? 0 : 1)); 83 + sprintf(index_path, "%s%sindex.js", filename, has_slash ? "" : "/"); 84 + return index_path; 76 85 } 77 86 78 87 if (has_js_extension(filename)) return NULL; ··· 96 105 void *try_oom(size_t size) { 97 106 void *p = malloc(size); 98 107 if (!p) { 99 - crfprintf(stderr, "<bold+red>FATAL</bold>: Out of memory\n"); 108 + crfprintf(stderr, msg.oom_fatal); 100 109 exit(EXIT_FAILURE); 101 110 } return p; 102 111 } ··· 117 126 memcpy(buf->ptr, src, len); 118 127 buf->ptr[len] = '\0'; 119 128 return buf->ptr; 120 - } 129 + }
+333
src/watch.c
··· 1 + #include "watch.h" 2 + #include "messages.h" 3 + 4 + #include <uv.h> 5 + #include <stdio.h> 6 + #include <signal.h> 7 + #include <stdint.h> 8 + #include <stdlib.h> 9 + #include <string.h> 10 + #include <stdbool.h> 11 + #include <crprintf.h> 12 + 13 + #ifdef _WIN32 14 + #include <direct.h> 15 + #endif 16 + 17 + #ifndef SIGKILL 18 + #define SIGKILL SIGTERM 19 + #endif 20 + 21 + typedef struct watch_state { 22 + uv_loop_t loop; 23 + uv_fs_event_t fs_event; 24 + uv_signal_t sigint; 25 + uv_signal_t sigterm; 26 + uv_timer_t kill_timer; 27 + uv_process_t *child; 28 + char **child_argv; 29 + char *watch_path; 30 + bool no_clear_screen; 31 + bool stop_requested; 32 + bool restart_pending; 33 + bool child_running; 34 + bool fs_event_inited; 35 + bool sigint_inited; 36 + bool sigterm_inited; 37 + bool kill_timer_inited; 38 + bool kill_timer_running; 39 + int final_exit_code; 40 + } watch_state_t; 41 + 42 + static void watch_clear_screen(void) { 43 + fputs("\033[3J\033[2J\033[H", stdout); 44 + fflush(stdout); 45 + } 46 + 47 + static char *watch_resolve_path(const char *path) { 48 + #ifdef _WIN32 49 + char *resolved = _fullpath(NULL, path, 0); 50 + if (resolved) return resolved; 51 + return _strdup(path); 52 + #else 53 + char abs_path[PATH_MAX]; 54 + const char *resolved = realpath(path, abs_path); 55 + return strdup(resolved ? resolved : path); 56 + #endif 57 + } 58 + 59 + static char **watch_build_child_argv(int argc, char **argv) { 60 + char **child_argv = calloc((size_t)argc + 1, sizeof(char *)); 61 + if (!child_argv) return NULL; 62 + 63 + int out = 0; 64 + for (int i = 0; i < argc; i++) { 65 + if (strcmp(argv[i], "--watch") == 0) continue; 66 + if (strcmp(argv[i], "--no-clear-screen") == 0) continue; 67 + child_argv[out++] = argv[i]; 68 + } 69 + 70 + child_argv[out] = NULL; 71 + if (out == 0) { 72 + free(child_argv); 73 + return NULL; 74 + } 75 + 76 + return child_argv; 77 + } 78 + 79 + static void watch_stop_kill_timer(watch_state_t *state) { 80 + if (!state->kill_timer_inited || !state->kill_timer_running) return; 81 + uv_timer_stop(&state->kill_timer); 82 + state->kill_timer_running = false; 83 + } 84 + 85 + static void watch_on_child_closed(uv_handle_t *handle) { free(handle); } 86 + static void watch_request_shutdown(watch_state_t *state, int exit_code); 87 + static void watch_try_spawn_child(watch_state_t *state); 88 + 89 + static void watch_on_kill_timer(uv_timer_t *timer) { 90 + watch_state_t *state = (watch_state_t *)timer->data; 91 + state->kill_timer_running = false; 92 + 93 + if (state->child_running && state->child) 94 + uv_process_kill(state->child, SIGKILL); 95 + if (state->stop_requested && !uv_is_closing((uv_handle_t *)&state->kill_timer)) 96 + uv_close((uv_handle_t *)&state->kill_timer, NULL); 97 + } 98 + 99 + static void watch_on_child_exit(uv_process_t *proc, int64_t exit_status, int term_signal) { 100 + watch_state_t *state = (watch_state_t *)proc->data; 101 + state->child_running = false; 102 + state->child = NULL; 103 + watch_stop_kill_timer(state); 104 + 105 + if (state->stop_requested) { 106 + if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) 107 + uv_close((uv_handle_t *)&state->kill_timer, NULL); 108 + } else if (state->restart_pending) { 109 + watch_try_spawn_child(state); 110 + } else if (term_signal > 0) { 111 + state->final_exit_code = 128 + term_signal; 112 + } else state->final_exit_code = (int)exit_status; 113 + 114 + uv_close((uv_handle_t *)proc, watch_on_child_closed); 115 + } 116 + 117 + static int watch_spawn_child(watch_state_t *state) { 118 + uv_process_t *proc = calloc(1, sizeof(*proc)); 119 + if (!proc) return UV_ENOMEM; 120 + 121 + uv_stdio_container_t stdio[3]; 122 + stdio[0].flags = UV_INHERIT_FD; stdio[0].data.fd = 0; 123 + stdio[1].flags = UV_INHERIT_FD; stdio[1].data.fd = 1; 124 + stdio[2].flags = UV_INHERIT_FD; stdio[2].data.fd = 2; 125 + 126 + uv_process_options_t options = {0}; 127 + options.file = state->child_argv[0]; 128 + options.args = state->child_argv; 129 + options.exit_cb = watch_on_child_exit; 130 + options.stdio_count = 3; 131 + options.stdio = stdio; 132 + 133 + int rc = uv_spawn(&state->loop, proc, &options); 134 + if (rc != 0) { 135 + free(proc); 136 + return rc; 137 + } 138 + 139 + proc->data = state; 140 + state->child = proc; 141 + state->child_running = true; 142 + state->restart_pending = false; 143 + return 0; 144 + } 145 + 146 + static void watch_try_spawn_child(watch_state_t *state) { 147 + if (state->stop_requested) return; 148 + if (!state->no_clear_screen && state->restart_pending) watch_clear_screen(); 149 + int rc = watch_spawn_child(state); 150 + 151 + if (rc != 0) { 152 + crfprintf(stderr, msg.watch_spawn_failed, uv_strerror(rc)); 153 + watch_request_shutdown(state, EXIT_FAILURE); 154 + } 155 + } 156 + 157 + static void watch_start_kill_timer(watch_state_t *state, uint64_t timeout_ms) { 158 + if (!state->kill_timer_inited || state->kill_timer_running) return; 159 + int rc = uv_timer_start(&state->kill_timer, watch_on_kill_timer, timeout_ms, 0); 160 + if (rc == 0) state->kill_timer_running = true; 161 + } 162 + 163 + static void watch_close_signals_and_watcher(watch_state_t *state) { 164 + if (state->fs_event_inited && !uv_is_closing((uv_handle_t *)&state->fs_event)) { 165 + uv_fs_event_stop(&state->fs_event); 166 + uv_close((uv_handle_t *)&state->fs_event, NULL); 167 + } 168 + 169 + if (state->sigint_inited && !uv_is_closing((uv_handle_t *)&state->sigint)) { 170 + uv_signal_stop(&state->sigint); 171 + uv_close((uv_handle_t *)&state->sigint, NULL); 172 + } 173 + 174 + if (state->sigterm_inited && !uv_is_closing((uv_handle_t *)&state->sigterm)) { 175 + uv_signal_stop(&state->sigterm); 176 + uv_close((uv_handle_t *)&state->sigterm, NULL); 177 + } 178 + } 179 + 180 + static void watch_request_shutdown(watch_state_t *state, int exit_code) { 181 + if (state->stop_requested) return; 182 + 183 + state->stop_requested = true; 184 + state->restart_pending = false; 185 + state->final_exit_code = exit_code; 186 + 187 + watch_close_signals_and_watcher(state); 188 + 189 + if (state->child_running && state->child) { 190 + int rc = uv_process_kill(state->child, SIGTERM); 191 + if (rc != 0 && rc != UV_ESRCH) { 192 + crfprintf(stderr, msg.watch_graceful_term, uv_strerror(rc)); 193 + } 194 + watch_start_kill_timer(state, 1000); 195 + } else if (state->kill_timer_inited && !uv_is_closing((uv_handle_t *)&state->kill_timer)) { 196 + uv_close((uv_handle_t *)&state->kill_timer, NULL); 197 + } 198 + } 199 + 200 + static void watch_request_restart(watch_state_t *state) { 201 + if (state->stop_requested) return; 202 + if (!state->restart_pending) {} 203 + state->restart_pending = true; 204 + 205 + if (state->child_running && state->child) { 206 + int rc = uv_process_kill(state->child, SIGTERM); 207 + if (rc != 0 && rc != UV_ESRCH) { 208 + crfprintf(stderr, msg.watch_child_error, uv_strerror(rc)); 209 + watch_request_shutdown(state, EXIT_FAILURE); 210 + return; 211 + } 212 + watch_start_kill_timer(state, 1000); 213 + } else watch_try_spawn_child(state); 214 + } 215 + 216 + static void watch_on_fs_event(uv_fs_event_t *handle, const char *filename, int events, int status) { 217 + watch_state_t *state = (watch_state_t *)handle->data; 218 + 219 + if (status < 0) { 220 + crfprintf(stderr, msg.watch_warn_normal, state->watch_path, uv_strerror(status)); 221 + return; 222 + } 223 + 224 + if ((events & (UV_CHANGE | UV_RENAME)) == 0) return; 225 + watch_request_restart(state); 226 + } 227 + 228 + static void watch_on_signal(uv_signal_t *handle, int signum) { 229 + watch_state_t *state = (watch_state_t *)handle->data; 230 + watch_request_shutdown(state, 128 + signum); 231 + } 232 + 233 + int ant_watch_run(int argc, char **argv, const char *entry_file, bool no_clear_screen) { 234 + if (!entry_file || !*entry_file) { 235 + crfprintf(stderr, msg.watch_entrypoint_missing); 236 + return EXIT_FAILURE; 237 + } 238 + 239 + watch_state_t state = {0}; 240 + state.child_argv = watch_build_child_argv(argc, argv); 241 + state.watch_path = watch_resolve_path(entry_file); 242 + state.no_clear_screen = no_clear_screen; 243 + state.final_exit_code = EXIT_SUCCESS; 244 + 245 + if (!state.child_argv || !state.watch_path) { 246 + free(state.child_argv); 247 + free(state.watch_path); 248 + crfprintf(stderr, msg.watch_start_fatal); 249 + return EXIT_FAILURE; 250 + } 251 + 252 + int rc = uv_loop_init(&state.loop); 253 + if (rc != 0) { 254 + crfprintf(stderr, msg.watch_loop_fatal, uv_strerror(rc)); 255 + free(state.child_argv); free(state.watch_path); 256 + return EXIT_FAILURE; 257 + } 258 + 259 + rc = uv_fs_event_init(&state.loop, &state.fs_event); 260 + if (rc == 0) state.fs_event_inited = true; 261 + 262 + if (rc == 0) { 263 + state.fs_event.data = &state; 264 + rc = uv_fs_event_start(&state.fs_event, watch_on_fs_event, state.watch_path, 0); 265 + } 266 + 267 + if (rc != 0) { 268 + crfprintf(stderr, msg.watch_file_failed, state.watch_path, uv_strerror(rc)); 269 + watch_request_shutdown(&state, EXIT_FAILURE); 270 + } 271 + 272 + if (rc == 0) { 273 + rc = uv_signal_init(&state.loop, &state.sigint); 274 + if (rc == 0) state.sigint_inited = true; 275 + if (rc == 0) { 276 + state.sigint.data = &state; 277 + rc = uv_signal_start(&state.sigint, watch_on_signal, SIGINT); 278 + } 279 + } 280 + 281 + if (rc == 0) { 282 + rc = uv_signal_init(&state.loop, &state.sigterm); 283 + if (rc == 0) state.sigterm_inited = true; 284 + if (rc == 0) { 285 + state.sigterm.data = &state; 286 + int sigterm_rc = uv_signal_start(&state.sigterm, watch_on_signal, SIGTERM); 287 + if (sigterm_rc != 0 && sigterm_rc != UV_ENOSYS && sigterm_rc != UV_EINVAL) { 288 + rc = sigterm_rc; 289 + } else if (sigterm_rc != 0) { 290 + uv_signal_stop(&state.sigterm); 291 + uv_close((uv_handle_t *)&state.sigterm, NULL); 292 + state.sigterm_inited = false; 293 + } 294 + } 295 + } 296 + 297 + if (rc == 0) { 298 + rc = uv_timer_init(&state.loop, &state.kill_timer); 299 + if (rc == 0) { 300 + state.kill_timer_inited = true; 301 + state.kill_timer.data = &state; 302 + } 303 + } 304 + 305 + if (rc != 0) { 306 + crfprintf(stderr, msg.watch_loop_handles_fatal, uv_strerror(rc)); 307 + watch_request_shutdown(&state, EXIT_FAILURE); 308 + } 309 + 310 + if (!state.stop_requested) watch_try_spawn_child(&state); 311 + while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {} 312 + 313 + if (state.fs_event_inited && !uv_is_closing((uv_handle_t *)&state.fs_event)) { 314 + uv_close((uv_handle_t *)&state.fs_event, NULL); 315 + } 316 + if (state.sigint_inited && !uv_is_closing((uv_handle_t *)&state.sigint)) { 317 + uv_close((uv_handle_t *)&state.sigint, NULL); 318 + } 319 + if (state.sigterm_inited && !uv_is_closing((uv_handle_t *)&state.sigterm)) { 320 + uv_close((uv_handle_t *)&state.sigterm, NULL); 321 + } 322 + if (state.kill_timer_inited && !uv_is_closing((uv_handle_t *)&state.kill_timer)) { 323 + uv_close((uv_handle_t *)&state.kill_timer, NULL); 324 + } 325 + while (uv_run(&state.loop, UV_RUN_DEFAULT) != 0) {} 326 + 327 + rc = uv_loop_close(&state.loop); 328 + if (rc != 0) crfprintf(stderr, msg.watch_loop_cleanup, uv_strerror(rc)); 329 + 330 + free(state.child_argv); 331 + free(state.watch_path); 332 + return state.final_exit_code; 333 + }
+1
tests/server.async.js
··· 9 9 return await meow(c); 10 10 } 11 11 12 + console.log('server on http://localhost:8000'); 12 13 Ant.serve(8000, server);