1use crate::startup_context::StartupFileKind;
2use crate::util::eval_source;
3#[cfg(feature = "plugin")]
4use nu_path::absolute_with;
5use nu_protocol::report_shell_error;
6#[cfg(feature = "plugin")]
7use nu_protocol::shell_error::generic::GenericError;
8use nu_protocol::shell_error::io::IoError;
9#[cfg(feature = "plugin")]
10use nu_protocol::{ParseError, PluginRegistryFile, Span, engine::StateWorkingSet};
11use nu_protocol::{
12 PipelineData, ShellError,
13 engine::{EngineState, Stack},
14};
15#[cfg(feature = "plugin")]
16use nu_utils::perf;
17#[cfg(feature = "plugin")]
18use nu_utils::time::Instant;
19use std::path::PathBuf;
20
21#[cfg(feature = "plugin")]
22const PLUGIN_FILE: &str = "plugin.msgpackz";
23#[cfg(feature = "plugin")]
24const OLD_PLUGIN_FILE: &str = "plugin.nu";
25
26#[cfg(feature = "plugin")]
32pub fn read_plugin_file(engine_state: &mut EngineState, override_span: Option<Span>) {
33 use nu_protocol::{ShellError, shell_error::io::IoError};
34
35 let span = override_span;
36 let is_override = engine_state.config_dirs.plugin_file.is_override();
37
38 if engine_state
40 .config_dirs
41 .plugin_file
42 .as_path()
43 .extension()
44 .is_some_and(|ext| ext == "nu")
45 {
46 let error = "Wrong plugin file format";
47 let msg = ".nu plugin files are no longer supported";
48 report_shell_error(
49 None,
50 engine_state,
51 &ShellError::Generic(
52 match span {
53 Some(span) => GenericError::new(error, msg, span),
54 None => GenericError::new_internal(error, msg),
55 }
56 .with_help("please recreate this file in the new .msgpackz format"),
57 ),
58 );
59 return;
60 }
61
62 let mut start_time = Instant::now();
63 add_plugin_file(engine_state, override_span);
65 perf!(
66 "add plugin file to engine_state",
67 start_time,
68 engine_state
69 .get_config()
70 .use_ansi_coloring
71 .get(engine_state)
72 );
73
74 start_time = Instant::now();
75 let plugin_path = engine_state.plugin_path.clone();
76 if let Some(plugin_path) = plugin_path {
77 let mut file = match std::fs::File::open(&plugin_path) {
79 Ok(file) => file,
80 Err(err) => {
81 if err.kind() == std::io::ErrorKind::NotFound {
82 log::warn!("Plugin file not found: {}", plugin_path.display());
83
84 if !is_override && migrate_old_plugin_file(engine_state) {
86 let Ok(file) = std::fs::File::open(&plugin_path) else {
87 log::warn!("Failed to load newly migrated plugin file");
88 return;
89 };
90 file
91 } else {
92 return;
93 }
94 } else {
95 report_shell_error(
96 None,
97 engine_state,
98 &ShellError::Io(IoError::new_internal_with_path(
99 err,
100 "Could not open plugin registry file",
101 plugin_path,
102 )),
103 );
104 return;
105 }
106 }
107 };
108
109 if file.metadata().is_ok_and(|m| m.len() == 0) {
111 log::warn!(
112 "Not reading plugin file because it's empty: {}",
113 plugin_path.display()
114 );
115 return;
116 }
117
118 let contents = match PluginRegistryFile::read_from(&mut file, span) {
120 Ok(contents) => contents,
121 Err(err) => {
122 log::warn!("Failed to read plugin registry file: {err:?}");
123 let error = format!(
124 "Error while reading plugin registry file: {}",
125 plugin_path.display()
126 );
127 let msg = "plugin path defined here";
128 report_shell_error(
129 None,
130 engine_state,
131 &ShellError::Generic(
132 match span {
133 Some(span) => GenericError::new(error, msg, span),
134 None => GenericError::new_internal(error, msg),
135 }
136 .with_help(
137 "you might try deleting the file and registering all of your plugins again",
138 ),
139 ),
140 );
141 return;
142 }
143 };
144
145 perf!(
146 &format!("read plugin file {}", plugin_path.display()),
147 start_time,
148 engine_state
149 .get_config()
150 .use_ansi_coloring
151 .get(engine_state)
152 );
153 start_time = Instant::now();
154
155 let mut working_set = StateWorkingSet::new(engine_state);
156
157 let plugin_load_errors =
158 nu_plugin_engine::load_plugin_file(&mut working_set, &contents, span);
159
160 if plugin_load_errors > 0 {
161 let error = format!(
162 "Failed to load {plugin_load_errors} plugin entr{} from {}",
163 if plugin_load_errors == 1 { "y" } else { "ies" },
164 plugin_path.display(),
165 );
166 let msg = "plugins with incompatible or invalid registry data were skipped";
167 let help = "run `plugin list` and re-add outdated plugins with `plugin add`";
168 let generic_error = match span {
169 Some(span) => GenericError::new(error, msg, span),
170 None => GenericError::new_internal(error, msg),
171 };
172 report_shell_error(
173 None,
174 engine_state,
175 &ShellError::Generic(generic_error.with_help(help)),
176 );
177 }
178
179 if let Err(err) = engine_state.merge_delta(working_set.render()) {
180 report_shell_error(None, engine_state, &err);
181 return;
182 }
183
184 perf!(
185 &format!("load plugin file {}", plugin_path.display()),
186 start_time,
187 engine_state
188 .get_config()
189 .use_ansi_coloring
190 .get(engine_state)
191 );
192 }
193}
194
195#[cfg(feature = "plugin")]
200pub fn add_plugin_file(engine_state: &mut EngineState, override_span: Option<Span>) {
201 use std::path::Path;
202
203 use nu_protocol::report_parse_error;
204
205 let plugin_path = engine_state.config_dirs.plugin_file.to_path_buf();
206 if plugin_path.as_os_str().is_empty() {
207 return;
208 }
209
210 let Ok(cwd) = engine_state.cwd_as_string(None) else {
211 return;
212 };
213
214 if engine_state.config_dirs.plugin_file.is_override() {
215 let path = Path::new(&plugin_path);
216 let path_dir = path.parent().unwrap_or(path);
217 if let Ok(path_dir) = absolute_with(path_dir, &cwd)
218 && path_dir.exists()
219 {
220 let path = path_dir.join(path.file_name().unwrap_or(path.as_os_str()));
221 let path = absolute_with(&path, &cwd).unwrap_or(path);
222 engine_state.plugin_path = Some(path);
223 } else {
224 report_parse_error(
225 None,
226 &StateWorkingSet::new(engine_state),
227 &ParseError::FileNotFound(
228 path_dir.to_string_lossy().into_owned(),
229 override_span.unwrap_or_else(Span::unknown),
230 ),
231 );
232 }
233 } else {
234 let plugin_path = absolute_with(&plugin_path, &cwd).unwrap_or(plugin_path);
236 engine_state.plugin_path = Some(plugin_path);
237 }
238}
239
240pub fn eval_config_contents(
241 config_path: PathBuf,
242 engine_state: &mut EngineState,
243 stack: &mut Stack,
244 strict_mode: bool,
245) {
246 eval_config_contents_with_kind(
247 config_path,
248 engine_state,
249 stack,
250 strict_mode,
251 StartupFileKind::Config,
252 )
253}
254
255pub fn eval_config_contents_with_kind(
262 config_path: PathBuf,
263 engine_state: &mut EngineState,
264 stack: &mut Stack,
265 strict_mode: bool,
266 kind: StartupFileKind,
267) {
268 if config_path.exists() & config_path.is_file() {
269 let config_filename = config_path.to_string_lossy();
270
271 match std::fs::read(&config_path) {
272 Ok(contents) => {
273 let prev_file = engine_state.file.take();
275 engine_state.file = Some(config_path.clone());
276
277 let exit_code = eval_source(
278 engine_state,
279 stack,
280 &contents,
281 &config_filename,
282 PipelineData::empty(),
283 false,
284 );
285 if exit_code != 0 && strict_mode {
286 std::process::exit(exit_code)
287 }
288
289 engine_state.file = prev_file;
291
292 if let Err(e) = engine_state.merge_env(stack) {
294 report_shell_error(Some(stack), engine_state, &e);
295 }
296 }
297 Err(err) => {
298 let mut io_err = IoError::new_internal_with_path(
301 err,
302 format!("Could not read {}", kind.display_name()),
303 config_path.clone(),
304 );
305 io_err.location = None;
307 let shell_err = ShellError::Io(io_err);
308 report_shell_error(None, engine_state, &shell_err);
309 if strict_mode {
310 std::process::exit(shell_err.exit_code().unwrap_or(1));
311 }
312 }
313 }
314 }
315}
316
317#[cfg(feature = "plugin")]
318pub fn migrate_old_plugin_file(engine_state: &EngineState) -> bool {
319 use nu_protocol::{
320 PluginExample, PluginIdentity, PluginRegistryItem, PluginRegistryItemData, PluginSignature,
321 ShellError, shell_error::io::IoError,
322 };
323 use std::collections::BTreeMap;
324
325 let start_time = Instant::now();
326
327 let config_dir = &engine_state.config_dirs.config_home;
328 if config_dir.as_os_str().is_empty() {
329 return false;
330 }
331
332 let Ok(old_plugin_file_path) = nu_path::absolute_with(OLD_PLUGIN_FILE, config_dir) else {
333 return false;
334 };
335
336 if !config_dir.exists() || !old_plugin_file_path.exists() {
337 return false;
338 }
339
340 let old_contents = match std::fs::read(&old_plugin_file_path) {
341 Ok(old_contents) => old_contents,
342 Err(err) => {
343 report_shell_error(
344 None,
345 engine_state,
346 &ShellError::Generic(
347 GenericError::new_internal("Can't read old plugin file to migrate", "")
348 .with_help(err.to_string()),
349 ),
350 );
351 return false;
352 }
353 };
354
355 let mut engine_state = engine_state.clone();
357 let mut stack = Stack::new();
358
359 if eval_source(
360 &mut engine_state,
361 &mut stack,
362 &old_contents,
363 &old_plugin_file_path.to_string_lossy(),
364 PipelineData::empty(),
365 false,
366 ) != 0
367 {
368 return false;
369 }
370
371 let mut contents = PluginRegistryFile::new();
373
374 let mut groups = BTreeMap::<PluginIdentity, Vec<PluginSignature>>::new();
375
376 for decl in engine_state.plugin_decls() {
377 if let Some(identity) = decl.plugin_identity() {
378 groups
379 .entry(identity.clone())
380 .or_default()
381 .push(PluginSignature {
382 sig: decl.signature(),
383 examples: decl
384 .examples()
385 .into_iter()
386 .map(PluginExample::from)
387 .collect(),
388 })
389 }
390 }
391
392 for (identity, commands) in groups {
393 contents.upsert_plugin(PluginRegistryItem {
394 name: identity.name().to_owned(),
395 filename: identity.filename().to_owned(),
396 shell: identity.shell().map(|p| p.to_owned()),
397 data: PluginRegistryItemData::Valid {
398 metadata: Default::default(),
399 commands,
400 },
401 });
402 }
403
404 let new_plugin_file_path = config_dir.join(PLUGIN_FILE);
406 if let Err(err) = std::fs::File::create(&new_plugin_file_path)
407 .map_err(|err| {
408 IoError::new_internal_with_path(
409 err,
410 "Could not create new plugin file",
411 new_plugin_file_path.clone(),
412 )
413 })
414 .map_err(ShellError::from)
415 .and_then(|file| contents.write_to(file, None))
416 {
417 report_shell_error(
418 None,
419 &engine_state,
420 &ShellError::Generic(
421 GenericError::new_internal("Failed to save migrated plugin file", "")
422 .with_help("ensure `$nu.plugin-path` is writable")
423 .with_inner([err]),
424 ),
425 );
426 return false;
427 }
428
429 if engine_state.is_interactive {
430 eprintln!(
431 "Your old plugin.nu file has been migrated to the new format: {}",
432 new_plugin_file_path.display()
433 );
434 eprintln!(
435 "The plugin.nu file has not been removed. If `plugin list` looks okay, \
436 you may do so manually."
437 );
438 }
439
440 perf!(
441 "migrate old plugin file",
442 start_time,
443 engine_state
444 .get_config()
445 .use_ansi_coloring
446 .get(&engine_state)
447 );
448 true
449}