Repository: AmbientRun/Ambient
File: campfire/src/web/serve.rs (Rust)

Code before:
    #[clap(flatten)]
    build: BuildOptions,
}

impl Serve {
    pub async fn run(&self) -> anyhow::Result<()> {
        if !tokio::fs::try_exists("web/www/node_modules")
            .await
            .context("Failed to query node_modules directory")?
        {
            log::info!("Installing node modules");
            tokio::process::Command::new("npm")
                .args(["install", "-d"])
                .current_dir("web/www")
                .kill_on_drop(true)
                .spawn()
                .context("Failed to spawn npm")?
                .wait()
                .await
                .context("Failed to run npm install")?;
        }

        let serve = self.serve();
        let watch = self.watch_and_build();

        tokio::select! {
            v = serve => v?,
            v = watch => v?,
        }

        Ok(())
    }

    #[cfg(target_os = "windows")]
    pub async fn serve(&self) -> anyhow::Result<()> {
        let dir = Path::new("web/www")
            .canonicalize()
            .context("Web server directory does not exist")?;

        // De-UNC the path.
        let working_dir = dunce::simplified(&dir).to_owned();

        let status = tokio::process::Command::new("cmd")
            .args(["/C", "npm", "run", "dev"])
            .current_dir(working_dir)
            .kill_on_drop(true)
            .spawn()
            .context("Failed to spawn npm")?
            .wait()
            .await
            .context("Failed to run dev web server")?;

Review comment (suggestion):
Ensures the package is always built before running the web server, to avoid situations where you are serving a `pkg` that has been hanging around and wondering why your changes don't show.

That has happened a few of us now 😝 

Code after:
pub struct Serve {
    #[clap(flatten)]
    build: BuildOptions,
    #[arg(long)]
    watch: bool,
}

impl Serve {
    pub async fn run(&self) -> anyhow::Result<()> {
        if !tokio::fs::try_exists("web/www/node_modules")
            .await
            .context("Failed to query node_modules directory")?
        {
            log::info!("Installing node modules");
            tokio::process::Command::new("npm")
                .args(["install", "-d"])
                .current_dir("web/www")
                .kill_on_drop(true)
                .spawn()
                .context("Failed to spawn npm")?
                .wait()
                .await
                .context("Failed to run npm install")?;
        }

        self.build
            .build()
            .await
            .context("Failed to build ambient client before serving")?;

        if !Path::new("./web/pkg").exists() {
            anyhow::bail!("Client package does not exist");
        }

        let serve = self.serve();
        let watch = self.watch_and_build();

        let ((), ()) = try_join(serve, watch).await?;

        Ok(())
    }

    #[cfg(target_os = "windows")]
    pub async fn serve(&self) -> anyhow::Result<()> {
        let dir = Path::new("web/www")
            .canonicalize()
            .context("Web server directory does not exist")?;

        // De-UNC the path.
        let working_dir = dunce::simplified(&dir).to_owned();