electron-cli 0.2.6 → 0.3.0-alpha.0

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 (45) hide show
  1. package/Cargo.lock +266 -0
  2. package/Cargo.toml +14 -0
  3. package/LICENSE +15 -14
  4. package/README.md +55 -52
  5. package/bin/electron-cli.js +47 -0
  6. package/package.json +35 -80
  7. package/rust-toolchain.toml +3 -0
  8. package/src/cli.rs +36 -0
  9. package/src/commands/doctor.rs +355 -0
  10. package/src/commands/inspect.rs +63 -0
  11. package/src/commands/mod.rs +3 -0
  12. package/src/commands/plan.rs +235 -0
  13. package/src/main.rs +25 -0
  14. package/src/output.rs +7 -0
  15. package/src/project.rs +320 -0
  16. package/tests/fixtures/electron-forge/package-lock.json +23 -0
  17. package/tests/fixtures/electron-forge/package.json +21 -0
  18. package/tests/fixtures/electron-forge/src/main.ts +8 -0
  19. package/.babelrc +0 -14
  20. package/.eslintrc +0 -7
  21. package/.npmignore +0 -48
  22. package/dist/cli.js +0 -15
  23. package/dist/commands/init.js +0 -247
  24. package/dist/commands/pack.js +0 -78
  25. package/dist/commands/start.js +0 -76
  26. package/dist/commands/stats.js +0 -79
  27. package/dist/init/dir.js +0 -29
  28. package/dist/init/git.js +0 -25
  29. package/dist/init/json.js +0 -31
  30. package/dist/init/npm.js +0 -17
  31. package/dist/templates/index.html +0 -13
  32. package/dist/templates/main.js +0 -55
  33. package/dist/util/change-dir.js +0 -22
  34. package/dist/util/get-versions.js +0 -53
  35. package/dist/util/pack.js +0 -17
  36. package/dist/util/path-from-cwd.js +0 -13
  37. package/dist/util/start-electron.js +0 -51
  38. package/dist/util/terminate.js +0 -25
  39. package/dist/util/version.js +0 -10
  40. package/dist/validate/check-system.js +0 -56
  41. package/dist/validate/name.js +0 -34
  42. package/gulpfile.js +0 -94
  43. package/templates/index.html +0 -13
  44. package/templates/main.js +0 -55
  45. package/yarn.lock +0 -4158
@@ -0,0 +1,355 @@
1
+ use std::process::Command;
2
+
3
+ use anyhow::Result;
4
+ use serde::Serialize;
5
+
6
+ use crate::{cli::CommandArgs, output, project::ProjectSnapshot};
7
+
8
+ #[derive(Debug, Serialize)]
9
+ struct DoctorReport {
10
+ project: ProjectSnapshot,
11
+ summary: DoctorSummary,
12
+ checks: Vec<Check>,
13
+ }
14
+
15
+ #[derive(Debug, Serialize)]
16
+ struct DoctorSummary {
17
+ pass: usize,
18
+ warn: usize,
19
+ fail: usize,
20
+ info: usize,
21
+ }
22
+
23
+ #[derive(Debug, Serialize)]
24
+ struct Check {
25
+ id: &'static str,
26
+ level: CheckLevel,
27
+ message: String,
28
+ detail: Option<String>,
29
+ remedy: Option<String>,
30
+ }
31
+
32
+ #[derive(Debug, Serialize, PartialEq, Eq)]
33
+ #[serde(rename_all = "lowercase")]
34
+ enum CheckLevel {
35
+ Pass,
36
+ Warn,
37
+ Fail,
38
+ Info,
39
+ }
40
+
41
+ pub fn run(args: CommandArgs) -> Result<()> {
42
+ let snapshot = crate::project::inspect(&args.cwd)?;
43
+ let report = build_report(snapshot);
44
+
45
+ if args.json {
46
+ output::json(&report)
47
+ } else {
48
+ print_report(&report);
49
+ Ok(())
50
+ }
51
+ }
52
+
53
+ fn build_report(snapshot: ProjectSnapshot) -> DoctorReport {
54
+ let mut checks = Vec::new();
55
+
56
+ checks.push(match &snapshot.package_json {
57
+ Some(path) => Check::pass(
58
+ "package-json",
59
+ "Found package.json",
60
+ Some(format!("Using {path}")),
61
+ ),
62
+ None => Check::fail(
63
+ "package-json",
64
+ "No package.json found",
65
+ Some("Run this command inside an Electron or JavaScript project.".to_string()),
66
+ Some("Create a package.json or pass --cwd PATH to an existing project.".to_string()),
67
+ ),
68
+ });
69
+
70
+ checks.push(match &snapshot.electron_dependency {
71
+ Some(version) => Check::pass(
72
+ "electron-dependency",
73
+ "Electron dependency is declared",
74
+ Some(format!("electron {version}")),
75
+ ),
76
+ None if snapshot.package_json.is_some() => Check::warn(
77
+ "electron-dependency",
78
+ "Electron dependency is not declared",
79
+ Some(
80
+ "This may be a generic JavaScript project, or Electron may be installed elsewhere."
81
+ .to_string(),
82
+ ),
83
+ Some(
84
+ "Install Electron with your package manager if this is an Electron app."
85
+ .to_string(),
86
+ ),
87
+ ),
88
+ None => Check::info(
89
+ "electron-dependency",
90
+ "Electron dependency could not be checked",
91
+ Some("No package.json was found.".to_string()),
92
+ ),
93
+ });
94
+
95
+ checks.push(match &snapshot.main {
96
+ Some(main) => Check::pass(
97
+ "main-entry",
98
+ "Main process entry is declared",
99
+ Some(format!("package.json main: {main}")),
100
+ ),
101
+ None if snapshot.electron_dependency.is_some() => Check::warn(
102
+ "main-entry",
103
+ "No package.json main field found",
104
+ Some("Electron apps usually need a main process entry.".to_string()),
105
+ Some(
106
+ "Add a main field, or document why your tooling supplies it another way."
107
+ .to_string(),
108
+ ),
109
+ ),
110
+ None => Check::info(
111
+ "main-entry",
112
+ "Main process entry is not declared",
113
+ Some("This only matters for Electron apps.".to_string()),
114
+ ),
115
+ });
116
+
117
+ checks.push(if snapshot.scripts.contains_key("start") || snapshot.scripts.contains_key("dev") {
118
+ Check::pass(
119
+ "dev-script",
120
+ "Development script is declared",
121
+ snapshot
122
+ .scripts
123
+ .get("start")
124
+ .or_else(|| snapshot.scripts.get("dev"))
125
+ .map(|script| format!("script: {script}")),
126
+ )
127
+ } else if snapshot.package_json.is_some() {
128
+ Check::warn(
129
+ "dev-script",
130
+ "No start or dev script found",
131
+ Some("A predictable development script makes the project easier for people and agents to run.".to_string()),
132
+ Some("Add a start or dev script to package.json.".to_string()),
133
+ )
134
+ } else {
135
+ Check::info(
136
+ "dev-script",
137
+ "Development scripts could not be checked",
138
+ Some("No package.json was found.".to_string()),
139
+ )
140
+ });
141
+
142
+ checks.push(if snapshot.package_manager.is_some() {
143
+ Check::pass(
144
+ "lockfile",
145
+ "Package manager lockfile detected",
146
+ snapshot.package_manager.clone(),
147
+ )
148
+ } else if snapshot.package_json.is_some() && snapshot.has_javascript_dependencies() {
149
+ Check::warn(
150
+ "lockfile",
151
+ "No package manager lockfile detected",
152
+ Some("Installs may not be reproducible without a lockfile.".to_string()),
153
+ Some("Run npm install, pnpm install, yarn install, or bun install.".to_string()),
154
+ )
155
+ } else if snapshot.package_json.is_some() {
156
+ Check::info(
157
+ "lockfile",
158
+ "No package manager lockfile detected",
159
+ Some("No JavaScript dependencies are declared, so a lockfile is optional.".to_string()),
160
+ )
161
+ } else {
162
+ Check::info(
163
+ "lockfile",
164
+ "Lockfile could not be checked",
165
+ Some("No package.json was found.".to_string()),
166
+ )
167
+ });
168
+
169
+ checks.push(if snapshot.forge_dependencies.is_empty() {
170
+ Check::info(
171
+ "forge",
172
+ "Electron Forge is not declared",
173
+ Some(
174
+ "That is fine; electron-cli can inspect projects that use other tooling."
175
+ .to_string(),
176
+ ),
177
+ )
178
+ } else {
179
+ Check::pass(
180
+ "forge",
181
+ "Electron Forge dependency detected",
182
+ Some(
183
+ snapshot
184
+ .forge_dependencies
185
+ .keys()
186
+ .cloned()
187
+ .collect::<Vec<_>>()
188
+ .join(", "),
189
+ ),
190
+ )
191
+ });
192
+
193
+ checks.push(command_check(
194
+ "node",
195
+ &["--version"],
196
+ "node-runtime",
197
+ "Node.js is available",
198
+ ));
199
+ checks.push(command_check(
200
+ "npm",
201
+ &["--version"],
202
+ "npm-cli",
203
+ "npm is available",
204
+ ));
205
+
206
+ let summary = DoctorSummary {
207
+ pass: checks
208
+ .iter()
209
+ .filter(|check| check.level == CheckLevel::Pass)
210
+ .count(),
211
+ warn: checks
212
+ .iter()
213
+ .filter(|check| check.level == CheckLevel::Warn)
214
+ .count(),
215
+ fail: checks
216
+ .iter()
217
+ .filter(|check| check.level == CheckLevel::Fail)
218
+ .count(),
219
+ info: checks
220
+ .iter()
221
+ .filter(|check| check.level == CheckLevel::Info)
222
+ .count(),
223
+ };
224
+
225
+ DoctorReport {
226
+ project: snapshot,
227
+ summary,
228
+ checks,
229
+ }
230
+ }
231
+
232
+ fn command_check(
233
+ command: &'static str,
234
+ args: &[&str],
235
+ id: &'static str,
236
+ success_message: &'static str,
237
+ ) -> Check {
238
+ match Command::new(command).args(args).output() {
239
+ Ok(output) if output.status.success() => {
240
+ let version = String::from_utf8_lossy(&output.stdout).trim().to_string();
241
+ Check::pass(id, success_message, Some(format!("{command} {version}")))
242
+ }
243
+ Ok(output) => Check::fail(
244
+ id,
245
+ format!("{command} returned a non-zero exit code"),
246
+ Some(format!("exit status: {}", output.status)),
247
+ Some(format!("Install or repair {command}.")),
248
+ ),
249
+ Err(error) => Check::fail(
250
+ id,
251
+ format!("{command} could not be executed"),
252
+ Some(error.to_string()),
253
+ Some(format!("Install {command} and make sure it is on PATH.")),
254
+ ),
255
+ }
256
+ }
257
+
258
+ fn print_report(report: &DoctorReport) {
259
+ println!("electron-cli doctor");
260
+ println!();
261
+ println!("Project");
262
+ println!(" root: {}", report.project.root);
263
+ match report.project.package_label() {
264
+ Some(label) => println!(" package: {label}"),
265
+ None => println!(" package: not found"),
266
+ }
267
+
268
+ println!();
269
+ println!(
270
+ "Summary: {} pass, {} warn, {} fail, {} info",
271
+ report.summary.pass, report.summary.warn, report.summary.fail, report.summary.info
272
+ );
273
+ println!();
274
+ println!("Checks");
275
+
276
+ for check in &report.checks {
277
+ println!(
278
+ " {:<4} {:<20} {}",
279
+ check.level.as_str(),
280
+ check.id,
281
+ check.message
282
+ );
283
+
284
+ if let Some(detail) = &check.detail {
285
+ println!(" detail: {detail}");
286
+ }
287
+
288
+ if let Some(remedy) = &check.remedy {
289
+ println!(" remedy: {remedy}");
290
+ }
291
+ }
292
+ }
293
+
294
+ impl Check {
295
+ fn pass(id: &'static str, message: impl Into<String>, detail: Option<String>) -> Self {
296
+ Self {
297
+ id,
298
+ level: CheckLevel::Pass,
299
+ message: message.into(),
300
+ detail,
301
+ remedy: None,
302
+ }
303
+ }
304
+
305
+ fn warn(
306
+ id: &'static str,
307
+ message: impl Into<String>,
308
+ detail: Option<String>,
309
+ remedy: Option<String>,
310
+ ) -> Self {
311
+ Self {
312
+ id,
313
+ level: CheckLevel::Warn,
314
+ message: message.into(),
315
+ detail,
316
+ remedy,
317
+ }
318
+ }
319
+
320
+ fn fail(
321
+ id: &'static str,
322
+ message: impl Into<String>,
323
+ detail: Option<String>,
324
+ remedy: Option<String>,
325
+ ) -> Self {
326
+ Self {
327
+ id,
328
+ level: CheckLevel::Fail,
329
+ message: message.into(),
330
+ detail,
331
+ remedy,
332
+ }
333
+ }
334
+
335
+ fn info(id: &'static str, message: impl Into<String>, detail: Option<String>) -> Self {
336
+ Self {
337
+ id,
338
+ level: CheckLevel::Info,
339
+ message: message.into(),
340
+ detail,
341
+ remedy: None,
342
+ }
343
+ }
344
+ }
345
+
346
+ impl CheckLevel {
347
+ fn as_str(&self) -> &'static str {
348
+ match self {
349
+ CheckLevel::Pass => "PASS",
350
+ CheckLevel::Warn => "WARN",
351
+ CheckLevel::Fail => "FAIL",
352
+ CheckLevel::Info => "INFO",
353
+ }
354
+ }
355
+ }
@@ -0,0 +1,63 @@
1
+ use anyhow::Result;
2
+
3
+ use crate::{cli::CommandArgs, output, project};
4
+
5
+ pub fn run(args: CommandArgs) -> Result<()> {
6
+ let snapshot = project::inspect(&args.cwd)?;
7
+
8
+ if args.json {
9
+ output::json(&snapshot)
10
+ } else {
11
+ println!("electron-cli inspect");
12
+ println!();
13
+ println!("Project");
14
+ println!(" root: {}", snapshot.root);
15
+
16
+ match &snapshot.package_json {
17
+ Some(path) => println!(" package.json: {path}"),
18
+ None => println!(" package.json: not found"),
19
+ }
20
+
21
+ if let Some(package) = snapshot.package_label() {
22
+ println!(" package: {package}");
23
+ }
24
+
25
+ if let Some(package_manager) = &snapshot.package_manager {
26
+ println!(" package manager: {package_manager}");
27
+ }
28
+
29
+ println!();
30
+ println!("Electron");
31
+ match &snapshot.electron_dependency {
32
+ Some(version) => println!(" electron: {version}"),
33
+ None => println!(" electron: not declared"),
34
+ }
35
+
36
+ if snapshot.forge_dependencies.is_empty() {
37
+ println!(" forge: not declared");
38
+ } else {
39
+ println!(" forge:");
40
+ for (name, version) in &snapshot.forge_dependencies {
41
+ println!(" {name}: {version}");
42
+ }
43
+ }
44
+
45
+ if !snapshot.scripts.is_empty() {
46
+ println!();
47
+ println!("Scripts");
48
+ for (name, script) in &snapshot.scripts {
49
+ println!(" {name}: {script}");
50
+ }
51
+ }
52
+
53
+ if !snapshot.signals.is_empty() {
54
+ println!();
55
+ println!("Signals");
56
+ for signal in &snapshot.signals {
57
+ println!(" {signal}");
58
+ }
59
+ }
60
+
61
+ Ok(())
62
+ }
63
+ }
@@ -0,0 +1,3 @@
1
+ pub mod doctor;
2
+ pub mod inspect;
3
+ pub mod plan;
@@ -0,0 +1,235 @@
1
+ use std::collections::BTreeMap;
2
+
3
+ use anyhow::Result;
4
+ use serde::Serialize;
5
+
6
+ use crate::{cli::CommandArgs, output, project};
7
+
8
+ #[derive(Debug, Serialize)]
9
+ struct PlanReport {
10
+ project_type: ProjectType,
11
+ recommended_commands: BTreeMap<String, String>,
12
+ missing: Vec<String>,
13
+ risks: Vec<String>,
14
+ notes: Vec<String>,
15
+ }
16
+
17
+ #[derive(Debug, Serialize)]
18
+ #[serde(rename_all = "kebab-case")]
19
+ enum ProjectType {
20
+ ElectronForge,
21
+ Electron,
22
+ JavaScript,
23
+ Unknown,
24
+ }
25
+
26
+ pub fn run(args: CommandArgs) -> Result<()> {
27
+ let snapshot = project::inspect(&args.cwd)?;
28
+ let report = build_report(&snapshot);
29
+
30
+ if args.json {
31
+ output::json(&report)
32
+ } else {
33
+ println!("electron-cli plan");
34
+ println!();
35
+ println!("Project");
36
+ println!(" root: {}", snapshot.root);
37
+ match snapshot.package_label() {
38
+ Some(label) => println!(" package: {label}"),
39
+ None => println!(" package: not found"),
40
+ }
41
+ println!(" type: {}", report.project_type.as_str());
42
+
43
+ if !report.recommended_commands.is_empty() {
44
+ println!();
45
+ println!("Recommended Commands");
46
+ for (name, command) in &report.recommended_commands {
47
+ println!(" {name}: {command}");
48
+ }
49
+ }
50
+
51
+ if !report.missing.is_empty() {
52
+ println!();
53
+ println!("Missing");
54
+ for item in &report.missing {
55
+ println!(" {item}");
56
+ }
57
+ }
58
+
59
+ if !report.risks.is_empty() {
60
+ println!();
61
+ println!("Risks");
62
+ for risk in &report.risks {
63
+ println!(" {risk}");
64
+ }
65
+ }
66
+
67
+ if !report.notes.is_empty() {
68
+ println!();
69
+ println!("Notes");
70
+ for note in &report.notes {
71
+ println!(" {note}");
72
+ }
73
+ }
74
+
75
+ Ok(())
76
+ }
77
+ }
78
+
79
+ fn build_report(snapshot: &project::ProjectSnapshot) -> PlanReport {
80
+ let project_type = detect_project_type(snapshot);
81
+ let mut recommended_commands = BTreeMap::new();
82
+ let mut missing = Vec::new();
83
+ let mut risks = Vec::new();
84
+ let mut notes = Vec::new();
85
+
86
+ if let Some(script) = first_script(snapshot, &["start", "dev"]) {
87
+ recommended_commands.insert("dev".to_string(), run_script(snapshot, script));
88
+ } else if snapshot.electron_dependency.is_some() && snapshot.main.is_some() {
89
+ recommended_commands.insert("dev".to_string(), package_exec(snapshot, "electron ."));
90
+ missing.push(
91
+ "Add a start or dev script so humans and agents have a stable entrypoint.".to_string(),
92
+ );
93
+ } else {
94
+ missing.push("No start or dev script was found.".to_string());
95
+ }
96
+
97
+ if let Some(script) = first_script(snapshot, &["package", "pack"]) {
98
+ recommended_commands.insert("package".to_string(), run_script(snapshot, script));
99
+ } else if matches!(project_type, ProjectType::ElectronForge) {
100
+ missing.push(
101
+ "No package script was found even though Electron Forge is declared.".to_string(),
102
+ );
103
+ }
104
+
105
+ if let Some(script) = first_script(snapshot, &["make", "dist"]) {
106
+ recommended_commands.insert("make".to_string(), run_script(snapshot, script));
107
+ }
108
+
109
+ recommended_commands.insert(
110
+ "diagnostics".to_string(),
111
+ "electron-cli doctor --json".to_string(),
112
+ );
113
+ recommended_commands.insert(
114
+ "inspect".to_string(),
115
+ "electron-cli inspect --json".to_string(),
116
+ );
117
+
118
+ if snapshot.package_json.is_none() {
119
+ risks.push(
120
+ "No package.json was found, so Electron project detection is limited.".to_string(),
121
+ );
122
+ }
123
+
124
+ if snapshot.electron_dependency.is_none() {
125
+ risks.push("Electron is not declared in package dependencies.".to_string());
126
+ }
127
+
128
+ if snapshot.electron_dependency.is_some() && snapshot.main.is_none() {
129
+ risks.push("Electron is declared, but package.json has no main process entry.".to_string());
130
+ }
131
+
132
+ if snapshot.has_javascript_dependencies() && snapshot.package_manager.is_none() {
133
+ risks.push("JavaScript dependencies are declared, but no lockfile was found.".to_string());
134
+ }
135
+
136
+ if matches!(project_type, ProjectType::ElectronForge) {
137
+ notes.push("Electron Forge was detected; wrapping Forge commands is the safest workflow path today.".to_string());
138
+ } else if snapshot.electron_dependency.is_some() {
139
+ notes.push("Electron was detected without Forge; prefer inspection before choosing a packaging path.".to_string());
140
+ } else {
141
+ notes.push("This does not currently look like an Electron app.".to_string());
142
+ }
143
+
144
+ PlanReport {
145
+ project_type,
146
+ recommended_commands,
147
+ missing,
148
+ risks,
149
+ notes,
150
+ }
151
+ }
152
+
153
+ fn detect_project_type(snapshot: &project::ProjectSnapshot) -> ProjectType {
154
+ if !snapshot.forge_dependencies.is_empty()
155
+ || snapshot
156
+ .scripts
157
+ .values()
158
+ .any(|script| script.contains("electron-forge"))
159
+ {
160
+ ProjectType::ElectronForge
161
+ } else if snapshot.electron_dependency.is_some() {
162
+ ProjectType::Electron
163
+ } else if snapshot.package_json.is_some() {
164
+ ProjectType::JavaScript
165
+ } else {
166
+ ProjectType::Unknown
167
+ }
168
+ }
169
+
170
+ fn first_script<'a>(snapshot: &project::ProjectSnapshot, names: &'a [&'a str]) -> Option<&'a str> {
171
+ names
172
+ .iter()
173
+ .copied()
174
+ .find(|name| snapshot.scripts.contains_key(*name))
175
+ }
176
+
177
+ fn run_script(snapshot: &project::ProjectSnapshot, script: &str) -> String {
178
+ match snapshot.package_manager.as_deref() {
179
+ Some("bun") => format!("bun run {script}"),
180
+ Some("pnpm") => format!("pnpm run {script}"),
181
+ Some("yarn") => format!("yarn run {script}"),
182
+ Some("npm") | None => format!("npm run {script}"),
183
+ Some(package_manager) => format!("{package_manager} run {script}"),
184
+ }
185
+ }
186
+
187
+ fn package_exec(snapshot: &project::ProjectSnapshot, command: &str) -> String {
188
+ match snapshot.package_manager.as_deref() {
189
+ Some("bun") => format!("bunx {command}"),
190
+ Some("pnpm") => format!("pnpm exec {command}"),
191
+ Some("yarn") => format!("yarn {command}"),
192
+ Some("npm") | None => format!("npx {command}"),
193
+ Some(package_manager) => format!("{package_manager} exec {command}"),
194
+ }
195
+ }
196
+
197
+ impl ProjectType {
198
+ fn as_str(&self) -> &'static str {
199
+ match self {
200
+ ProjectType::ElectronForge => "electron-forge",
201
+ ProjectType::Electron => "electron",
202
+ ProjectType::JavaScript => "javascript",
203
+ ProjectType::Unknown => "unknown",
204
+ }
205
+ }
206
+ }
207
+
208
+ #[cfg(test)]
209
+ mod tests {
210
+ use std::path::Path;
211
+
212
+ use super::*;
213
+
214
+ #[test]
215
+ fn plans_for_electron_forge_fixture() {
216
+ let fixture = Path::new(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/electron-forge");
217
+ let snapshot = project::inspect(&fixture).expect("fixture should inspect");
218
+
219
+ let report = build_report(&snapshot);
220
+
221
+ assert!(matches!(report.project_type, ProjectType::ElectronForge));
222
+ assert_eq!(
223
+ report.recommended_commands.get("dev").map(String::as_str),
224
+ Some("npm run start")
225
+ );
226
+ assert_eq!(
227
+ report
228
+ .recommended_commands
229
+ .get("package")
230
+ .map(String::as_str),
231
+ Some("npm run package")
232
+ );
233
+ assert!(report.risks.is_empty());
234
+ }
235
+ }
package/src/main.rs ADDED
@@ -0,0 +1,25 @@
1
+ mod cli;
2
+ mod commands;
3
+ mod output;
4
+ mod project;
5
+
6
+ use anyhow::Result;
7
+ use clap::Parser;
8
+ use cli::{Cli, Commands};
9
+
10
+ fn main() {
11
+ if let Err(error) = run() {
12
+ eprintln!("error: {error:#}");
13
+ std::process::exit(1);
14
+ }
15
+ }
16
+
17
+ fn run() -> Result<()> {
18
+ let cli = Cli::parse();
19
+
20
+ match cli.command {
21
+ Commands::Doctor(args) => commands::doctor::run(args),
22
+ Commands::Inspect(args) => commands::inspect::run(args),
23
+ Commands::Plan(args) => commands::plan::run(args),
24
+ }
25
+ }
package/src/output.rs ADDED
@@ -0,0 +1,7 @@
1
+ use anyhow::Result;
2
+ use serde::Serialize;
3
+
4
+ pub fn json(value: &impl Serialize) -> Result<()> {
5
+ println!("{}", serde_json::to_string_pretty(value)?);
6
+ Ok(())
7
+ }