1use crate::compress::generic::GenericCompressor;
2use crate::compress::{CompressionResult, Compressor, Specificity};
3
4pub struct PnpmCompressor;
5
6impl Compressor for PnpmCompressor {
7 fn specificity(&self) -> Specificity {
8 Specificity::PackageManager
9 }
10
11 fn matches(&self, command: &str) -> bool {
12 command
13 .split_whitespace()
14 .next()
15 .is_some_and(|head| head == "pnpm")
16 }
17
18 fn compress_with_exit_code(
19 &self,
20 command: &str,
21 output: &str,
22 _exit_code: Option<i32>,
23 ) -> CompressionResult {
24 match pnpm_subcommand(command).as_deref() {
25 Some("install" | "i" | "add" | "remove") => compress_package(output).into(),
26 Some("run" | "test" | "build") => GenericCompressor::compress_output(output).into(),
27 _ => GenericCompressor::compress_output(output).into(),
28 }
29 }
30}
31
32const PNPM_SUBCOMMANDS: &[&str] = &[
37 "install",
38 "i",
39 "add",
40 "remove",
41 "rm",
42 "uninstall",
43 "un",
44 "update",
45 "up",
46 "upgrade",
47 "outdated",
48 "audit",
49 "outdated-of",
50 "publish",
51 "pack",
52 "run",
53 "test",
54 "t",
55 "exec",
56 "x",
57 "dlx",
58 "create",
59 "init",
60 "build",
61 "start",
62 "link",
63 "unlink",
64 "view",
65 "info",
66 "show",
67 "config",
68 "help",
69 "version",
70 "ls",
71 "list",
72 "list-modules",
73 "list-bin",
74 "ping",
75 "whoami",
76 "login",
77 "logout",
78 "deploy",
79 "dedupe",
80 "fetch",
81 "import",
82 "patch",
83 "patch-commit",
84 "patch-remove",
85 "prune",
86 "rebuild",
87 "recursive",
88 "root",
89 "store",
90 "why",
91 "doctor",
92 "env",
93 "server",
94 "setup",
95];
96
97fn pnpm_subcommand(command: &str) -> Option<String> {
98 command
99 .split_whitespace()
100 .skip_while(|token| *token != "pnpm")
101 .skip(1)
102 .find(|token| PNPM_SUBCOMMANDS.contains(token))
103 .map(ToString::to_string)
104}
105
106fn compress_package(output: &str) -> String {
107 let mut result = Vec::new();
108 let mut progress_seen = 0usize;
109 let mut up_to_date_seen = false;
110
111 for line in output.lines() {
112 let trimmed = line.trim_start();
113 if trimmed.starts_with("Progress: resolved ") {
114 progress_seen += 1;
115 if progress_seen > 2 {
116 continue;
117 }
118 }
119 if trimmed == "Already up-to-date" {
120 if up_to_date_seen {
121 continue;
122 }
123 up_to_date_seen = true;
124 }
125 if trimmed.contains("WARN GET_NO_AUTH")
126 || trimmed.starts_with("ERR_PNPM_")
127 || trimmed.starts_with("Progress: resolved ")
128 || trimmed == "Already up-to-date"
129 || trimmed.starts_with("dependencies:")
130 || trimmed.starts_with("devDependencies:")
131 || trimmed.starts_with("Done in ")
132 {
133 result.push(line.to_string());
134 }
135 }
136
137 trim_trailing_lines(&result.join("\n"))
138}
139
140fn trim_trailing_lines(input: &str) -> String {
141 input
142 .lines()
143 .map(str::trim_end)
144 .collect::<Vec<_>>()
145 .join("\n")
146}