electron-cli 0.3.0-alpha.12 → 0.3.0-alpha.13

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.
package/Cargo.lock CHANGED
@@ -376,7 +376,7 @@ checksum = "91622ff5e7162018101f2fea40d6ebf4a78bbe5a49736a2020649edf9693679e"
376
376
 
377
377
  [[package]]
378
378
  name = "electron-cli"
379
- version = "0.3.0-alpha.12"
379
+ version = "0.3.0-alpha.13"
380
380
  dependencies = [
381
381
  "anyhow",
382
382
  "apple-dmg",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "electron-cli"
3
- version = "0.3.0-alpha.12"
3
+ version = "0.3.0-alpha.13"
4
4
  edition = "2021"
5
5
  description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
6
6
  license = "MIT"
package/README.md CHANGED
@@ -36,7 +36,7 @@ The Rust-native flow currently owns:
36
36
  - `init --template minimal`: writes a local Electron starter without Electron Forge.
37
37
  - `start`: launches the installed Electron runtime directly.
38
38
  - `package`: copies the installed Electron runtime, app files, installed production dependency closure, app metadata, macOS icon, and extra resources into a local app bundle for the current platform and architecture.
39
- - `make`: runs `package` and writes a distributable under `out/make/<target>/<platform>/<arch>/`; ZIP works on all platforms, `--target dmg` writes a basic macOS disk image, `--target deb` / `--target rpm` write Linux packages, and `--target msi` writes a basic Windows Installer package.
39
+ - `make`: runs `package` and writes distributables under `out/make/<target>/<platform>/<arch>/`; it reads JSON-shaped `config.forge.makers` / `electronCli.makers` arrays when `--target` is omitted, and `--target` still forces one maker. ZIP works on all platforms, `--target dmg` writes a basic macOS disk image, `--target deb` / `--target rpm` write Linux packages, and `--target msi` writes a basic Windows Installer package.
40
40
  - `publish`: runs `make` and publishes the distributable to a local directory with a manifest or to GitHub Releases.
41
41
 
42
42
  The GitHub publisher creates or reuses a release, uploads the selected make artifact, and can replace an existing asset with `--force`. It reads `GITHUB_TOKEN` or `GH_TOKEN` and can infer `OWNER/REPO` from `package.json` `repository`, or you can pass `--github-repo`.
@@ -54,12 +54,26 @@ Package metadata can be configured in `package.json`:
54
54
  "appCategoryType": "public.app-category.developer-tools",
55
55
  "icon": "assets/icon",
56
56
  "extraResource": "assets/config.json"
57
+ },
58
+ "makers": [
59
+ { "name": "@electron-forge/maker-zip" },
60
+ { "name": "@electron-forge/maker-dmg", "platforms": ["darwin"] },
61
+ { "name": "@electron-forge/maker-deb", "platforms": ["linux"] },
62
+ { "name": "@electron-forge/maker-rpm", "platforms": ["linux"] },
63
+ { "name": "@electron-forge/maker-wix", "platforms": ["win32"] }
64
+ ]
65
+ },
66
+ "config": {
67
+ "forge": {
68
+ "makers": [
69
+ { "name": "@electron-forge/maker-zip" }
70
+ ]
57
71
  }
58
72
  }
59
73
  }
60
74
  ```
61
75
 
62
- The package command also reads JSON-shaped `config.forge.packagerConfig` and `electronPackagerConfig` entries for the same fields. JavaScript Forge config files are not evaluated.
76
+ The package command also reads JSON-shaped `config.forge.packagerConfig` and `electronPackagerConfig` entries for the same fields. The make command maps JSON-shaped Forge maker names to the Rust-native targets it supports: zip, dmg, deb, rpm, and wix/msi. JavaScript Forge config files are not evaluated.
63
77
 
64
78
  ## Install
65
79
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-cli",
3
- "version": "0.3.0-alpha.12",
3
+ "version": "0.3.0-alpha.13",
4
4
  "description": "Experimental Rust CLI for Electron project diagnostics and workflow automation",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.rs CHANGED
@@ -164,9 +164,9 @@ pub struct MakeArgs {
164
164
  #[arg(long)]
165
165
  pub arch: Option<String>,
166
166
 
167
- /// Maker target to run.
168
- #[arg(long, value_enum, default_value_t = MakeTarget::Zip)]
169
- pub target: MakeTarget,
167
+ /// Maker target to run. Overrides configured makers when provided.
168
+ #[arg(long, value_enum)]
169
+ pub target: Option<MakeTarget>,
170
170
 
171
171
  /// Reuse an existing package output instead of running package first.
172
172
  #[arg(long)]
@@ -15,6 +15,7 @@ use fscommon::BufStream;
15
15
  use msi::{Column, Insert, Language, Package, PackageType, Value};
16
16
  use rpm::{BuildConfig, CompressionType, FileOptions, PackageBuilder};
17
17
  use serde::Serialize;
18
+ use serde_json::Value as JsonValue;
18
19
  use tar::{Builder as TarBuilder, Header as TarHeader};
19
20
  use uuid::Uuid;
20
21
  use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter};
@@ -23,12 +24,15 @@ use crate::{
23
24
  cli::{MakeArgs, MakeTarget, PackageArgs},
24
25
  commands::package::{self, PackageReport},
25
26
  output,
27
+ project::ProjectSnapshot,
26
28
  };
27
29
 
28
30
  #[derive(Debug, Serialize)]
29
31
  pub(crate) struct MakeReport {
30
32
  package: PackageReport,
31
33
  target: String,
34
+ #[serde(skip)]
35
+ target_kind: MakeTarget,
32
36
  skip_package: bool,
33
37
  dry_run: bool,
34
38
  make_dir: Utf8PathBuf,
@@ -38,27 +42,53 @@ pub(crate) struct MakeReport {
38
42
  warnings: Vec<String>,
39
43
  }
40
44
 
41
- #[derive(Debug, Serialize)]
45
+ #[derive(Clone, Copy, Debug, Serialize)]
42
46
  #[serde(rename_all = "kebab-case")]
43
47
  enum MakeStatus {
44
48
  Planned,
45
49
  Made,
46
50
  }
47
51
 
52
+ struct ResolvedMakeTargets {
53
+ targets: Vec<MakeTarget>,
54
+ warnings: Vec<String>,
55
+ }
56
+
57
+ #[derive(Debug, Serialize)]
58
+ struct MakeRunReport<'a> {
59
+ targets: &'a [MakeReport],
60
+ dry_run: bool,
61
+ status: MakeStatus,
62
+ warnings: Vec<String>,
63
+ }
64
+
48
65
  pub fn run(args: MakeArgs) -> Result<()> {
49
- let mut report = build_report(&args)?;
66
+ let mut reports = build_reports(&args)?;
50
67
 
51
68
  if args.dry_run {
52
- return print_report(&report, args.json);
69
+ return print_reports(&reports, args.json, MakeStatus::Planned);
53
70
  }
54
71
 
55
- execute_make(&mut report, &args)?;
56
- report.mark_made()?;
72
+ execute_make_reports(&mut reports, &args)?;
57
73
 
58
- print_report(&report, args.json)
74
+ print_reports(&reports, args.json, MakeStatus::Made)
59
75
  }
60
76
 
61
77
  pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
78
+ let reports = build_reports(args)?;
79
+ if reports.len() != 1 {
80
+ bail!(
81
+ "Expected one make target, but resolved {}. Pass --target to select one target.",
82
+ reports.len()
83
+ );
84
+ }
85
+ Ok(reports
86
+ .into_iter()
87
+ .next()
88
+ .expect("length was checked above"))
89
+ }
90
+
91
+ pub(crate) fn build_reports(args: &MakeArgs) -> Result<Vec<MakeReport>> {
62
92
  let package_args = PackageArgs {
63
93
  cwd: args.cwd.clone(),
64
94
  out_dir: args.out_dir.clone(),
@@ -70,29 +100,47 @@ pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
70
100
  json: false,
71
101
  };
72
102
  let snapshot = crate::project::inspect(&package_args.cwd)?;
73
- let package = package::build_report(snapshot, &package_args)?;
103
+ let resolved = resolve_make_targets(&snapshot, args)?;
104
+ let config_warnings = resolved.warnings;
105
+ resolved
106
+ .targets
107
+ .into_iter()
108
+ .map(|target| {
109
+ let package = package::build_report(snapshot.clone(), &package_args)?;
110
+ build_report_for_target(package, target, args, &config_warnings)
111
+ })
112
+ .collect()
113
+ }
114
+
115
+ fn build_report_for_target(
116
+ package: PackageReport,
117
+ target: MakeTarget,
118
+ args: &MakeArgs,
119
+ config_warnings: &[String],
120
+ ) -> Result<MakeReport> {
74
121
  let make_dir = Path::new(package.output_dir().as_str())
75
122
  .join("make")
76
- .join(args.target.as_str())
123
+ .join(target.as_str())
77
124
  .join(package.platform())
78
125
  .join(package.arch());
79
- let artifact = make_artifact_path(&make_dir, &package, args.target);
126
+ let artifact = make_artifact_path(&make_dir, &package, target);
80
127
 
81
128
  let mut warnings = package.warnings().to_vec();
82
- if matches!(args.target, MakeTarget::Deb | MakeTarget::Rpm) && package.platform() != "linux" {
129
+ warnings.extend(config_warnings.iter().cloned());
130
+ if matches!(target, MakeTarget::Deb | MakeTarget::Rpm) && package.platform() != "linux" {
83
131
  warnings.push(format!(
84
132
  "{} maker only supports linux packages; target platform is {}.",
85
- args.target.as_str(),
133
+ target.as_str(),
86
134
  package.platform()
87
135
  ));
88
136
  }
89
- if args.target == MakeTarget::Dmg && package.platform() != "darwin" {
137
+ if target == MakeTarget::Dmg && package.platform() != "darwin" {
90
138
  warnings.push(format!(
91
139
  "dmg maker only supports macOS packages; target platform is {}.",
92
140
  package.platform()
93
141
  ));
94
142
  }
95
- if args.target == MakeTarget::Msi && package.platform() != "win32" {
143
+ if target == MakeTarget::Msi && package.platform() != "win32" {
96
144
  warnings.push(format!(
97
145
  "msi maker only supports Windows packages; target platform is {}.",
98
146
  package.platform()
@@ -114,7 +162,8 @@ pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
114
162
 
115
163
  Ok(MakeReport {
116
164
  package,
117
- target: args.target.as_str().to_string(),
165
+ target: target.as_str().to_string(),
166
+ target_kind: target,
118
167
  skip_package: args.skip_package,
119
168
  dry_run: args.dry_run,
120
169
  make_dir: utf8_path(make_dir)?,
@@ -125,17 +174,215 @@ pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
125
174
  })
126
175
  }
127
176
 
177
+ struct ConfiguredMaker {
178
+ label: String,
179
+ target: Option<MakeTarget>,
180
+ platforms: Vec<String>,
181
+ }
182
+
183
+ fn resolve_make_targets(
184
+ snapshot: &ProjectSnapshot,
185
+ args: &MakeArgs,
186
+ ) -> Result<ResolvedMakeTargets> {
187
+ if let Some(target) = args.target {
188
+ return Ok(ResolvedMakeTargets {
189
+ targets: vec![target],
190
+ warnings: Vec::new(),
191
+ });
192
+ }
193
+
194
+ let platform = args.platform.clone().unwrap_or_else(current_platform_label);
195
+ let makers = configured_makers(snapshot)?;
196
+ let mut warnings = Vec::new();
197
+ let mut targets = Vec::new();
198
+
199
+ for maker in &makers {
200
+ let Some(target) = maker.target else {
201
+ warnings.push(format!(
202
+ "Configured maker is not implemented yet and will be skipped: {}.",
203
+ maker.label
204
+ ));
205
+ continue;
206
+ };
207
+ if !maker_applies_to_platform(maker, &platform) {
208
+ continue;
209
+ }
210
+ if !targets.contains(&target) {
211
+ targets.push(target);
212
+ }
213
+ }
214
+
215
+ if targets.is_empty() {
216
+ if makers.is_empty() {
217
+ targets.push(MakeTarget::Zip);
218
+ } else {
219
+ warnings.push(format!(
220
+ "No supported configured makers apply to {platform}; defaulting to zip. Pass --target to override."
221
+ ));
222
+ targets.push(MakeTarget::Zip);
223
+ }
224
+ }
225
+
226
+ Ok(ResolvedMakeTargets { targets, warnings })
227
+ }
228
+
229
+ fn configured_makers(snapshot: &ProjectSnapshot) -> Result<Vec<ConfiguredMaker>> {
230
+ let Some(package_json_path) = &snapshot.package_json else {
231
+ return Ok(Vec::new());
232
+ };
233
+ let package_json_path = Path::new(package_json_path.as_str());
234
+ let raw = fs::read_to_string(package_json_path)
235
+ .with_context(|| format!("Could not read {}", package_json_path.display()))?;
236
+ let package = serde_json::from_str::<JsonValue>(&raw)
237
+ .with_context(|| format!("Could not parse {}", package_json_path.display()))?;
238
+
239
+ let mut makers = Vec::new();
240
+ for value in [
241
+ package
242
+ .get("config")
243
+ .and_then(|config| config.get("forge"))
244
+ .and_then(|forge| forge.get("makers")),
245
+ package
246
+ .get("electronCli")
247
+ .or_else(|| package.get("electron-cli"))
248
+ .and_then(|config| config.get("makers")),
249
+ ]
250
+ .into_iter()
251
+ .flatten()
252
+ {
253
+ makers.extend(parse_maker_list(value));
254
+ }
255
+
256
+ Ok(makers)
257
+ }
258
+
259
+ fn parse_maker_list(value: &JsonValue) -> Vec<ConfiguredMaker> {
260
+ match value {
261
+ JsonValue::Array(values) => values.iter().filter_map(parse_maker).collect(),
262
+ _ => Vec::new(),
263
+ }
264
+ }
265
+
266
+ fn parse_maker(value: &JsonValue) -> Option<ConfiguredMaker> {
267
+ match value {
268
+ JsonValue::String(label) => Some(ConfiguredMaker {
269
+ label: label.clone(),
270
+ target: maker_target(label),
271
+ platforms: Vec::new(),
272
+ }),
273
+ JsonValue::Object(object) => {
274
+ let label = object
275
+ .get("name")
276
+ .or_else(|| object.get("target"))
277
+ .or_else(|| object.get("maker"))
278
+ .and_then(JsonValue::as_str)?
279
+ .to_string();
280
+ Some(ConfiguredMaker {
281
+ target: maker_target(&label),
282
+ platforms: string_values(object.get("platforms")),
283
+ label,
284
+ })
285
+ }
286
+ _ => None,
287
+ }
288
+ }
289
+
290
+ fn maker_target(label: &str) -> Option<MakeTarget> {
291
+ let label = label.trim().to_ascii_lowercase();
292
+ let compact = label
293
+ .trim_start_matches("@electron-forge/")
294
+ .trim_start_matches("electron-forge-")
295
+ .trim_start_matches("maker-");
296
+
297
+ if matches!(compact, "zip" | "@electron-forge/maker-zip")
298
+ || label.ends_with("/maker-zip")
299
+ || label.ends_with("maker-zip")
300
+ {
301
+ Some(MakeTarget::Zip)
302
+ } else if compact == "dmg" || label.ends_with("/maker-dmg") || label.ends_with("maker-dmg") {
303
+ Some(MakeTarget::Dmg)
304
+ } else if compact == "deb" || label.ends_with("/maker-deb") || label.ends_with("maker-deb") {
305
+ Some(MakeTarget::Deb)
306
+ } else if compact == "rpm" || label.ends_with("/maker-rpm") || label.ends_with("maker-rpm") {
307
+ Some(MakeTarget::Rpm)
308
+ } else if matches!(compact, "msi" | "wix")
309
+ || label.ends_with("/maker-wix")
310
+ || label.ends_with("maker-wix")
311
+ {
312
+ Some(MakeTarget::Msi)
313
+ } else {
314
+ None
315
+ }
316
+ }
317
+
318
+ fn maker_applies_to_platform(maker: &ConfiguredMaker, platform: &str) -> bool {
319
+ maker.platforms.is_empty()
320
+ || maker
321
+ .platforms
322
+ .iter()
323
+ .any(|configured| configured == platform || configured == "*")
324
+ }
325
+
326
+ fn string_values(value: Option<&JsonValue>) -> Vec<String> {
327
+ match value {
328
+ Some(JsonValue::String(value)) => vec![value.clone()],
329
+ Some(JsonValue::Array(values)) => values
330
+ .iter()
331
+ .filter_map(JsonValue::as_str)
332
+ .map(ToOwned::to_owned)
333
+ .collect(),
334
+ _ => Vec::new(),
335
+ }
336
+ }
337
+
338
+ fn current_platform_label() -> String {
339
+ if cfg!(target_os = "macos") {
340
+ "darwin".to_string()
341
+ } else if cfg!(target_os = "windows") {
342
+ "win32".to_string()
343
+ } else {
344
+ "linux".to_string()
345
+ }
346
+ }
347
+
128
348
  pub(crate) fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
349
+ ensure_package_ready(std::slice::from_mut(report), args)?;
350
+ execute_make_artifact(report, args)?;
351
+ Ok(())
352
+ }
353
+
354
+ pub(crate) fn execute_make_reports(reports: &mut [MakeReport], args: &MakeArgs) -> Result<()> {
355
+ if reports.is_empty() {
356
+ bail!("No make targets were resolved.");
357
+ }
358
+ ensure_package_ready(reports, args)?;
359
+ for report in reports {
360
+ execute_make_artifact(report, args)?;
361
+ report.mark_made()?;
362
+ }
363
+ Ok(())
364
+ }
365
+
366
+ fn ensure_package_ready(reports: &mut [MakeReport], args: &MakeArgs) -> Result<()> {
367
+ let first = reports
368
+ .first_mut()
369
+ .context("No make targets were resolved.")?;
129
370
  if !args.skip_package {
130
- package::execute_package(&report.package, args.force)?;
131
- report.package.mark_packaged();
132
- } else if !Path::new(report.package.bundle_dir().as_str()).exists() {
371
+ package::execute_package(&first.package, args.force)?;
372
+ for report in reports {
373
+ report.package.mark_packaged();
374
+ }
375
+ } else if !Path::new(first.package.bundle_dir().as_str()).exists() {
133
376
  bail!(
134
377
  "Package output does not exist: {}. Run without --skip-package or run electron-cli package first.",
135
- report.package.bundle_dir()
378
+ first.package.bundle_dir()
136
379
  );
137
380
  }
138
381
 
382
+ Ok(())
383
+ }
384
+
385
+ fn execute_make_artifact(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
139
386
  let artifact = Path::new(report.artifact.as_str());
140
387
  if artifact.exists() {
141
388
  if args.force {
@@ -151,7 +398,7 @@ pub(crate) fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<(
151
398
 
152
399
  fs::create_dir_all(report.make_dir.as_str())
153
400
  .with_context(|| format!("Could not create {}", report.make_dir))?;
154
- match args.target {
401
+ match report.target_kind {
155
402
  MakeTarget::Zip => {
156
403
  write_zip_archive(Path::new(report.package.bundle_dir().as_str()), artifact)?
157
404
  }
@@ -204,6 +451,69 @@ fn print_report(report: &MakeReport, json: bool) -> Result<()> {
204
451
  Ok(())
205
452
  }
206
453
 
454
+ fn print_reports(reports: &[MakeReport], json: bool, status: MakeStatus) -> Result<()> {
455
+ if reports.len() == 1 {
456
+ return print_report(&reports[0], json);
457
+ }
458
+
459
+ let warnings = combined_warnings(reports);
460
+ if json {
461
+ return output::json(&MakeRunReport {
462
+ targets: reports,
463
+ dry_run: reports.iter().any(|report| report.dry_run),
464
+ status,
465
+ warnings,
466
+ });
467
+ }
468
+
469
+ println!("electron-cli make");
470
+ println!();
471
+ if let Some(first) = reports.first() {
472
+ println!("Project");
473
+ println!(" root: {}", first.package.project().root);
474
+ match first.package.project().package_label() {
475
+ Some(label) => println!(" package: {label}"),
476
+ None => println!(" package: not found"),
477
+ }
478
+ println!(" app name: {}", first.package.app_name());
479
+ println!(
480
+ " target platform: {} {}",
481
+ first.package.platform(),
482
+ first.package.arch()
483
+ );
484
+ println!(" status: {}", status.as_str());
485
+ }
486
+
487
+ println!();
488
+ println!("Artifacts");
489
+ for report in reports {
490
+ println!(" {}: {}", report.target, report.artifact);
491
+ if let Some(size) = report.artifact_size {
492
+ println!(" size: {size} bytes");
493
+ }
494
+ }
495
+
496
+ if !warnings.is_empty() {
497
+ println!();
498
+ println!("Warnings");
499
+ for warning in warnings {
500
+ println!(" {warning}");
501
+ }
502
+ }
503
+
504
+ Ok(())
505
+ }
506
+
507
+ fn combined_warnings(reports: &[MakeReport]) -> Vec<String> {
508
+ let mut warnings = Vec::new();
509
+ for warning in reports.iter().flat_map(|report| report.warnings()) {
510
+ if !warnings.contains(warning) {
511
+ warnings.push(warning.clone());
512
+ }
513
+ }
514
+ warnings
515
+ }
516
+
207
517
  fn make_artifact_path(make_dir: &Path, package: &PackageReport, target: MakeTarget) -> PathBuf {
208
518
  match target {
209
519
  MakeTarget::Zip => make_dir.join(format!(
@@ -1909,7 +2219,7 @@ mod tests {
1909
2219
  name: None,
1910
2220
  platform: None,
1911
2221
  arch: None,
1912
- target: crate::cli::MakeTarget::Zip,
2222
+ target: Some(crate::cli::MakeTarget::Zip),
1913
2223
  skip_package: false,
1914
2224
  force: false,
1915
2225
  dry_run: true,
@@ -1946,7 +2256,7 @@ mod tests {
1946
2256
  name: None,
1947
2257
  platform: Some("linux".to_string()),
1948
2258
  arch: Some("x64".to_string()),
1949
- target: crate::cli::MakeTarget::Deb,
2259
+ target: Some(crate::cli::MakeTarget::Deb),
1950
2260
  skip_package: false,
1951
2261
  force: false,
1952
2262
  dry_run: true,
@@ -1980,7 +2290,7 @@ mod tests {
1980
2290
  name: None,
1981
2291
  platform: Some("darwin".to_string()),
1982
2292
  arch: Some("arm64".to_string()),
1983
- target: crate::cli::MakeTarget::Dmg,
2293
+ target: Some(crate::cli::MakeTarget::Dmg),
1984
2294
  skip_package: false,
1985
2295
  force: false,
1986
2296
  dry_run: true,
@@ -2014,7 +2324,7 @@ mod tests {
2014
2324
  name: None,
2015
2325
  platform: Some("linux".to_string()),
2016
2326
  arch: Some("x64".to_string()),
2017
- target: crate::cli::MakeTarget::Rpm,
2327
+ target: Some(crate::cli::MakeTarget::Rpm),
2018
2328
  skip_package: false,
2019
2329
  force: false,
2020
2330
  dry_run: true,
@@ -2048,7 +2358,7 @@ mod tests {
2048
2358
  name: None,
2049
2359
  platform: Some("win32".to_string()),
2050
2360
  arch: Some("x64".to_string()),
2051
- target: crate::cli::MakeTarget::Msi,
2361
+ target: Some(crate::cli::MakeTarget::Msi),
2052
2362
  skip_package: false,
2053
2363
  force: false,
2054
2364
  dry_run: true,
@@ -2069,6 +2379,79 @@ mod tests {
2069
2379
  let _ = fs::remove_dir_all(root);
2070
2380
  }
2071
2381
 
2382
+ #[test]
2383
+ fn builds_make_reports_from_configured_forge_makers() {
2384
+ let root = unique_temp_dir("configured-makers");
2385
+ write_package_json_with_makers(
2386
+ &root,
2387
+ r#"[
2388
+ {"name":"@electron-forge/maker-zip"},
2389
+ {"name":"@electron-forge/maker-deb","platforms":["linux"]},
2390
+ {"name":"@electron-forge/maker-rpm","platforms":["darwin"]},
2391
+ {"name":"@electron-forge/maker-squirrel","platforms":["linux"]}
2392
+ ]"#,
2393
+ );
2394
+ write_app_file(&root);
2395
+ write_fake_electron_dist(&root);
2396
+
2397
+ let args = MakeArgs {
2398
+ cwd: root.clone(),
2399
+ out_dir: PathBuf::from("out"),
2400
+ name: None,
2401
+ platform: Some("linux".to_string()),
2402
+ arch: Some("x64".to_string()),
2403
+ target: None,
2404
+ skip_package: false,
2405
+ force: false,
2406
+ dry_run: true,
2407
+ json: true,
2408
+ };
2409
+ let reports = build_reports(&args).expect("reports should build");
2410
+
2411
+ assert_eq!(reports.len(), 2);
2412
+ assert_eq!(reports[0].target(), "zip");
2413
+ assert_eq!(reports[1].target(), "deb");
2414
+ assert!(reports[0]
2415
+ .warnings()
2416
+ .iter()
2417
+ .any(|warning| warning.contains("@electron-forge/maker-squirrel")));
2418
+
2419
+ let _ = fs::remove_dir_all(root);
2420
+ }
2421
+
2422
+ #[test]
2423
+ fn explicit_make_target_overrides_configured_makers() {
2424
+ let root = unique_temp_dir("target-override");
2425
+ write_package_json_with_makers(
2426
+ &root,
2427
+ r#"[{"name":"@electron-forge/maker-zip"},{"name":"@electron-forge/maker-deb"}]"#,
2428
+ );
2429
+ write_app_file(&root);
2430
+ write_fake_electron_dist(&root);
2431
+
2432
+ let args = MakeArgs {
2433
+ cwd: root.clone(),
2434
+ out_dir: PathBuf::from("out"),
2435
+ name: None,
2436
+ platform: Some("win32".to_string()),
2437
+ arch: Some("x64".to_string()),
2438
+ target: Some(crate::cli::MakeTarget::Msi),
2439
+ skip_package: false,
2440
+ force: false,
2441
+ dry_run: true,
2442
+ json: true,
2443
+ };
2444
+ let report = build_report(&args).expect("report should build");
2445
+
2446
+ assert_eq!(report.target(), "msi");
2447
+ assert!(report
2448
+ .warnings()
2449
+ .iter()
2450
+ .all(|warning| !warning.contains("maker-deb")));
2451
+
2452
+ let _ = fs::remove_dir_all(root);
2453
+ }
2454
+
2072
2455
  #[test]
2073
2456
  fn makes_zip_artifact_after_packaging() {
2074
2457
  let root = unique_temp_dir("execute");
@@ -2082,7 +2465,7 @@ mod tests {
2082
2465
  name: None,
2083
2466
  platform: None,
2084
2467
  arch: None,
2085
- target: crate::cli::MakeTarget::Zip,
2468
+ target: Some(crate::cli::MakeTarget::Zip),
2086
2469
  skip_package: false,
2087
2470
  force: false,
2088
2471
  dry_run: false,
@@ -2111,6 +2494,44 @@ mod tests {
2111
2494
  let _ = fs::remove_dir_all(root);
2112
2495
  }
2113
2496
 
2497
+ #[test]
2498
+ fn makes_multiple_configured_artifacts_from_existing_package() {
2499
+ let root = unique_temp_dir("configured-execute");
2500
+ write_package_json_with_makers(
2501
+ &root,
2502
+ r#"[
2503
+ {"name":"@electron-forge/maker-zip","platforms":["win32"]},
2504
+ {"name":"@electron-forge/maker-wix","platforms":["win32"]}
2505
+ ]"#,
2506
+ );
2507
+ write_app_file(&root);
2508
+ write_fake_windows_bundle(&root.join("out/starter-app-win32-x64"), "starter-app.exe");
2509
+
2510
+ let args = MakeArgs {
2511
+ cwd: root.clone(),
2512
+ out_dir: PathBuf::from("out"),
2513
+ name: None,
2514
+ platform: Some("win32".to_string()),
2515
+ arch: Some("x64".to_string()),
2516
+ target: None,
2517
+ skip_package: true,
2518
+ force: false,
2519
+ dry_run: false,
2520
+ json: true,
2521
+ };
2522
+ let mut reports = build_reports(&args).expect("reports should build");
2523
+
2524
+ execute_make_reports(&mut reports, &args).expect("configured makers should execute");
2525
+
2526
+ assert_eq!(reports.len(), 2);
2527
+ assert_eq!(reports[0].target(), "zip");
2528
+ assert_eq!(reports[1].target(), "msi");
2529
+ assert!(Path::new(reports[0].artifact.as_str()).exists());
2530
+ assert!(Path::new(reports[1].artifact.as_str()).exists());
2531
+
2532
+ let _ = fs::remove_dir_all(root);
2533
+ }
2534
+
2114
2535
  #[test]
2115
2536
  fn writes_deb_archive_with_control_and_data_members() {
2116
2537
  let root = unique_temp_dir("deb-archive");
@@ -2124,7 +2545,7 @@ mod tests {
2124
2545
  name: None,
2125
2546
  platform: Some("linux".to_string()),
2126
2547
  arch: Some("x64".to_string()),
2127
- target: crate::cli::MakeTarget::Deb,
2548
+ target: Some(crate::cli::MakeTarget::Deb),
2128
2549
  skip_package: false,
2129
2550
  force: false,
2130
2551
  dry_run: true,
@@ -2183,7 +2604,7 @@ mod tests {
2183
2604
  name: None,
2184
2605
  platform: Some("darwin".to_string()),
2185
2606
  arch: Some("arm64".to_string()),
2186
- target: crate::cli::MakeTarget::Dmg,
2607
+ target: Some(crate::cli::MakeTarget::Dmg),
2187
2608
  skip_package: false,
2188
2609
  force: false,
2189
2610
  dry_run: true,
@@ -2251,7 +2672,7 @@ mod tests {
2251
2672
  name: None,
2252
2673
  platform: Some("linux".to_string()),
2253
2674
  arch: Some("x64".to_string()),
2254
- target: crate::cli::MakeTarget::Rpm,
2675
+ target: Some(crate::cli::MakeTarget::Rpm),
2255
2676
  skip_package: false,
2256
2677
  force: false,
2257
2678
  dry_run: true,
@@ -2310,7 +2731,7 @@ mod tests {
2310
2731
  name: None,
2311
2732
  platform: Some("win32".to_string()),
2312
2733
  arch: Some("x64".to_string()),
2313
- target: crate::cli::MakeTarget::Msi,
2734
+ target: Some(crate::cli::MakeTarget::Msi),
2314
2735
  skip_package: false,
2315
2736
  force: false,
2316
2737
  dry_run: true,
@@ -2384,7 +2805,7 @@ mod tests {
2384
2805
  name: None,
2385
2806
  platform: None,
2386
2807
  arch: None,
2387
- target: crate::cli::MakeTarget::Deb,
2808
+ target: Some(crate::cli::MakeTarget::Deb),
2388
2809
  skip_package: false,
2389
2810
  force: false,
2390
2811
  dry_run: false,
@@ -2416,7 +2837,7 @@ mod tests {
2416
2837
  name: None,
2417
2838
  platform: None,
2418
2839
  arch: None,
2419
- target: crate::cli::MakeTarget::Dmg,
2840
+ target: Some(crate::cli::MakeTarget::Dmg),
2420
2841
  skip_package: false,
2421
2842
  force: false,
2422
2843
  dry_run: false,
@@ -2448,7 +2869,7 @@ mod tests {
2448
2869
  name: None,
2449
2870
  platform: None,
2450
2871
  arch: None,
2451
- target: crate::cli::MakeTarget::Rpm,
2872
+ target: Some(crate::cli::MakeTarget::Rpm),
2452
2873
  skip_package: false,
2453
2874
  force: false,
2454
2875
  dry_run: false,
@@ -2471,6 +2892,23 @@ mod tests {
2471
2892
  .expect("package.json should be written");
2472
2893
  }
2473
2894
 
2895
+ fn write_package_json_with_makers(root: &Path, makers: &str) {
2896
+ fs::write(
2897
+ root.join("package.json"),
2898
+ format!(
2899
+ r#"{{
2900
+ "name":"starter-app",
2901
+ "version":"0.1.0",
2902
+ "license":"MIT",
2903
+ "main":"src/main.js",
2904
+ "devDependencies":{{"electron":"30.0.0"}},
2905
+ "config":{{"forge":{{"makers":{makers}}}}}
2906
+ }}"#
2907
+ ),
2908
+ )
2909
+ .expect("package.json with makers should be written");
2910
+ }
2911
+
2474
2912
  fn write_app_file(root: &Path) {
2475
2913
  fs::create_dir_all(root.join("src")).expect("src should be created");
2476
2914
  fs::write(root.join("src/main.js"), "console.log('hello');")
@@ -98,7 +98,7 @@ fn build_report(args: &PublishArgs) -> Result<PublishReport> {
98
98
  name: args.name.clone(),
99
99
  platform: args.platform.clone(),
100
100
  arch: args.arch.clone(),
101
- target: args.target,
101
+ target: Some(args.target),
102
102
  skip_package: false,
103
103
  force: args.force,
104
104
  dry_run: false,
@@ -235,7 +235,7 @@ fn execute_publish(report: &mut PublishReport, args: &PublishArgs) -> Result<()>
235
235
  name: args.name.clone(),
236
236
  platform: args.platform.clone(),
237
237
  arch: args.arch.clone(),
238
- target: args.target,
238
+ target: Some(args.target),
239
239
  skip_package: false,
240
240
  force: args.force,
241
241
  dry_run: false,
package/src/project.rs CHANGED
@@ -9,7 +9,7 @@ use camino::Utf8PathBuf;
9
9
  use serde::Serialize;
10
10
  use serde_json::Value;
11
11
 
12
- #[derive(Debug, Serialize)]
12
+ #[derive(Clone, Debug, Serialize)]
13
13
  pub struct ProjectSnapshot {
14
14
  pub root: Utf8PathBuf,
15
15
  pub package_json: Option<Utf8PathBuf>,