@ape-egg/vibe-compiler 4.3.9 → 4.3.11
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.
- package/bin/vibe-compile.js +4 -2
- package/native/vibe-compiler-darwin-arm64 +0 -0
- package/native/vibe-compiler-linux-x64 +0 -0
- package/package.json +1 -1
- package/src/Cargo.lock +2 -1
- package/src/Cargo.toml +2 -1
- package/src/compiler/mod.rs +1 -0
- package/src/compiler/shutdown.rs +110 -0
- package/src/compiler/watcher.rs +4 -24
- package/src/main.rs +5 -1
package/bin/vibe-compile.js
CHANGED
|
@@ -45,6 +45,8 @@ const wireLifecycle = (child) => {
|
|
|
45
45
|
process.on('exit', () => child.kill());
|
|
46
46
|
};
|
|
47
47
|
|
|
48
|
+
const launcherArgs = ['--launcher', String(process.ppid)];
|
|
49
|
+
|
|
48
50
|
const run = () => {
|
|
49
51
|
const binary = getPlatformBinary();
|
|
50
52
|
const binaryPath = join(nativeDir, binary);
|
|
@@ -78,7 +80,7 @@ const run = () => {
|
|
|
78
80
|
}
|
|
79
81
|
|
|
80
82
|
// Pass --cwd to tell the Rust binary the original working directory
|
|
81
|
-
const cargoArgs = ['run', '--release', '--', '--cwd', originalCwd, ...userArgs];
|
|
83
|
+
const cargoArgs = ['run', '--release', '--', '--cwd', originalCwd, ...launcherArgs, ...userArgs];
|
|
82
84
|
|
|
83
85
|
const cargo = spawn('cargo', cargoArgs, {
|
|
84
86
|
cwd: srcDir,
|
|
@@ -103,7 +105,7 @@ const run = () => {
|
|
|
103
105
|
}
|
|
104
106
|
|
|
105
107
|
// Run the native binary (it runs in the user's cwd, so no --cwd needed)
|
|
106
|
-
const child = spawn(binaryPath, userArgs, {
|
|
108
|
+
const child = spawn(binaryPath, [...launcherArgs, ...userArgs], {
|
|
107
109
|
stdio: 'inherit',
|
|
108
110
|
});
|
|
109
111
|
wireLifecycle(child);
|
|
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.
|
|
3
|
+
"version": "4.3.11",
|
|
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.
|
|
1602
|
+
version = "4.3.11"
|
|
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.
|
|
3
|
+
version = "4.3.11"
|
|
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
|
package/src/compiler/mod.rs
CHANGED
|
@@ -0,0 +1,110 @@
|
|
|
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, launcher: Option<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(
|
|
45
|
+
std::iter::once(parent)
|
|
46
|
+
.chain(launcher)
|
|
47
|
+
.map(|pid| event(pid as usize, libc::EVFILT_PROC, libc::NOTE_EXIT)),
|
|
48
|
+
)
|
|
49
|
+
.collect();
|
|
50
|
+
let queue = unsafe { libc::kqueue() };
|
|
51
|
+
let registered = unsafe {
|
|
52
|
+
libc::kevent(queue, watched.as_ptr(), watched.len() as i32, std::ptr::null_mut(), 0, std::ptr::null())
|
|
53
|
+
};
|
|
54
|
+
if registered == -1 || std::os::unix::process::parent_id() != parent {
|
|
55
|
+
release(&lock, "parent process exited", 0);
|
|
56
|
+
}
|
|
57
|
+
std::thread::spawn(move || {
|
|
58
|
+
let mut fired = event(0, 0, 0);
|
|
59
|
+
unsafe { libc::kevent(queue, std::ptr::null(), 0, &mut fired, 1, std::ptr::null()) };
|
|
60
|
+
match fired.filter {
|
|
61
|
+
libc::EVFILT_SIGNAL => release(&lock, "received shutdown signal", 128 + fired.ident as i32),
|
|
62
|
+
_ if fired.ident == parent as usize => release(&lock, "parent process exited", 0),
|
|
63
|
+
_ => release(&lock, "launcher process exited", 0),
|
|
64
|
+
}
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
#[cfg(target_os = "linux")]
|
|
69
|
+
fn release_on_launcher_exit(launcher: u32, lock: &PathBuf) {
|
|
70
|
+
match unsafe { libc::syscall(libc::SYS_pidfd_open, launcher, 0) } as libc::c_int {
|
|
71
|
+
-1 if std::io::Error::last_os_error().raw_os_error() == Some(libc::ESRCH) => {
|
|
72
|
+
release(lock, "launcher process exited", 0)
|
|
73
|
+
}
|
|
74
|
+
-1 => {}
|
|
75
|
+
pidfd => {
|
|
76
|
+
let lock = lock.clone();
|
|
77
|
+
std::thread::spawn(move || {
|
|
78
|
+
let mut exited = libc::pollfd { fd: pidfd, events: libc::POLLIN, revents: 0 };
|
|
79
|
+
unsafe { libc::poll(&mut exited, 1, -1) };
|
|
80
|
+
release(&lock, "launcher process exited", 0)
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
#[cfg(all(unix, not(target_os = "macos")))]
|
|
87
|
+
pub fn release_on_exit(parent: u32, launcher: Option<u32>, lock: PathBuf) {
|
|
88
|
+
#[cfg(target_os = "linux")]
|
|
89
|
+
unsafe {
|
|
90
|
+
libc::prctl(libc::PR_SET_PDEATHSIG, libc::SIGHUP)
|
|
91
|
+
};
|
|
92
|
+
if std::os::unix::process::parent_id() != parent {
|
|
93
|
+
release(&lock, "parent process exited", 0);
|
|
94
|
+
}
|
|
95
|
+
#[cfg(target_os = "linux")]
|
|
96
|
+
launcher.iter().for_each(|&pid| release_on_launcher_exit(pid, &lock));
|
|
97
|
+
std::thread::spawn(move || {
|
|
98
|
+
let mut sig = 0;
|
|
99
|
+
unsafe { libc::sigwait(&signal_set(), &mut sig) };
|
|
100
|
+
release(&lock, "received shutdown signal", 128 + sig)
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
#[cfg(not(unix))]
|
|
105
|
+
pub fn block() -> u32 {
|
|
106
|
+
0
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
#[cfg(not(unix))]
|
|
110
|
+
pub fn release_on_exit(_parent: u32, _launcher: Option<u32>, _lock: PathBuf) {}
|
package/src/compiler/watcher.rs
CHANGED
|
@@ -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
|
|
@@ -461,11 +440,12 @@ fn restart_with_fresh_config(lock: WatchLock) -> ! {
|
|
|
461
440
|
}
|
|
462
441
|
|
|
463
442
|
/// Start watching for file changes
|
|
464
|
-
pub fn watch(config: Config, verbose: bool) -> std::result::Result<(), Box<dyn std::error::Error>> {
|
|
443
|
+
pub fn watch(config: Config, verbose: bool, launcher: Option<u32>) -> 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
|
-
|
|
448
|
+
shutdown::release_on_exit(parent, launcher, _watch_lock.path.clone());
|
|
469
449
|
|
|
470
450
|
println!("{}", "Building dependency graph...".cyan());
|
|
471
451
|
let mut graph = build_dependency_graph(&config)?;
|
package/src/main.rs
CHANGED
|
@@ -98,6 +98,10 @@ struct Args {
|
|
|
98
98
|
/// Compile the pages tree to SPA output: fragments + route table + shell
|
|
99
99
|
#[arg(long)]
|
|
100
100
|
spa: bool,
|
|
101
|
+
|
|
102
|
+
/// Pid of the process that launched the wrapper; --watch also exits when it does
|
|
103
|
+
#[arg(long, hide = true)]
|
|
104
|
+
launcher: Option<u32>,
|
|
101
105
|
}
|
|
102
106
|
|
|
103
107
|
fn main() {
|
|
@@ -256,7 +260,7 @@ fn main() {
|
|
|
256
260
|
|
|
257
261
|
// Handle watch mode
|
|
258
262
|
if args.watch {
|
|
259
|
-
if let Err(e) = compiler::watcher::watch(config, args.verbose) {
|
|
263
|
+
if let Err(e) = compiler::watcher::watch(config, args.verbose, args.launcher) {
|
|
260
264
|
eprintln!("{}: {}", "Error".red(), e);
|
|
261
265
|
std::process::exit(1);
|
|
262
266
|
}
|