@hasna/terminal 2.3.0 → 2.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/dist/cli.js +64 -16
  2. package/package.json +1 -1
  3. package/src/ai.ts +8 -0
  4. package/src/cli.tsx +57 -18
  5. package/src/output-processor.ts +6 -1
  6. package/src/output-store.ts +58 -12
  7. package/src/tool-profiles.ts +139 -0
  8. package/temp/rtk/.claude/agents/code-reviewer.md +0 -221
  9. package/temp/rtk/.claude/agents/debugger.md +0 -519
  10. package/temp/rtk/.claude/agents/rtk-testing-specialist.md +0 -461
  11. package/temp/rtk/.claude/agents/rust-rtk.md +0 -511
  12. package/temp/rtk/.claude/agents/technical-writer.md +0 -355
  13. package/temp/rtk/.claude/commands/diagnose.md +0 -352
  14. package/temp/rtk/.claude/commands/test-routing.md +0 -362
  15. package/temp/rtk/.claude/hooks/bash/pre-commit-format.sh +0 -16
  16. package/temp/rtk/.claude/hooks/rtk-rewrite.sh +0 -70
  17. package/temp/rtk/.claude/hooks/rtk-suggest.sh +0 -152
  18. package/temp/rtk/.claude/rules/cli-testing.md +0 -526
  19. package/temp/rtk/.claude/skills/issue-triage/SKILL.md +0 -348
  20. package/temp/rtk/.claude/skills/issue-triage/templates/issue-comment.md +0 -134
  21. package/temp/rtk/.claude/skills/performance.md +0 -435
  22. package/temp/rtk/.claude/skills/pr-triage/SKILL.md +0 -315
  23. package/temp/rtk/.claude/skills/pr-triage/templates/review-comment.md +0 -71
  24. package/temp/rtk/.claude/skills/repo-recap.md +0 -206
  25. package/temp/rtk/.claude/skills/rtk-tdd/SKILL.md +0 -78
  26. package/temp/rtk/.claude/skills/rtk-tdd/references/testing-patterns.md +0 -124
  27. package/temp/rtk/.claude/skills/security-guardian.md +0 -503
  28. package/temp/rtk/.claude/skills/ship.md +0 -404
  29. package/temp/rtk/.github/workflows/benchmark.yml +0 -34
  30. package/temp/rtk/.github/workflows/dco-check.yaml +0 -12
  31. package/temp/rtk/.github/workflows/release-please.yml +0 -51
  32. package/temp/rtk/.github/workflows/release.yml +0 -343
  33. package/temp/rtk/.github/workflows/security-check.yml +0 -135
  34. package/temp/rtk/.github/workflows/validate-docs.yml +0 -78
  35. package/temp/rtk/.release-please-manifest.json +0 -3
  36. package/temp/rtk/ARCHITECTURE.md +0 -1491
  37. package/temp/rtk/CHANGELOG.md +0 -640
  38. package/temp/rtk/CLAUDE.md +0 -605
  39. package/temp/rtk/CONTRIBUTING.md +0 -199
  40. package/temp/rtk/Cargo.lock +0 -1668
  41. package/temp/rtk/Cargo.toml +0 -64
  42. package/temp/rtk/Formula/rtk.rb +0 -43
  43. package/temp/rtk/INSTALL.md +0 -390
  44. package/temp/rtk/LICENSE +0 -21
  45. package/temp/rtk/README.md +0 -386
  46. package/temp/rtk/README_es.md +0 -159
  47. package/temp/rtk/README_fr.md +0 -197
  48. package/temp/rtk/README_ja.md +0 -159
  49. package/temp/rtk/README_ko.md +0 -159
  50. package/temp/rtk/README_zh.md +0 -167
  51. package/temp/rtk/ROADMAP.md +0 -15
  52. package/temp/rtk/SECURITY.md +0 -217
  53. package/temp/rtk/TEST_EXEC_TIME.md +0 -102
  54. package/temp/rtk/build.rs +0 -57
  55. package/temp/rtk/docs/AUDIT_GUIDE.md +0 -432
  56. package/temp/rtk/docs/FEATURES.md +0 -1410
  57. package/temp/rtk/docs/TROUBLESHOOTING.md +0 -309
  58. package/temp/rtk/docs/filter-workflow.md +0 -102
  59. package/temp/rtk/docs/images/gain-dashboard.jpg +0 -0
  60. package/temp/rtk/docs/tracking.md +0 -583
  61. package/temp/rtk/hooks/opencode-rtk.ts +0 -39
  62. package/temp/rtk/hooks/rtk-awareness.md +0 -29
  63. package/temp/rtk/hooks/rtk-rewrite.sh +0 -61
  64. package/temp/rtk/hooks/test-rtk-rewrite.sh +0 -442
  65. package/temp/rtk/install.sh +0 -124
  66. package/temp/rtk/release-please-config.json +0 -10
  67. package/temp/rtk/scripts/benchmark.sh +0 -592
  68. package/temp/rtk/scripts/check-installation.sh +0 -162
  69. package/temp/rtk/scripts/install-local.sh +0 -37
  70. package/temp/rtk/scripts/rtk-economics.sh +0 -137
  71. package/temp/rtk/scripts/test-all.sh +0 -561
  72. package/temp/rtk/scripts/test-aristote.sh +0 -227
  73. package/temp/rtk/scripts/test-tracking.sh +0 -79
  74. package/temp/rtk/scripts/update-readme-metrics.sh +0 -32
  75. package/temp/rtk/scripts/validate-docs.sh +0 -73
  76. package/temp/rtk/src/aws_cmd.rs +0 -880
  77. package/temp/rtk/src/binlog.rs +0 -1645
  78. package/temp/rtk/src/cargo_cmd.rs +0 -1727
  79. package/temp/rtk/src/cc_economics.rs +0 -1157
  80. package/temp/rtk/src/ccusage.rs +0 -340
  81. package/temp/rtk/src/config.rs +0 -187
  82. package/temp/rtk/src/container.rs +0 -855
  83. package/temp/rtk/src/curl_cmd.rs +0 -134
  84. package/temp/rtk/src/deps.rs +0 -268
  85. package/temp/rtk/src/diff_cmd.rs +0 -367
  86. package/temp/rtk/src/discover/mod.rs +0 -274
  87. package/temp/rtk/src/discover/provider.rs +0 -388
  88. package/temp/rtk/src/discover/registry.rs +0 -2022
  89. package/temp/rtk/src/discover/report.rs +0 -202
  90. package/temp/rtk/src/discover/rules.rs +0 -667
  91. package/temp/rtk/src/display_helpers.rs +0 -402
  92. package/temp/rtk/src/dotnet_cmd.rs +0 -1771
  93. package/temp/rtk/src/dotnet_format_report.rs +0 -133
  94. package/temp/rtk/src/dotnet_trx.rs +0 -593
  95. package/temp/rtk/src/env_cmd.rs +0 -204
  96. package/temp/rtk/src/filter.rs +0 -462
  97. package/temp/rtk/src/filters/README.md +0 -52
  98. package/temp/rtk/src/filters/ansible-playbook.toml +0 -34
  99. package/temp/rtk/src/filters/basedpyright.toml +0 -47
  100. package/temp/rtk/src/filters/biome.toml +0 -45
  101. package/temp/rtk/src/filters/brew-install.toml +0 -37
  102. package/temp/rtk/src/filters/composer-install.toml +0 -40
  103. package/temp/rtk/src/filters/df.toml +0 -16
  104. package/temp/rtk/src/filters/dotnet-build.toml +0 -64
  105. package/temp/rtk/src/filters/du.toml +0 -16
  106. package/temp/rtk/src/filters/fail2ban-client.toml +0 -15
  107. package/temp/rtk/src/filters/gcc.toml +0 -49
  108. package/temp/rtk/src/filters/gcloud.toml +0 -22
  109. package/temp/rtk/src/filters/hadolint.toml +0 -24
  110. package/temp/rtk/src/filters/helm.toml +0 -29
  111. package/temp/rtk/src/filters/iptables.toml +0 -27
  112. package/temp/rtk/src/filters/jj.toml +0 -28
  113. package/temp/rtk/src/filters/jq.toml +0 -24
  114. package/temp/rtk/src/filters/make.toml +0 -41
  115. package/temp/rtk/src/filters/markdownlint.toml +0 -24
  116. package/temp/rtk/src/filters/mix-compile.toml +0 -27
  117. package/temp/rtk/src/filters/mix-format.toml +0 -15
  118. package/temp/rtk/src/filters/mvn-build.toml +0 -44
  119. package/temp/rtk/src/filters/oxlint.toml +0 -43
  120. package/temp/rtk/src/filters/ping.toml +0 -63
  121. package/temp/rtk/src/filters/pio-run.toml +0 -40
  122. package/temp/rtk/src/filters/poetry-install.toml +0 -50
  123. package/temp/rtk/src/filters/pre-commit.toml +0 -35
  124. package/temp/rtk/src/filters/ps.toml +0 -16
  125. package/temp/rtk/src/filters/quarto-render.toml +0 -41
  126. package/temp/rtk/src/filters/rsync.toml +0 -48
  127. package/temp/rtk/src/filters/shellcheck.toml +0 -27
  128. package/temp/rtk/src/filters/shopify-theme.toml +0 -29
  129. package/temp/rtk/src/filters/skopeo.toml +0 -45
  130. package/temp/rtk/src/filters/sops.toml +0 -16
  131. package/temp/rtk/src/filters/ssh.toml +0 -44
  132. package/temp/rtk/src/filters/stat.toml +0 -34
  133. package/temp/rtk/src/filters/swift-build.toml +0 -41
  134. package/temp/rtk/src/filters/systemctl-status.toml +0 -33
  135. package/temp/rtk/src/filters/terraform-plan.toml +0 -35
  136. package/temp/rtk/src/filters/tofu-fmt.toml +0 -16
  137. package/temp/rtk/src/filters/tofu-init.toml +0 -38
  138. package/temp/rtk/src/filters/tofu-plan.toml +0 -35
  139. package/temp/rtk/src/filters/tofu-validate.toml +0 -17
  140. package/temp/rtk/src/filters/trunk-build.toml +0 -39
  141. package/temp/rtk/src/filters/ty.toml +0 -50
  142. package/temp/rtk/src/filters/uv-sync.toml +0 -37
  143. package/temp/rtk/src/filters/xcodebuild.toml +0 -99
  144. package/temp/rtk/src/filters/yamllint.toml +0 -25
  145. package/temp/rtk/src/find_cmd.rs +0 -598
  146. package/temp/rtk/src/format_cmd.rs +0 -386
  147. package/temp/rtk/src/gain.rs +0 -723
  148. package/temp/rtk/src/gh_cmd.rs +0 -1651
  149. package/temp/rtk/src/git.rs +0 -2012
  150. package/temp/rtk/src/go_cmd.rs +0 -592
  151. package/temp/rtk/src/golangci_cmd.rs +0 -254
  152. package/temp/rtk/src/grep_cmd.rs +0 -288
  153. package/temp/rtk/src/gt_cmd.rs +0 -810
  154. package/temp/rtk/src/hook_audit_cmd.rs +0 -283
  155. package/temp/rtk/src/hook_check.rs +0 -171
  156. package/temp/rtk/src/init.rs +0 -1859
  157. package/temp/rtk/src/integrity.rs +0 -537
  158. package/temp/rtk/src/json_cmd.rs +0 -231
  159. package/temp/rtk/src/learn/detector.rs +0 -628
  160. package/temp/rtk/src/learn/mod.rs +0 -119
  161. package/temp/rtk/src/learn/report.rs +0 -184
  162. package/temp/rtk/src/lint_cmd.rs +0 -694
  163. package/temp/rtk/src/local_llm.rs +0 -316
  164. package/temp/rtk/src/log_cmd.rs +0 -248
  165. package/temp/rtk/src/ls.rs +0 -324
  166. package/temp/rtk/src/main.rs +0 -2482
  167. package/temp/rtk/src/mypy_cmd.rs +0 -389
  168. package/temp/rtk/src/next_cmd.rs +0 -241
  169. package/temp/rtk/src/npm_cmd.rs +0 -236
  170. package/temp/rtk/src/parser/README.md +0 -267
  171. package/temp/rtk/src/parser/error.rs +0 -46
  172. package/temp/rtk/src/parser/formatter.rs +0 -336
  173. package/temp/rtk/src/parser/mod.rs +0 -311
  174. package/temp/rtk/src/parser/types.rs +0 -119
  175. package/temp/rtk/src/pip_cmd.rs +0 -302
  176. package/temp/rtk/src/playwright_cmd.rs +0 -479
  177. package/temp/rtk/src/pnpm_cmd.rs +0 -573
  178. package/temp/rtk/src/prettier_cmd.rs +0 -221
  179. package/temp/rtk/src/prisma_cmd.rs +0 -482
  180. package/temp/rtk/src/psql_cmd.rs +0 -382
  181. package/temp/rtk/src/pytest_cmd.rs +0 -384
  182. package/temp/rtk/src/read.rs +0 -217
  183. package/temp/rtk/src/rewrite_cmd.rs +0 -50
  184. package/temp/rtk/src/ruff_cmd.rs +0 -402
  185. package/temp/rtk/src/runner.rs +0 -271
  186. package/temp/rtk/src/summary.rs +0 -297
  187. package/temp/rtk/src/tee.rs +0 -405
  188. package/temp/rtk/src/telemetry.rs +0 -248
  189. package/temp/rtk/src/toml_filter.rs +0 -1655
  190. package/temp/rtk/src/tracking.rs +0 -1416
  191. package/temp/rtk/src/tree.rs +0 -209
  192. package/temp/rtk/src/tsc_cmd.rs +0 -259
  193. package/temp/rtk/src/utils.rs +0 -432
  194. package/temp/rtk/src/verify_cmd.rs +0 -47
  195. package/temp/rtk/src/vitest_cmd.rs +0 -385
  196. package/temp/rtk/src/wc_cmd.rs +0 -401
  197. package/temp/rtk/src/wget_cmd.rs +0 -260
  198. package/temp/rtk/tests/fixtures/dotnet/build_failed.txt +0 -11
  199. package/temp/rtk/tests/fixtures/dotnet/format_changes.json +0 -31
  200. package/temp/rtk/tests/fixtures/dotnet/format_empty.json +0 -1
  201. package/temp/rtk/tests/fixtures/dotnet/format_success.json +0 -12
  202. package/temp/rtk/tests/fixtures/dotnet/test_failed.txt +0 -18
@@ -1,573 +0,0 @@
1
- use crate::tracking;
2
- use anyhow::{Context, Result};
3
- use serde::Deserialize;
4
- use std::collections::HashMap;
5
- use std::ffi::OsString;
6
- use std::process::Command;
7
-
8
- use crate::parser::{
9
- emit_degradation_warning, emit_passthrough_warning, truncate_output, Dependency,
10
- DependencyState, FormatMode, OutputParser, ParseResult, TokenFormatter,
11
- };
12
-
13
- /// pnpm list JSON output structure
14
- #[derive(Debug, Deserialize)]
15
- struct PnpmListOutput {
16
- #[serde(flatten)]
17
- packages: HashMap<String, PnpmPackage>,
18
- }
19
-
20
- #[derive(Debug, Deserialize)]
21
- struct PnpmPackage {
22
- version: Option<String>,
23
- #[serde(rename = "dependencies", default)]
24
- dependencies: HashMap<String, PnpmPackage>,
25
- #[serde(rename = "devDependencies", default)]
26
- dev_dependencies: HashMap<String, PnpmPackage>,
27
- }
28
-
29
- /// pnpm outdated JSON output structure
30
- #[derive(Debug, Deserialize)]
31
- struct PnpmOutdatedOutput {
32
- #[serde(flatten)]
33
- packages: HashMap<String, PnpmOutdatedPackage>,
34
- }
35
-
36
- #[derive(Debug, Deserialize)]
37
- struct PnpmOutdatedPackage {
38
- current: String,
39
- latest: String,
40
- wanted: Option<String>,
41
- #[serde(rename = "dependencyType", default)]
42
- dependency_type: String,
43
- }
44
-
45
- /// Parser for pnpm list output
46
- pub struct PnpmListParser;
47
-
48
- impl OutputParser for PnpmListParser {
49
- type Output = DependencyState;
50
-
51
- fn parse(input: &str) -> ParseResult<DependencyState> {
52
- // Tier 1: Try JSON parsing
53
- match serde_json::from_str::<PnpmListOutput>(input) {
54
- Ok(json) => {
55
- let mut dependencies = Vec::new();
56
- let mut total_count = 0;
57
-
58
- for (name, pkg) in &json.packages {
59
- collect_dependencies(name, pkg, false, &mut dependencies, &mut total_count);
60
- }
61
-
62
- let result = DependencyState {
63
- total_packages: total_count,
64
- outdated_count: 0, // list doesn't provide outdated info
65
- dependencies,
66
- };
67
-
68
- ParseResult::Full(result)
69
- }
70
- Err(e) => {
71
- // Tier 2: Try text extraction
72
- match extract_list_text(input) {
73
- Some(result) => {
74
- ParseResult::Degraded(result, vec![format!("JSON parse failed: {}", e)])
75
- }
76
- None => {
77
- // Tier 3: Passthrough
78
- ParseResult::Passthrough(truncate_output(input, 500))
79
- }
80
- }
81
- }
82
- }
83
- }
84
- }
85
-
86
- /// Recursively collect dependencies from pnpm package tree
87
- fn collect_dependencies(
88
- name: &str,
89
- pkg: &PnpmPackage,
90
- is_dev: bool,
91
- deps: &mut Vec<Dependency>,
92
- count: &mut usize,
93
- ) {
94
- if let Some(version) = &pkg.version {
95
- deps.push(Dependency {
96
- name: name.to_string(),
97
- current_version: version.clone(),
98
- latest_version: None,
99
- wanted_version: None,
100
- dev_dependency: is_dev,
101
- });
102
- *count += 1;
103
- }
104
-
105
- for (dep_name, dep_pkg) in &pkg.dependencies {
106
- collect_dependencies(dep_name, dep_pkg, is_dev, deps, count);
107
- }
108
-
109
- for (dep_name, dep_pkg) in &pkg.dev_dependencies {
110
- collect_dependencies(dep_name, dep_pkg, true, deps, count);
111
- }
112
- }
113
-
114
- /// Tier 2: Extract list info from text output
115
- fn extract_list_text(output: &str) -> Option<DependencyState> {
116
- let mut dependencies = Vec::new();
117
- let mut count = 0;
118
-
119
- for line in output.lines() {
120
- // Skip box-drawing and metadata
121
- if line.contains('│')
122
- || line.contains('├')
123
- || line.contains('└')
124
- || line.contains("Legend:")
125
- || line.trim().is_empty()
126
- {
127
- continue;
128
- }
129
-
130
- // Parse lines like: "package@1.2.3"
131
- let parts: Vec<&str> = line.split_whitespace().collect();
132
- if !parts.is_empty() {
133
- let pkg_str = parts[0];
134
- if let Some(at_pos) = pkg_str.rfind('@') {
135
- let name = &pkg_str[..at_pos];
136
- let version = &pkg_str[at_pos + 1..];
137
- if !name.is_empty() && !version.is_empty() {
138
- dependencies.push(Dependency {
139
- name: name.to_string(),
140
- current_version: version.to_string(),
141
- latest_version: None,
142
- wanted_version: None,
143
- dev_dependency: false,
144
- });
145
- count += 1;
146
- }
147
- }
148
- }
149
- }
150
-
151
- if count > 0 {
152
- Some(DependencyState {
153
- total_packages: count,
154
- outdated_count: 0,
155
- dependencies,
156
- })
157
- } else {
158
- None
159
- }
160
- }
161
-
162
- /// Parser for pnpm outdated output
163
- pub struct PnpmOutdatedParser;
164
-
165
- impl OutputParser for PnpmOutdatedParser {
166
- type Output = DependencyState;
167
-
168
- fn parse(input: &str) -> ParseResult<DependencyState> {
169
- // Tier 1: Try JSON parsing
170
- match serde_json::from_str::<PnpmOutdatedOutput>(input) {
171
- Ok(json) => {
172
- let mut dependencies = Vec::new();
173
- let mut outdated_count = 0;
174
-
175
- for (name, pkg) in &json.packages {
176
- if pkg.current != pkg.latest {
177
- outdated_count += 1;
178
- }
179
-
180
- dependencies.push(Dependency {
181
- name: name.clone(),
182
- current_version: pkg.current.clone(),
183
- latest_version: Some(pkg.latest.clone()),
184
- wanted_version: pkg.wanted.clone(),
185
- dev_dependency: pkg.dependency_type == "devDependencies",
186
- });
187
- }
188
-
189
- let result = DependencyState {
190
- total_packages: dependencies.len(),
191
- outdated_count,
192
- dependencies,
193
- };
194
-
195
- ParseResult::Full(result)
196
- }
197
- Err(e) => {
198
- // Tier 2: Try text extraction
199
- match extract_outdated_text(input) {
200
- Some(result) => {
201
- ParseResult::Degraded(result, vec![format!("JSON parse failed: {}", e)])
202
- }
203
- None => {
204
- // Tier 3: Passthrough
205
- ParseResult::Passthrough(truncate_output(input, 500))
206
- }
207
- }
208
- }
209
- }
210
- }
211
- }
212
-
213
- /// Tier 2: Extract outdated info from text output
214
- fn extract_outdated_text(output: &str) -> Option<DependencyState> {
215
- let mut dependencies = Vec::new();
216
- let mut outdated_count = 0;
217
-
218
- for line in output.lines() {
219
- // Skip box-drawing, headers, legend
220
- if line.contains('│')
221
- || line.contains('├')
222
- || line.contains('└')
223
- || line.contains('─')
224
- || line.starts_with("Legend:")
225
- || line.starts_with("Package")
226
- || line.trim().is_empty()
227
- {
228
- continue;
229
- }
230
-
231
- // Parse lines: "package current wanted latest"
232
- let parts: Vec<&str> = line.split_whitespace().collect();
233
- if parts.len() >= 4 {
234
- let name = parts[0];
235
- let current = parts[1];
236
- let latest = parts[3];
237
-
238
- if current != latest {
239
- outdated_count += 1;
240
- }
241
-
242
- dependencies.push(Dependency {
243
- name: name.to_string(),
244
- current_version: current.to_string(),
245
- latest_version: Some(latest.to_string()),
246
- wanted_version: parts.get(2).map(|s| s.to_string()),
247
- dev_dependency: false,
248
- });
249
- }
250
- }
251
-
252
- if !dependencies.is_empty() {
253
- Some(DependencyState {
254
- total_packages: dependencies.len(),
255
- outdated_count,
256
- dependencies,
257
- })
258
- } else {
259
- None
260
- }
261
- }
262
-
263
- /// Validates npm package name according to official rules
264
- fn is_valid_package_name(name: &str) -> bool {
265
- if name.is_empty() || name.len() > 214 {
266
- return false;
267
- }
268
-
269
- // No path traversal
270
- if name.contains("..") {
271
- return false;
272
- }
273
-
274
- // Only safe characters
275
- name.chars()
276
- .all(|c| c.is_alphanumeric() || matches!(c, '@' | '/' | '-' | '_' | '.'))
277
- }
278
-
279
- #[derive(Debug, Clone)]
280
- pub enum PnpmCommand {
281
- List { depth: usize },
282
- Outdated,
283
- Install { packages: Vec<String> },
284
- }
285
-
286
- pub fn run(cmd: PnpmCommand, args: &[String], verbose: u8) -> Result<()> {
287
- match cmd {
288
- PnpmCommand::List { depth } => run_list(depth, args, verbose),
289
- PnpmCommand::Outdated => run_outdated(args, verbose),
290
- PnpmCommand::Install { packages } => run_install(&packages, args, verbose),
291
- }
292
- }
293
-
294
- fn run_list(depth: usize, args: &[String], verbose: u8) -> Result<()> {
295
- let timer = tracking::TimedExecution::start();
296
-
297
- let mut cmd = Command::new("pnpm");
298
- cmd.arg("list");
299
- cmd.arg(format!("--depth={}", depth));
300
- cmd.arg("--json");
301
-
302
- for arg in args {
303
- cmd.arg(arg);
304
- }
305
-
306
- let output = cmd.output().context("Failed to run pnpm list")?;
307
-
308
- if !output.status.success() {
309
- let stderr = String::from_utf8_lossy(&output.stderr);
310
- anyhow::bail!("pnpm list failed: {}", stderr);
311
- }
312
-
313
- let stdout = String::from_utf8_lossy(&output.stdout);
314
-
315
- // Parse output using PnpmListParser
316
- let parse_result = PnpmListParser::parse(&stdout);
317
- let mode = FormatMode::from_verbosity(verbose);
318
-
319
- let filtered = match parse_result {
320
- ParseResult::Full(data) => {
321
- if verbose > 0 {
322
- eprintln!("pnpm list (Tier 1: Full JSON parse)");
323
- }
324
- data.format(mode)
325
- }
326
- ParseResult::Degraded(data, warnings) => {
327
- if verbose > 0 {
328
- emit_degradation_warning("pnpm list", &warnings.join(", "));
329
- }
330
- data.format(mode)
331
- }
332
- ParseResult::Passthrough(raw) => {
333
- emit_passthrough_warning("pnpm list", "All parsing tiers failed");
334
- raw
335
- }
336
- };
337
-
338
- println!("{}", filtered);
339
-
340
- timer.track(
341
- &format!("pnpm list --depth={}", depth),
342
- &format!("rtk pnpm list --depth={}", depth),
343
- &stdout,
344
- &filtered,
345
- );
346
-
347
- Ok(())
348
- }
349
-
350
- fn run_outdated(args: &[String], verbose: u8) -> Result<()> {
351
- let timer = tracking::TimedExecution::start();
352
-
353
- let mut cmd = Command::new("pnpm");
354
- cmd.arg("outdated");
355
- cmd.arg("--format");
356
- cmd.arg("json");
357
-
358
- for arg in args {
359
- cmd.arg(arg);
360
- }
361
-
362
- let output = cmd.output().context("Failed to run pnpm outdated")?;
363
- let stdout = String::from_utf8_lossy(&output.stdout);
364
- let stderr = String::from_utf8_lossy(&output.stderr);
365
- let combined = format!("{}{}", stdout, stderr);
366
-
367
- // Parse output using PnpmOutdatedParser
368
- let parse_result = PnpmOutdatedParser::parse(&stdout);
369
- let mode = FormatMode::from_verbosity(verbose);
370
-
371
- let filtered = match parse_result {
372
- ParseResult::Full(data) => {
373
- if verbose > 0 {
374
- eprintln!("pnpm outdated (Tier 1: Full JSON parse)");
375
- }
376
- data.format(mode)
377
- }
378
- ParseResult::Degraded(data, warnings) => {
379
- if verbose > 0 {
380
- emit_degradation_warning("pnpm outdated", &warnings.join(", "));
381
- }
382
- data.format(mode)
383
- }
384
- ParseResult::Passthrough(raw) => {
385
- emit_passthrough_warning("pnpm outdated", "All parsing tiers failed");
386
- raw
387
- }
388
- };
389
-
390
- if filtered.trim().is_empty() {
391
- println!("All packages up-to-date ✓");
392
- } else {
393
- println!("{}", filtered);
394
- }
395
-
396
- timer.track("pnpm outdated", "rtk pnpm outdated", &combined, &filtered);
397
-
398
- Ok(())
399
- }
400
-
401
- fn run_install(packages: &[String], args: &[String], verbose: u8) -> Result<()> {
402
- let timer = tracking::TimedExecution::start();
403
-
404
- // Validate package names to prevent command injection
405
- for pkg in packages {
406
- if !is_valid_package_name(pkg) {
407
- anyhow::bail!(
408
- "Invalid package name: '{}' (contains unsafe characters)",
409
- pkg
410
- );
411
- }
412
- }
413
-
414
- let mut cmd = Command::new("pnpm");
415
- cmd.arg("install");
416
-
417
- for pkg in packages {
418
- cmd.arg(pkg);
419
- }
420
-
421
- for arg in args {
422
- cmd.arg(arg);
423
- }
424
-
425
- if verbose > 0 {
426
- eprintln!("pnpm install running...");
427
- }
428
-
429
- let output = cmd.output().context("Failed to run pnpm install")?;
430
- let stdout = String::from_utf8_lossy(&output.stdout);
431
- let stderr = String::from_utf8_lossy(&output.stderr);
432
-
433
- if !output.status.success() {
434
- anyhow::bail!("pnpm install failed: {}", stderr);
435
- }
436
-
437
- let combined = format!("{}{}", stdout, stderr);
438
- let filtered = filter_pnpm_install(&combined);
439
-
440
- println!("{}", filtered);
441
-
442
- timer.track(
443
- &format!("pnpm install {}", packages.join(" ")),
444
- &format!("rtk pnpm install {}", packages.join(" ")),
445
- &combined,
446
- &filtered,
447
- );
448
-
449
- Ok(())
450
- }
451
-
452
- /// Filter pnpm install output - remove progress bars, keep summary
453
- fn filter_pnpm_install(output: &str) -> String {
454
- let mut result = Vec::new();
455
- let mut saw_progress = false;
456
-
457
- for line in output.lines() {
458
- // Skip progress bars
459
- if line.contains("Progress") || line.contains('│') || line.contains('%') {
460
- saw_progress = true;
461
- continue;
462
- }
463
-
464
- if saw_progress && line.trim().is_empty() {
465
- continue;
466
- }
467
-
468
- // Keep error lines
469
- if line.contains("ERR") || line.contains("error") || line.contains("ERROR") {
470
- result.push(line.to_string());
471
- continue;
472
- }
473
-
474
- // Keep summary lines
475
- if line.contains("packages in")
476
- || line.contains("dependencies")
477
- || line.starts_with('+')
478
- || line.starts_with('-')
479
- {
480
- result.push(line.trim().to_string());
481
- }
482
- }
483
-
484
- if result.is_empty() {
485
- "ok ✓".to_string()
486
- } else {
487
- result.join("\n")
488
- }
489
- }
490
-
491
- /// Runs an unsupported pnpm subcommand by passing it through directly
492
- pub fn run_passthrough(args: &[OsString], verbose: u8) -> Result<()> {
493
- let timer = tracking::TimedExecution::start();
494
-
495
- if verbose > 0 {
496
- eprintln!("pnpm passthrough: {:?}", args);
497
- }
498
- let status = Command::new("pnpm")
499
- .args(args)
500
- .status()
501
- .context("Failed to run pnpm")?;
502
-
503
- let args_str = tracking::args_display(args);
504
- timer.track_passthrough(
505
- &format!("pnpm {}", args_str),
506
- &format!("rtk pnpm {} (passthrough)", args_str),
507
- );
508
-
509
- if !status.success() {
510
- std::process::exit(status.code().unwrap_or(1));
511
- }
512
- Ok(())
513
- }
514
-
515
- #[cfg(test)]
516
- mod tests {
517
- use super::*;
518
-
519
- #[test]
520
- fn test_pnpm_list_parser_json() {
521
- let json = r#"{
522
- "my-project": {
523
- "version": "1.0.0",
524
- "dependencies": {
525
- "express": {
526
- "version": "4.18.2"
527
- }
528
- }
529
- }
530
- }"#;
531
-
532
- let result = PnpmListParser::parse(json);
533
- assert_eq!(result.tier(), 1);
534
- assert!(result.is_ok());
535
-
536
- let data = result.unwrap();
537
- assert!(data.total_packages >= 2);
538
- }
539
-
540
- #[test]
541
- fn test_pnpm_outdated_parser_json() {
542
- let json = r#"{
543
- "express": {
544
- "current": "4.18.2",
545
- "latest": "4.19.0",
546
- "wanted": "4.18.2"
547
- }
548
- }"#;
549
-
550
- let result = PnpmOutdatedParser::parse(json);
551
- assert_eq!(result.tier(), 1);
552
- assert!(result.is_ok());
553
-
554
- let data = result.unwrap();
555
- assert_eq!(data.outdated_count, 1);
556
- assert_eq!(data.dependencies[0].name, "express");
557
- }
558
-
559
- #[test]
560
- fn test_package_name_validation() {
561
- assert!(is_valid_package_name("lodash"));
562
- assert!(is_valid_package_name("@clerk/express"));
563
- assert!(!is_valid_package_name("../../../etc/passwd"));
564
- assert!(!is_valid_package_name("lodash; rm -rf /"));
565
- }
566
-
567
- #[test]
568
- fn test_run_passthrough_accepts_args() {
569
- // Test that run_passthrough compiles and has correct signature
570
- let _args: Vec<OsString> = vec![OsString::from("help")];
571
- // Compile-time verification that the function exists with correct signature
572
- }
573
- }