Skip to content

Commit 860acb2

Browse files
committed
Fix moose config reading w/ read only config
1 parent 8e7a6b0 commit 860acb2

3 files changed

Lines changed: 105 additions & 53 deletions

File tree

apps/framework-cli/src/cli/routines/mod.rs

Lines changed: 53 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -467,11 +467,8 @@ pub async fn start_development_mode(
467467
settings: &Settings,
468468
enable_mcp: bool,
469469
) -> anyhow::Result<()> {
470-
// Set global flag so ensure_typescript_compiled knows to skip
471-
// (tspc --watch handles compilation in dev mode)
472470
use crate::utilities::constants::IS_DEV_MODE;
473471
use std::sync::atomic::Ordering;
474-
IS_DEV_MODE.store(true, Ordering::Relaxed);
475472

476473
display::show_message_wrapper(
477474
MessageType::Info,
@@ -515,8 +512,29 @@ pub async fn start_development_mode(
515512
// For TypeScript, initial compilation is required (no ts-node fallback). Fail fast if it fails.
516513
let ts_compile_handle = if project.language == SupportedLanguages::Typescript {
517514
use crate::cli::ts_compilation_watcher::spawn_and_await_initial_compile;
515+
use crate::framework::typescript::parser::ensure_typescript_compiled;
518516
match spawn_and_await_initial_compile(&project).await {
519-
Ok(handle) => Some(handle),
517+
Ok(Some(handle)) => {
518+
// tspc --watch is working; set IS_DEV_MODE so ensure_typescript_compiled
519+
// is a no-op (tspc --watch handles compilation going forward)
520+
IS_DEV_MODE.store(true, Ordering::Relaxed);
521+
Some(handle)
522+
}
523+
Ok(None) => {
524+
// Old moose-tspc without --watch support: run single compilation,
525+
// then use FileWatcher for watching changes
526+
warn!("moose-tspc does not support --watch; using file watcher fallback");
527+
display::show_message_wrapper(
528+
MessageType::Highlight,
529+
Message {
530+
action: "Fallback".to_string(),
531+
details: "moose-tspc does not support --watch; using file watcher"
532+
.to_string(),
533+
},
534+
);
535+
ensure_typescript_compiled(&project)?;
536+
None
537+
}
520538
Err(e) => {
521539
error!("Initial TypeScript compilation failed: {}", e);
522540
display::show_message_wrapper(
@@ -739,44 +757,38 @@ pub async fn start_development_mode(
739757
// Create shutdown channel for graceful watcher termination
740758
let (watcher_shutdown_tx, watcher_shutdown_rx) = tokio::sync::watch::channel(false);
741759

742-
// Use TypeScript compilation watcher for TS projects (incremental compilation)
743-
// Use file watcher for Python projects
760+
// Use TypeScript compilation watcher when tspc --watch is available (ts_compile_handle is Some),
761+
// otherwise use file watcher (Python projects, or TypeScript with old moose-tspc)
744762
let state_storage = Arc::new(state_storage);
745-
match project.language {
746-
SupportedLanguages::Typescript => {
747-
// Pass the handle from spawn_and_await_initial_compile() if we have one.
748-
// This continues watching the already-running tspc process instead of
749-
// spawning a new one, and ensures we don't trigger duplicate plan_changes.
750-
let ts_watcher = TsCompilationWatcher::new();
751-
ts_watcher.start(
752-
project.clone(),
753-
route_update_channel,
754-
webapp_update_channel,
755-
infra_map,
756-
process_registry.clone(),
757-
metrics.clone(),
758-
state_storage,
759-
settings.clone(),
760-
processing_coordinator.clone(),
761-
watcher_shutdown_rx,
762-
ts_compile_handle,
763-
)?;
764-
}
765-
SupportedLanguages::Python => {
766-
let file_watcher = FileWatcher::new();
767-
file_watcher.start(
768-
project.clone(),
769-
route_update_channel,
770-
webapp_update_channel,
771-
infra_map,
772-
process_registry.clone(),
773-
metrics.clone(),
774-
state_storage,
775-
settings.clone(),
776-
processing_coordinator.clone(),
777-
watcher_shutdown_rx,
778-
)?;
779-
}
763+
if let Some(handle) = ts_compile_handle {
764+
let ts_watcher = TsCompilationWatcher::new();
765+
ts_watcher.start(
766+
project.clone(),
767+
route_update_channel,
768+
webapp_update_channel,
769+
infra_map,
770+
process_registry.clone(),
771+
metrics.clone(),
772+
state_storage,
773+
settings.clone(),
774+
processing_coordinator.clone(),
775+
watcher_shutdown_rx,
776+
Some(handle),
777+
)?;
778+
} else {
779+
let file_watcher = FileWatcher::new();
780+
file_watcher.start(
781+
project.clone(),
782+
route_update_channel,
783+
webapp_update_channel,
784+
infra_map,
785+
process_registry.clone(),
786+
metrics.clone(),
787+
state_storage,
788+
settings.clone(),
789+
processing_coordinator.clone(),
790+
watcher_shutdown_rx,
791+
)?;
780792
}
781793

782794
// Log MCP server status

apps/framework-cli/src/cli/settings.rs

Lines changed: 44 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -290,10 +290,28 @@ is_moose_developer=false
290290
}
291291
};
292292

293-
table.entry("enabled").or_insert(value(true));
294-
table.entry("is_moose_developer").or_insert(value(false));
295-
296-
std::fs::write(path, toml.to_string())?;
293+
let mut changed = false;
294+
if let Entry::Vacant(e) = table.entry("enabled") {
295+
e.insert(value(true));
296+
changed = true;
297+
}
298+
if let Entry::Vacant(e) = table.entry("is_moose_developer") {
299+
e.insert(value(false));
300+
changed = true;
301+
}
302+
303+
if changed {
304+
if let Err(e) = std::fs::write(&path, toml.to_string()) {
305+
if e.kind() == std::io::ErrorKind::PermissionDenied {
306+
warn!(
307+
"Config file {} is read-only (externally managed); skipping write",
308+
path.display()
309+
);
310+
return Ok(());
311+
}
312+
return Err(e);
313+
}
314+
}
297315
}
298316
Err(e) => {
299317
show_message!(
@@ -426,7 +444,17 @@ pub fn set_suppress_dev_setup_prompt(value_to_set: bool) -> Result<(), std::io::
426444
}
427445
}
428446

429-
std::fs::write(path, doc.to_string())
447+
if let Err(e) = std::fs::write(&path, doc.to_string()) {
448+
if e.kind() == std::io::ErrorKind::PermissionDenied {
449+
warn!(
450+
"Config file {} is read-only (externally managed); skipping write",
451+
path.display()
452+
);
453+
return Ok(());
454+
}
455+
return Err(e);
456+
}
457+
Ok(())
430458
}
431459

432460
/// Updates the global CLI config (~/.moose/config.toml) to set the
@@ -457,7 +485,17 @@ pub fn set_docs_default_language(language: &str) -> Result<(), std::io::Error> {
457485
}
458486
}
459487

460-
std::fs::write(path, doc.to_string())
488+
if let Err(e) = std::fs::write(&path, doc.to_string()) {
489+
if e.kind() == std::io::ErrorKind::PermissionDenied {
490+
warn!(
491+
"Config file {} is read-only (externally managed); skipping write",
492+
path.display()
493+
);
494+
return Ok(());
495+
}
496+
return Err(e);
497+
}
498+
Ok(())
461499
}
462500

463501
#[cfg(test)]

apps/framework-cli/src/cli/ts_compilation_watcher.rs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,7 @@ fn spawn_tspc_watch(project: &Project) -> Result<Child, TsCompilationWatcherErro
128128
/// then call `start()` to begin background watching.
129129
pub async fn spawn_and_await_initial_compile(
130130
project: &Project,
131-
) -> Result<InitialCompileHandle, TsCompilationWatcherError> {
131+
) -> Result<Option<InitialCompileHandle>, TsCompilationWatcherError> {
132132
debug!(
133133
"Spawning moose-tspc --watch for initial compilation: {:?}",
134134
project.app_dir().display()
@@ -210,7 +210,7 @@ pub async fn spawn_and_await_initial_compile(
210210
} else if event.is_compile_complete() {
211211
display_compilation_success(&event);
212212
// Initial compilation done! Return handle for continued watching.
213-
return Ok(InitialCompileHandle { child, line_rx });
213+
return Ok(Some(InitialCompileHandle { child, line_rx }));
214214
}
215215
}
216216
Err(_) => {
@@ -220,12 +220,14 @@ pub async fn spawn_and_await_initial_compile(
220220
}
221221
}
222222
None => {
223-
// Kill the child process to avoid resource leak
223+
// moose-tspc exited without producing any JSON events.
224+
// This means the installed version doesn't support --watch
225+
// (it interpreted --watch as an outDir argument, ran a single
226+
// compilation, and exited). Fall back to FileWatcher.
227+
warn!("moose-tspc exited without JSON events, --watch not supported; falling back to single compilation");
224228
let _ = child.kill();
225229
let _ = child.wait();
226-
return Err(TsCompilationWatcherError::ReadError(
227-
"tspc process closed stdout before initial compilation completed".into(),
228-
));
230+
return Ok(None);
229231
}
230232
}
231233
}

0 commit comments

Comments
 (0)