diff --git a/CHANGELOG.md b/CHANGELOG.md index 0cc1c1312..a99bc115c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,6 @@ # Changelog +- **Added** `-s`/`--silent` flag to suppress `vp run`'s own output (command line, cache-status indicators, and summary), leaving only the tasks' output ([#429](https://github.com/voidzero-dev/vite-task/pull/429)) - **Added** A task's `env` and `untrackedEnv` glob patterns now support `!` negation: a `!`-prefixed pattern excludes matching variables (e.g. `["VITE_*", "!VITE_SECRET"]` tracks every `VITE_*` except `VITE_SECRET`) ([#425](https://github.com/voidzero-dev/vite-task/pull/425)) - **Fixed** `package.json` and `pnpm-workspace.yaml` files with a UTF-8 BOM no longer fail to parse ([#424](https://github.com/voidzero-dev/vite-task/pull/424)) - **Changed** `vp run --filter ` now exits 0 with a warning when the filter matches no packages, matching pnpm. Use `--fail-if-no-match` to restore the previous strict behavior ([#393](https://github.com/voidzero-dev/vite-task/pull/393)) diff --git a/crates/vite_task/src/cli/mod.rs b/crates/vite_task/src/cli/mod.rs index d17e38291..ee2c139d2 100644 --- a/crates/vite_task/src/cli/mod.rs +++ b/crates/vite_task/src/cli/mod.rs @@ -40,6 +40,11 @@ pub struct RunFlags { #[clap(default_value = "false", short = 'v', long)] pub verbose: bool, + /// Suppress the runner's own output: the per-task command line, cache-status + /// indicators, and the run summary. The tasks' own output is unaffected. + #[clap(default_value = "false", short = 's', long, conflicts_with = "verbose")] + pub silent: bool, + /// Force caching on for all tasks and scripts. #[clap(long, conflicts_with = "no_cache")] pub cache: bool, diff --git a/crates/vite_task/src/session/mod.rs b/crates/vite_task/src/session/mod.rs index 19711dd48..6fb71fded 100644 --- a/crates/vite_task/src/session/mod.rs +++ b/crates/vite_task/src/session/mod.rs @@ -263,6 +263,10 @@ impl<'a> Session<'a> { /// # Panics /// /// Panics if parsing a hardcoded bare `RunCommand` fails (should never happen). + #[expect( + clippy::too_many_lines, + reason = "single dispatch point that plans the command and wires up the reporter pipeline" + )] async fn main_inner(&mut self, command: Command) -> Result<(), SessionError> { match command.into_resolved() { ResolvedCommand::Cache { ref subcmd } => self.handle_cache_command(subcmd), @@ -335,6 +339,9 @@ impl<'a> Session<'a> { stderr: stderr_supports_color(), }; + // `--silent` suppresses the per-task command line and summary. + let silent = run_command.flags.silent; + let inner: Box = match run_command .flags .log @@ -343,16 +350,19 @@ impl<'a> Session<'a> { Arc::clone(&workspace_path), writer, color_support, + silent, )), crate::cli::LogMode::Labeled => Box::new(LabeledReporterBuilder::new( Arc::clone(&workspace_path), writer, color_support, + silent, )), crate::cli::LogMode::Grouped => Box::new(GroupedReporterBuilder::new( Arc::clone(&workspace_path), writer, color_support, + silent, )), }; @@ -364,6 +374,7 @@ impl<'a> Session<'a> { Some(self.make_summary_writer()), self.program_name.clone(), color_support, + silent, )); // Don't let SIGINT/CTRL_C kill the runner. Child tasks receive // the signal directly from the terminal driver and handle it diff --git a/crates/vite_task/src/session/reporter/grouped/mod.rs b/crates/vite_task/src/session/reporter/grouped/mod.rs index d3c508808..2c55e4fff 100644 --- a/crates/vite_task/src/session/reporter/grouped/mod.rs +++ b/crates/vite_task/src/session/reporter/grouped/mod.rs @@ -22,6 +22,7 @@ pub struct GroupedReporterBuilder { workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, } impl GroupedReporterBuilder { @@ -30,12 +31,18 @@ impl GroupedReporterBuilder { /// `LeafExecutionReporter::start`) strip ANSI on the way into the buffer, /// so by the time the buffer reaches `writer` it already matches the /// terminal's colour capability. `writer` is therefore stored unwrapped. + /// + /// `silent` suppresses the runner's chrome — the labeled command line / + /// cache indicator emitted in `LeafExecutionReporter::start` and the + /// `── [pkg#task] ──` block header — leaving just the buffered task output; + /// error banners are unaffected. pub fn new( workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, ) -> Self { - Self { workspace_path, writer, color_support } + Self { workspace_path, writer, color_support, silent } } } @@ -45,6 +52,7 @@ impl GraphExecutionReporterBuilder for GroupedReporterBuilder { writer: Rc::new(RefCell::new(self.writer)), workspace_path: self.workspace_path, color_support: self.color_support, + silent: self.silent, }) } } @@ -53,6 +61,7 @@ struct GroupedGraphReporter { writer: Rc>>, workspace_path: Arc, color_support: ColorSupport, + silent: bool, } impl GraphExecutionReporter for GroupedGraphReporter { @@ -70,6 +79,7 @@ impl GraphExecutionReporter for GroupedGraphReporter { started: false, grouped_buffer: None, color_support: self.color_support, + silent: self.silent, }) } @@ -88,20 +98,27 @@ struct GroupedLeafReporter { started: bool, grouped_buffer: Option>>>, color_support: ColorSupport, + silent: bool, } impl LeafExecutionReporter for GroupedLeafReporter { fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); - self.started = true; // Print labeled command line immediately (before output is buffered). - let labeled_line = vite_str::format!("{} {line}", self.label); - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(labeled_line.as_bytes()); - let _ = writer.flush(); + // `--silent` suppresses it; the buffered task output is still flushed + // in `finish`. + if !self.silent { + let line = format_command_with_cache_status( + &self.display, + &self.workspace_path, + &cache_status, + ); + let labeled_line = vite_str::format!("{} {line}", self.label); + let mut writer = self.writer.borrow_mut(); + let _ = writer.write_all(labeled_line.as_bytes()); + let _ = writer.flush(); + } // Create shared buffer for both stdout and stderr. let buffer = Rc::new(RefCell::new(Vec::new())); @@ -128,23 +145,26 @@ impl LeafExecutionReporter for GroupedLeafReporter { _cache_update_status: CacheUpdateStatus, error: Option, ) { - // Build grouped block: header + buffered output. + // Build grouped block: header + buffered output. `--silent` drops the + // `── [pkg#task] ──` block header, leaving just the task's own output. let mut extra = Vec::new(); if let Some(ref grouped_buffer) = self.grouped_buffer { let content = grouped_buffer.borrow(); if !content.is_empty() { - let header = vite_str::format!( - "{} {} {}\n", - "──".style(Style::new().bright_black()), - self.label, - "──".style(Style::new().bright_black()) - ); - extra.extend_from_slice(header.as_bytes()); + if !self.silent { + let header = vite_str::format!( + "{} {} {}\n", + "──".style(Style::new().bright_black()), + self.label, + "──".style(Style::new().bright_black()) + ); + extra.extend_from_slice(header.as_bytes()); + } extra.extend_from_slice(&content); } } - write_leaf_trailing_output(&self.writer, error, self.started, &extra); + write_leaf_trailing_output(&self.writer, error, self.started, self.silent, &extra); } } @@ -177,6 +197,7 @@ mod tests { test_path(), Box::new(std::io::sink()), ColorSupport::uniform(false), + false, )); let mut reporter = builder.build(); let mut leaf = reporter.new_leaf_execution(&item.execution_item_display, leaf_kind(item)); diff --git a/crates/vite_task/src/session/reporter/interleaved/mod.rs b/crates/vite_task/src/session/reporter/interleaved/mod.rs index 9722e95e0..bd81f7f6d 100644 --- a/crates/vite_task/src/session/reporter/interleaved/mod.rs +++ b/crates/vite_task/src/session/reporter/interleaved/mod.rs @@ -16,6 +16,7 @@ pub struct InterleavedReporterBuilder { workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, } impl InterleavedReporterBuilder { @@ -24,12 +25,17 @@ impl InterleavedReporterBuilder { /// stored unwrapped. `color_support` is forwarded to the pipe writers /// in `LeafExecutionReporter::start`, where ANSI emitted by child tasks is stripped /// for non-terminal sinks. + /// + /// `silent` suppresses the per-task command line / cache indicator emitted + /// in `LeafExecutionReporter::start`; task output and error banners are + /// unaffected. pub fn new( workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, ) -> Self { - Self { workspace_path, writer, color_support } + Self { workspace_path, writer, color_support, silent } } } @@ -39,6 +45,7 @@ impl GraphExecutionReporterBuilder for InterleavedReporterBuilder { writer: Rc::new(RefCell::new(self.writer)), workspace_path: self.workspace_path, color_support: self.color_support, + silent: self.silent, }) } } @@ -47,6 +54,7 @@ struct InterleavedGraphReporter { writer: Rc>>, workspace_path: Arc, color_support: ColorSupport, + silent: bool, } impl GraphExecutionReporter for InterleavedGraphReporter { @@ -67,6 +75,7 @@ impl GraphExecutionReporter for InterleavedGraphReporter { stdio_suggestion, started: false, color_support: self.color_support, + silent: self.silent, }) } @@ -84,18 +93,25 @@ struct InterleavedLeafReporter { stdio_suggestion: StdioSuggestion, started: bool, color_support: ColorSupport, + silent: bool, } impl LeafExecutionReporter for InterleavedLeafReporter { fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); - self.started = true; - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(line.as_bytes()); - let _ = writer.flush(); + // `--silent` suppresses the command line / cache indicator; the task's + // own output still streams through the pipe writers below. + if !self.silent { + let line = format_command_with_cache_status( + &self.display, + &self.workspace_path, + &cache_status, + ); + let mut writer = self.writer.borrow_mut(); + let _ = writer.write_all(line.as_bytes()); + let _ = writer.flush(); + } StdioConfig { suggestion: self.stdio_suggestion, @@ -118,7 +134,7 @@ impl LeafExecutionReporter for InterleavedLeafReporter { _cache_update_status: CacheUpdateStatus, error: Option, ) { - write_leaf_trailing_output(&self.writer, error, self.started, &[]); + write_leaf_trailing_output(&self.writer, error, self.started, self.silent, &[]); } } @@ -150,6 +166,7 @@ mod tests { test_path(), Box::new(std::io::sink()), ColorSupport::uniform(false), + false, )); let mut reporter = builder.build(); let mut leaf = reporter.new_leaf_execution(display, leaf_kind); diff --git a/crates/vite_task/src/session/reporter/labeled/mod.rs b/crates/vite_task/src/session/reporter/labeled/mod.rs index fa41fa641..2f888d1b3 100644 --- a/crates/vite_task/src/session/reporter/labeled/mod.rs +++ b/crates/vite_task/src/session/reporter/labeled/mod.rs @@ -21,18 +21,24 @@ pub struct LabeledReporterBuilder { workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, } impl LabeledReporterBuilder { /// `writer` is stored unwrapped — the reporter's own writes pick /// colour-vs-plain at format time via `ColorizeExt`. Child-process /// pipes are stripped per-stream inside `LeafExecutionReporter::start`. + /// + /// `silent` suppresses the labeled command line / cache indicator emitted + /// in `LeafExecutionReporter::start`; task output and error banners are + /// unaffected. pub fn new( workspace_path: Arc, writer: Box, color_support: ColorSupport, + silent: bool, ) -> Self { - Self { workspace_path, writer, color_support } + Self { workspace_path, writer, color_support, silent } } } @@ -42,6 +48,7 @@ impl GraphExecutionReporterBuilder for LabeledReporterBuilder { writer: Rc::new(RefCell::new(self.writer)), workspace_path: self.workspace_path, color_support: self.color_support, + silent: self.silent, }) } } @@ -50,6 +57,7 @@ struct LabeledGraphReporter { writer: Rc>>, workspace_path: Arc, color_support: ColorSupport, + silent: bool, } impl GraphExecutionReporter for LabeledGraphReporter { @@ -64,6 +72,7 @@ impl GraphExecutionReporter for LabeledGraphReporter { workspace_path: Arc::clone(&self.workspace_path), started: false, color_support: self.color_support, + silent: self.silent, }) } @@ -80,20 +89,28 @@ struct LabeledLeafReporter { workspace_path: Arc, started: bool, color_support: ColorSupport, + silent: bool, } impl LeafExecutionReporter for LabeledLeafReporter { fn start(&mut self, cache_status: CacheStatus) -> StdioConfig { let label = format_task_label(&self.display); - let line = - format_command_with_cache_status(&self.display, &self.workspace_path, &cache_status); self.started = true; - let labeled_line = vite_str::format!("{label} {line}"); - let mut writer = self.writer.borrow_mut(); - let _ = writer.write_all(labeled_line.as_bytes()); - let _ = writer.flush(); + // `--silent` suppresses the labeled command line / cache indicator; the + // task's own output still streams through the labeled pipe writers below. + if !self.silent { + let line = format_command_with_cache_status( + &self.display, + &self.workspace_path, + &cache_status, + ); + let labeled_line = vite_str::format!("{label} {line}"); + let mut writer = self.writer.borrow_mut(); + let _ = writer.write_all(labeled_line.as_bytes()); + let _ = writer.flush(); + } let prefix = vite_str::format!("{label} "); @@ -118,7 +135,7 @@ impl LeafExecutionReporter for LabeledLeafReporter { _cache_update_status: CacheUpdateStatus, error: Option, ) { - write_leaf_trailing_output(&self.writer, error, self.started, &[]); + write_leaf_trailing_output(&self.writer, error, self.started, self.silent, &[]); } } @@ -151,6 +168,7 @@ mod tests { test_path(), Box::new(std::io::sink()), ColorSupport::uniform(false), + false, )); let mut reporter = builder.build(); let mut leaf = reporter.new_leaf_execution(&item.execution_item_display, leaf_kind(item)); diff --git a/crates/vite_task/src/session/reporter/mod.rs b/crates/vite_task/src/session/reporter/mod.rs index 8f7bc37b4..0309465e4 100644 --- a/crates/vite_task/src/session/reporter/mod.rs +++ b/crates/vite_task/src/session/reporter/mod.rs @@ -310,10 +310,15 @@ fn format_error_message(message: &str) -> Str { /// Write the trailing output for a leaf execution: optional extra content (e.g., grouped /// output block), error message, and a separating newline. +/// +/// The separating newline pairs with the per-task command line printed in +/// `start`. Under `--silent` (no command line) it is skipped, so back-to-back +/// task output isn't padded with blank lines. fn write_leaf_trailing_output( writer: &std::cell::RefCell>, error: Option, started: bool, + silent: bool, extra: &[u8], ) { let mut buf = Vec::new(); @@ -325,7 +330,7 @@ fn write_leaf_trailing_output( buf.extend_from_slice(format_error_message(&message).as_bytes()); } - if started { + if started && !silent { buf.push(b'\n'); } diff --git a/crates/vite_task/src/session/reporter/summary_reporter.rs b/crates/vite_task/src/session/reporter/summary_reporter.rs index 789bc29a2..bfbcc4d27 100644 --- a/crates/vite_task/src/session/reporter/summary_reporter.rs +++ b/crates/vite_task/src/session/reporter/summary_reporter.rs @@ -33,6 +33,7 @@ pub struct SummaryReporterBuilder { show_details: bool, write_summary: Option, program_name: Str, + silent: bool, } impl SummaryReporterBuilder { @@ -40,6 +41,14 @@ impl SummaryReporterBuilder { /// owns per-stream stripping of the child-process pipe writers; the /// reporter's own summary text picks colour-vs-plain at format time /// via `ColorizeExt`, so `writer` is stored unwrapped. + /// + /// `silent` suppresses the run summary printed at finish. The summary is + /// still computed and persisted (so `--last-details` keeps working); only + /// the console output is skipped. + #[expect( + clippy::too_many_arguments, + reason = "mirrors the mode reporter builders' constructors; the silent flag is one more knob" + )] pub fn new( inner: Box, workspace_path: Arc, @@ -48,8 +57,9 @@ impl SummaryReporterBuilder { write_summary: Option, program_name: Str, _color_support: ColorSupport, + silent: bool, ) -> Self { - Self { inner, workspace_path, writer, show_details, write_summary, program_name } + Self { inner, workspace_path, writer, show_details, write_summary, program_name, silent } } } @@ -63,6 +73,7 @@ impl GraphExecutionReporterBuilder for SummaryReporterBuilder { show_details: self.show_details, write_summary: self.write_summary, program_name: self.program_name, + silent: self.silent, }) } } @@ -75,6 +86,7 @@ struct SummaryGraphReporter { show_details: bool, write_summary: Option, program_name: Str, + silent: bool, } impl GraphExecutionReporter for SummaryGraphReporter { @@ -131,7 +143,11 @@ impl GraphExecutionReporter for SummaryGraphReporter { let summary = LastRunSummary { tasks, exit_code }; - let summary_buf = if self.show_details { + // `--silent` suppresses the console summary; it is still persisted via + // `write_summary` below so `--last-details` continues to work. + let summary_buf = if self.silent { + Vec::new() + } else if self.show_details { format_full_summary(&summary) } else { format_compact_summary(&summary, &self.program_name) diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml index 3df60595f..f7a38f5fc 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots.toml @@ -132,3 +132,45 @@ steps = [ "--last-details", ], comment = "display saved summary" }, ] + +[[e2e]] +name = "single_task_silent_suppresses_command_line_and_summary" +comment = """ +`vt run --silent` on a cache hit should print only the task's own output — no command line, no cache-hit indicator, and no summary. +""" +cwd = "packages/a" +steps = [ + { argv = [ + "vt", + "run", + "build", + ], comment = "first run, populate cache" }, + { argv = [ + "vt", + "run", + "--silent", + "build", + ], comment = "second run, cache hit → only task output" }, +] + +[[e2e]] +name = "multi_task_silent_suppresses_command_lines_and_summary" +comment = """ +`vt run -r --silent` should suppress every per-task command line and the multi-task summary, leaving only the tasks' own output. +""" +steps = [["vt", "run", "-r", "--silent", "build"]] + +[[e2e]] +name = "silent_conflicts_with_verbose" +comment = """ +`--silent` and `--verbose` are mutually exclusive; combining them is a usage error. +""" +cwd = "packages/a" +steps = [["vt", "run", "--silent", "--verbose", "build"]] + +[[e2e]] +name = "multi_task_silent_grouped_suppresses_block_headers" +comment = """ +`vt run -r --silent --log grouped` should suppress the `── [pkg#task] ──` block headers that grouped mode normally prints, leaving only the tasks' own output. +""" +steps = [["vt", "run", "-r", "--silent", "--log", "grouped", "build"]] diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_grouped_suppresses_block_headers.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_grouped_suppresses_block_headers.md new file mode 100644 index 000000000..795eee80e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_grouped_suppresses_block_headers.md @@ -0,0 +1,10 @@ +# multi_task_silent_grouped_suppresses_block_headers + +`vt run -r --silent --log grouped` should suppress the `── [pkg#task] ──` block headers that grouped mode normally prints, leaving only the tasks' own output. + +## `vt run -r --silent --log grouped build` + +``` +built-a +built-b +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_suppresses_command_lines_and_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_suppresses_command_lines_and_summary.md new file mode 100644 index 000000000..0ac60c42e --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/multi_task_silent_suppresses_command_lines_and_summary.md @@ -0,0 +1,10 @@ +# multi_task_silent_suppresses_command_lines_and_summary + +`vt run -r --silent` should suppress every per-task command line and the multi-task summary, leaving only the tasks' own output. + +## `vt run -r --silent build` + +``` +built-a +built-b +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/silent_conflicts_with_verbose.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/silent_conflicts_with_verbose.md new file mode 100644 index 000000000..ce617f4eb --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/silent_conflicts_with_verbose.md @@ -0,0 +1,15 @@ +# silent_conflicts_with_verbose + +`--silent` and `--verbose` are mutually exclusive; combining them is a usage error. + +## `vt run --silent --verbose build` + +**Exit code:** 2 + +``` +error: the argument '--silent' cannot be used with '--verbose' + +Usage: vt run --silent ... + +For more information, try '--help'. +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_silent_suppresses_command_line_and_summary.md b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_silent_suppresses_command_line_and_summary.md new file mode 100644 index 000000000..5580c3ece --- /dev/null +++ b/crates/vite_task_bin/tests/e2e_snapshots/fixtures/summary_output/snapshots/single_task_silent_suppresses_command_line_and_summary.md @@ -0,0 +1,20 @@ +# single_task_silent_suppresses_command_line_and_summary + +`vt run --silent` on a cache hit should print only the task's own output — no command line, no cache-hit indicator, and no summary. + +## `vt run build` + +first run, populate cache + +``` +~/packages/a$ vtt print built-a +built-a +``` + +## `vt run --silent build` + +second run, cache hit → only task output + +``` +built-a +``` diff --git a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs index 0137a1275..2a9264ae0 100644 --- a/crates/vite_task_bin/tests/e2e_snapshots/redact.rs +++ b/crates/vite_task_bin/tests/e2e_snapshots/redact.rs @@ -64,6 +64,20 @@ pub fn redact_e2e_output(mut output: String, workspace_root: &str) -> String { redact_string(&mut output, &redactions); + // Strip the Windows `.exe` suffix from the runner binaries. clap derives the + // program name in usage/error strings from argv[0], so on Windows it emits + // `vt.exe`/`vtt.exe` where Unix emits `vt`/`vtt`. Dropping the extension keeps + // such snapshots platform-independent. No-op on Unix (no `.exe` to match). + { + use cow_utils::CowUtils as _; + for exe in ["vt.exe", "vtt.exe"] { + let stripped = exe.strip_suffix(".exe").unwrap(); + if let Cow::Owned(replaced) = output.as_str().cow_replace(exe, stripped) { + output = replaced; + } + } + } + // Redact UUIDs (e.g. cache archive filenames `.tar.zst`) to "" let uuid_regex = regex::Regex::new(r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}").unwrap();