Skip to content
Open
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions cli/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ serde_yaml.workspace = true
toml.workspace = true
chrono.workspace = true
hotpath = { workspace = true, optional = true }
notify = "8.2.0"

[dev-dependencies]
tokio-test.workspace = true
Expand Down
16 changes: 11 additions & 5 deletions cli/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,9 @@ enum Command {
#[arg(short, long)]
no_strict: bool,

#[arg(short, long)]
watch: bool,

/// The files to run
#[arg(required = true)]
paths: Vec<String>,
Expand Down Expand Up @@ -229,25 +232,26 @@ fn run_main() -> Result<()> {
// Check if this is currently a single-file executable
if let Ok(Some(js)) = find_section(ANDROMEDA_JS_CODE_SECTION) {
// Try to load embedded config, fall back to defaults if not found
let (verbose, no_strict) = match find_section(ANDROMEDA_CONFIG_SECTION) {
let (verbose, no_strict, watch) = match find_section(ANDROMEDA_CONFIG_SECTION) {
Ok(Some(config_bytes)) => {
match serde_json::from_slice::<EmbeddedConfig>(config_bytes) {
Ok(config) => (config.verbose, config.no_strict),
Ok(config) => (config.verbose, config.no_strict, false),
Err(_) => {
// If config is corrupted or in old format, use defaults
(false, false)
(false, false, false)
}
}
}
_ => {
// No config section found (old binary format), use defaults
(false, false)
(false, false, false)
}
};

return run(
verbose,
no_strict,
watch,
vec![RuntimeFile::Embedded {
path: String::from("internal"),
content: js,
Expand Down Expand Up @@ -277,6 +281,7 @@ fn run_main() -> Result<()> {
command: Command::Run {
verbose: false,
no_strict: false,
watch: false,
paths: vec![raw_args[0].clone()],
},
}
Expand All @@ -302,13 +307,14 @@ fn run_main() -> Result<()> {
Command::Run {
verbose,
no_strict,
watch,
paths,
} => {
let runtime_files: Vec<RuntimeFile> = paths
.into_iter()
.map(|path| RuntimeFile::Local { path })
.collect();
run(verbose, no_strict, runtime_files)
run(verbose, no_strict, watch, runtime_files)
}
Command::Compile {
path,
Expand Down
57 changes: 55 additions & 2 deletions cli/src/run.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

use crate::config::{AndromedaConfig, ConfigManager};
use crate::error::{Result, read_file_with_context};
use notify::Watcher;
use std::time::{Duration};
use andromeda_core::{
AndromedaError, ErrorReporter, HostData, ImportMap, Runtime, RuntimeConfig, RuntimeFile,
};
Expand All @@ -13,8 +15,59 @@ use andromeda_runtime::{

#[allow(clippy::result_large_err)]
#[cfg_attr(feature = "hotpath", hotpath::measure)]
pub fn run(verbose: bool, no_strict: bool, files: Vec<RuntimeFile>) -> Result<()> {
create_runtime_files(verbose, no_strict, files, None)
pub fn run(verbose: bool, no_strict: bool, watch: bool, files: Vec<RuntimeFile>) -> Result<()> {
if watch {
// get the directories of all local files
let directories: Vec<std::path::PathBuf> = files
.iter()
.filter_map(|file| {
if let RuntimeFile::Local { path } = file {
std::path::Path::new(path).parent().map(|p| p.to_path_buf())
} else {
None
}
})
.collect();
watch_and_run(verbose, no_strict, files, directories)
} else {
create_runtime_files(verbose, no_strict, files, None)
}
}

fn watch_and_run(
verbose: bool,
no_strict: bool,
files: Vec<RuntimeFile>,
directories: Vec<std::path::PathBuf>,
) -> Result<()> {
let (tx, rx) = std::sync::mpsc::channel();
let mut watcher = notify::recommended_watcher(tx).unwrap();

for dir in &directories {
watcher
.watch(dir, notify::RecursiveMode::Recursive).unwrap();
}

create_runtime_files(verbose, no_strict, files.clone(), None)?;

let debounce = Duration::from_millis(300);
loop {
// Wait for the first event (blocking)
match rx.recv() {
Ok(_event) => {
// Now, collect any additional events that arrive within the debounce window
while rx.recv_timeout(debounce).is_ok() {
// Just drain events within debounce window
}
// After debounce window, trigger the action
create_runtime_files(verbose, no_strict, files.clone(), None)?;
}
Err(_e) => {
break;
}
}
}
Ok(())
}

#[allow(clippy::result_large_err)]
Expand Down
1 change: 1 addition & 0 deletions core/src/runtime.rs
Original file line number Diff line number Diff line change
Expand Up @@ -517,6 +517,7 @@ pub type EventLoopHandler<UserMacroTask> = fn(
host_data: &HostData<UserMacroTask>,
);

#[derive(Clone)]
pub enum RuntimeFile {
Embedded {
path: String,
Expand Down
Loading