@ape-egg/vibe-compiler 4.3.9 → 4.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ape-egg/vibe-compiler",
3
- "version": "4.3.9",
3
+ "version": "4.3.10",
4
4
  "type": "module",
5
5
  "description": "Optional Rust compiler for Vibe \u2014 pre-renders, inlines components, and optimizes iterations. The runtime never needs it.",
6
6
  "homepage": "https://vibe.korte.kim",
package/src/Cargo.lock CHANGED
@@ -1599,12 +1599,13 @@ checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
1599
1599
 
1600
1600
  [[package]]
1601
1601
  name = "vibe-compiler"
1602
- version = "4.3.9"
1602
+ version = "4.3.10"
1603
1603
  dependencies = [
1604
1604
  "clap",
1605
1605
  "colored",
1606
1606
  "glob",
1607
1607
  "html5ever",
1608
+ "libc",
1608
1609
  "markup5ever",
1609
1610
  "markup5ever_rcdom",
1610
1611
  "notify",
package/src/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "vibe-compiler"
3
- version = "4.3.9"
3
+ version = "4.3.10"
4
4
  edition = "2021"
5
5
  description = "Vibe framework compiler - compiles Vibe source files into optimized output"
6
6
  authors = ["Kim Korte"]
@@ -30,6 +30,7 @@ swc_ecma_visit = "=0.107.0"
30
30
  notify = "6.1"
31
31
  notify-debouncer-full = "0.3"
32
32
  rayon = "1.10"
33
+ libc = "0.2"
33
34
 
34
35
  [profile.release]
35
36
  opt-level = 3
@@ -9,6 +9,7 @@ mod component_tagger;
9
9
  mod binding_case;
10
10
  mod name_binding_protect;
11
11
  mod reassignment_analyzer;
12
+ mod shutdown;
12
13
  pub mod watcher;
13
14
 
14
15
  pub use compile::Compiler;
@@ -0,0 +1,85 @@
1
+ use std::path::PathBuf;
2
+
3
+ #[cfg(unix)]
4
+ const SIGNALS: [libc::c_int; 3] = [libc::SIGINT, libc::SIGTERM, libc::SIGHUP];
5
+
6
+ #[cfg(unix)]
7
+ fn signal_set() -> libc::sigset_t {
8
+ unsafe {
9
+ let mut set = std::mem::zeroed();
10
+ libc::sigemptyset(&mut set);
11
+ SIGNALS.iter().for_each(|&sig| {
12
+ libc::sigaddset(&mut set, sig);
13
+ });
14
+ set
15
+ }
16
+ }
17
+
18
+ #[cfg(unix)]
19
+ fn release(lock: &PathBuf, reason: &str, code: i32) -> ! {
20
+ let _ = std::fs::remove_file(lock);
21
+ eprintln!("{} — shutting down watcher", reason);
22
+ std::process::exit(code)
23
+ }
24
+
25
+ #[cfg(unix)]
26
+ pub fn block() -> u32 {
27
+ unsafe { libc::pthread_sigmask(libc::SIG_BLOCK, &signal_set(), std::ptr::null_mut()) };
28
+ std::os::unix::process::parent_id()
29
+ }
30
+
31
+ #[cfg(target_os = "macos")]
32
+ pub fn release_on_exit(parent: u32, lock: PathBuf) {
33
+ let event = |ident: usize, filter: i16, fflags: u32| libc::kevent {
34
+ ident,
35
+ filter,
36
+ flags: libc::EV_ADD,
37
+ fflags,
38
+ data: 0,
39
+ udata: std::ptr::null_mut(),
40
+ };
41
+ let watched: Vec<libc::kevent> = SIGNALS
42
+ .iter()
43
+ .map(|&sig| event(sig as usize, libc::EVFILT_SIGNAL, 0))
44
+ .chain([event(parent as usize, libc::EVFILT_PROC, libc::NOTE_EXIT)])
45
+ .collect();
46
+ let queue = unsafe { libc::kqueue() };
47
+ let registered = unsafe {
48
+ libc::kevent(queue, watched.as_ptr(), watched.len() as i32, std::ptr::null_mut(), 0, std::ptr::null())
49
+ };
50
+ if registered == -1 || std::os::unix::process::parent_id() != parent {
51
+ release(&lock, "parent process exited", 0);
52
+ }
53
+ std::thread::spawn(move || {
54
+ let mut fired = event(0, 0, 0);
55
+ unsafe { libc::kevent(queue, std::ptr::null(), 0, &mut fired, 1, std::ptr::null()) };
56
+ match fired.filter {
57
+ libc::EVFILT_SIGNAL => release(&lock, "received shutdown signal", 128 + fired.ident as i32),
58
+ _ => release(&lock, "parent process exited", 0),
59
+ }
60
+ });
61
+ }
62
+
63
+ #[cfg(all(unix, not(target_os = "macos")))]
64
+ pub fn release_on_exit(parent: u32, lock: PathBuf) {
65
+ #[cfg(target_os = "linux")]
66
+ unsafe {
67
+ libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGHUP)
68
+ };
69
+ if std::os::unix::process::parent_id() != parent {
70
+ release(&lock, "parent process exited", 0);
71
+ }
72
+ std::thread::spawn(move || {
73
+ let mut sig = 0;
74
+ unsafe { libc::sigwait(&signal_set(), &mut sig) };
75
+ release(&lock, "received shutdown signal", 128 + sig)
76
+ });
77
+ }
78
+
79
+ #[cfg(not(unix))]
80
+ pub fn block() -> u32 {
81
+ 0
82
+ }
83
+
84
+ #[cfg(not(unix))]
85
+ pub fn release_on_exit(_parent: u32, _lock: PathBuf) {}
@@ -6,7 +6,7 @@ use colored::Colorize;
6
6
  use notify_debouncer_full::{new_debouncer, notify::{RecursiveMode, Watcher}};
7
7
  use regex::Regex;
8
8
 
9
- use super::{Compiler, compile::should_skip_path};
9
+ use super::{Compiler, compile::should_skip_path, shutdown};
10
10
  use crate::config::Config;
11
11
  use crate::constants::{HYPERSPEED_DIR, MANIFEST_SUFFIX};
12
12
 
@@ -406,27 +406,6 @@ fn scan_directory(
406
406
  Ok(())
407
407
  }
408
408
 
409
- /// A watcher whose spawning wrapper dies without unwinding (SIGKILL, crashed
410
- /// dev server) is orphaned: it keeps watching and its lock blocks every future
411
- /// `--watch` on the same output. Reparenting is the orphan signal — when the
412
- /// parent pid changes (to init or a reaper), the owner is gone, so release the
413
- /// lock and exit. `std::process::exit` skips Drop, hence the explicit remove.
414
- #[cfg(unix)]
415
- fn exit_when_orphaned(lock_path: PathBuf) {
416
- let parent = std::os::unix::process::parent_id();
417
- std::thread::spawn(move || loop {
418
- std::thread::sleep(Duration::from_secs(2));
419
- if std::os::unix::process::parent_id() != parent {
420
- eprintln!("parent process exited — shutting down watcher");
421
- let _ = std::fs::remove_file(&lock_path);
422
- std::process::exit(0);
423
- }
424
- });
425
- }
426
-
427
- #[cfg(not(unix))]
428
- fn exit_when_orphaned(_lock_path: PathBuf) {}
429
-
430
409
  /// A config change invalidates every piece of watcher state — dependency
431
410
  /// graph, compiler, parser, watch roots, output lock. The cleanest
432
411
  /// expression is a fresh start: release the lock and exec the same binary
@@ -464,8 +443,9 @@ fn restart_with_fresh_config(lock: WatchLock) -> ! {
464
443
  pub fn watch(config: Config, verbose: bool) -> std::result::Result<(), Box<dyn std::error::Error>> {
465
444
  // Held for the watcher's whole lifetime; a second watcher on the same
466
445
  // output exits loudly instead of silently racing this one.
446
+ let parent = shutdown::block();
467
447
  let _watch_lock = WatchLock::acquire(&config.output)?;
468
- exit_when_orphaned(_watch_lock.path.clone());
448
+ shutdown::release_on_exit(parent, _watch_lock.path.clone());
469
449
 
470
450
  println!("{}", "Building dependency graph...".cyan());
471
451
  let mut graph = build_dependency_graph(&config)?;