Skip to main content

cli/lib/compiler/
assets_manager.rs

1//! Upstream source: `../nest-cli/lib/compiler/assets-manager.ts`.
2
3use std::collections::BTreeSet;
4use std::fs;
5use std::path::{Path, PathBuf};
6
7use super::helpers::copy_path_resolve::copy_path_resolve;
8pub use super::{AssetDeleteOnUnlink, AssetPlan};
9
10#[derive(Clone, Debug, Default, PartialEq, Eq)]
11pub struct AssetsManager {
12    copied_once: BTreeSet<(PathBuf, PathBuf)>,
13    action_in_progress: bool,
14    watchers: Vec<AssetWatchPlan>,
15}
16
17#[derive(Clone, Debug, PartialEq, Eq)]
18pub struct AssetAction {
19    pub action: AssetFileAction,
20    pub file_path: PathBuf,
21    pub source_root: PathBuf,
22    pub out_dir: PathBuf,
23    pub watch_assets_mode: bool,
24    pub item_watch_assets: bool,
25}
26
27#[derive(Clone, Copy, Debug, PartialEq, Eq)]
28pub enum AssetFileAction {
29    Change,
30    Unlink,
31}
32
33#[derive(Clone, Debug, PartialEq, Eq)]
34pub struct AssetWatchPlan {
35    pub glob: String,
36    pub exclude: Option<String>,
37    pub out_dir: PathBuf,
38}
39
40impl AssetsManager {
41    pub fn copy_assets(&mut self, assets: &[AssetPlan], source_root: impl AsRef<Path>) {
42        self.watchers.extend(
43            assets
44                .iter()
45                .filter(|asset| asset.watch_assets)
46                .map(|asset| AssetWatchPlan {
47                    glob: asset.glob.clone(),
48                    exclude: asset.exclude.clone(),
49                    out_dir: asset.out_dir.clone(),
50                }),
51        );
52        let source_root = source_root.as_ref();
53        for asset in assets.iter().filter(|asset| !asset.watch_assets) {
54            let include = asset.include.as_deref().unwrap_or_else(|| Path::new(""));
55            let path = source_root.join(include).join(&asset.glob);
56            let action = AssetAction {
57                action: AssetFileAction::Change,
58                file_path: path,
59                source_root: source_root.to_path_buf(),
60                out_dir: asset.out_dir.clone(),
61                watch_assets_mode: false,
62                item_watch_assets: false,
63            };
64            let _ = self.action_on_file(&action);
65        }
66    }
67
68    pub fn action_on_file(&mut self, option: &AssetAction) -> Result<Option<PathBuf>, String> {
69        let key = (option.file_path.clone(), option.out_dir.clone());
70        let is_watch_enabled = option.watch_assets_mode || option.item_watch_assets;
71        if !is_watch_enabled && self.copied_once.contains(&key) {
72            return Ok(None);
73        }
74        self.copied_once.insert(key);
75        self.action_in_progress = true;
76
77        let up = option.source_root.components().count();
78        let dest = copy_path_resolve(
79            &option.file_path.to_string_lossy(),
80            &option.out_dir.to_string_lossy(),
81            up,
82        )?;
83
84        match option.action {
85            AssetFileAction::Change => {
86                if let Some(parent) = dest.parent() {
87                    fs::create_dir_all(parent).map_err(|error| {
88                        format!("Failed to create \"{}\": {error}", parent.display())
89                    })?;
90                }
91                fs::copy(&option.file_path, &dest).map_err(|error| {
92                    format!(
93                        "Failed to copy \"{}\" to \"{}\": {error}",
94                        option.file_path.display(),
95                        dest.display()
96                    )
97                })?;
98            }
99            AssetFileAction::Unlink => match fs::remove_file(&dest) {
100                Ok(()) => {}
101                Err(error) if error.kind() == std::io::ErrorKind::NotFound => {}
102                Err(error) => {
103                    return Err(format!("Failed to remove \"{}\": {error}", dest.display()));
104                }
105            },
106        }
107        Ok(Some(dest))
108    }
109
110    pub fn close_watchers(&mut self) -> usize {
111        let count = self.watchers.len();
112        self.watchers.clear();
113        self.action_in_progress = false;
114        count
115    }
116
117    pub fn watchers(&self) -> &[AssetWatchPlan] {
118        &self.watchers
119    }
120
121    pub fn action_in_progress(&self) -> bool {
122        self.action_in_progress
123    }
124}