electron-cli 0.3.0-alpha.14 → 0.3.0-alpha.16

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.
@@ -18,6 +18,7 @@ pub(crate) struct PackageReport {
18
18
  app_name: String,
19
19
  executable_name: String,
20
20
  metadata: PackageMetadata,
21
+ signing: PackageSigningPlan,
21
22
  platform: String,
22
23
  arch: String,
23
24
  electron_dist: Utf8PathBuf,
@@ -55,6 +56,37 @@ struct IconResource {
55
56
  to: Utf8PathBuf,
56
57
  }
57
58
 
59
+ #[derive(Clone, Debug, Serialize)]
60
+ struct PackageSigningPlan {
61
+ macos: MacosSigningPlan,
62
+ }
63
+
64
+ #[derive(Clone, Debug, Serialize)]
65
+ struct MacosSigningPlan {
66
+ sign: MacosSignPlan,
67
+ notarize: MacosNotarizePlan,
68
+ }
69
+
70
+ #[derive(Clone, Debug, Serialize)]
71
+ struct MacosSignPlan {
72
+ configured: bool,
73
+ enabled: bool,
74
+ identity: Option<String>,
75
+ entitlements: Vec<Utf8PathBuf>,
76
+ entitlements_inherit: Option<Utf8PathBuf>,
77
+ hardened_runtime: Option<bool>,
78
+ gatekeeper_assess: Option<bool>,
79
+ }
80
+
81
+ #[derive(Clone, Debug, Serialize)]
82
+ struct MacosNotarizePlan {
83
+ configured: bool,
84
+ enabled: bool,
85
+ auth_method: Option<String>,
86
+ keychain_profile: Option<String>,
87
+ keychain: Option<String>,
88
+ }
89
+
58
90
  #[derive(Clone, Copy, Debug, Serialize)]
59
91
  #[serde(rename_all = "kebab-case")]
60
92
  enum PackageStatus {
@@ -67,6 +99,7 @@ struct PackageJsonConfig {
67
99
  product_name: Option<String>,
68
100
  app_version: Option<String>,
69
101
  packager: PackagerConfig,
102
+ warnings: Vec<String>,
70
103
  }
71
104
 
72
105
  #[derive(Debug, Default)]
@@ -81,6 +114,35 @@ struct PackagerConfig {
81
114
  icon: Vec<String>,
82
115
  extra_resource: Vec<String>,
83
116
  darwin_dark_mode_support: bool,
117
+ osx_sign: MacosSignConfig,
118
+ osx_notarize: MacosNotarizeConfig,
119
+ }
120
+
121
+ #[derive(Clone, Debug, Default)]
122
+ struct MacosSignConfig {
123
+ configured: bool,
124
+ enabled: bool,
125
+ invalid_type: bool,
126
+ identity: Option<String>,
127
+ entitlements: Vec<String>,
128
+ entitlements_inherit: Option<String>,
129
+ hardened_runtime: Option<bool>,
130
+ gatekeeper_assess: Option<bool>,
131
+ }
132
+
133
+ #[derive(Clone, Debug, Default)]
134
+ struct MacosNotarizeConfig {
135
+ configured: bool,
136
+ enabled: bool,
137
+ invalid_type: bool,
138
+ apple_id_set: bool,
139
+ apple_id_password_set: bool,
140
+ team_id_set: bool,
141
+ apple_api_key: Option<String>,
142
+ apple_api_key_id_set: bool,
143
+ apple_api_issuer_set: bool,
144
+ keychain_profile: Option<String>,
145
+ keychain: Option<String>,
84
146
  }
85
147
 
86
148
  pub fn run(args: PackageArgs) -> Result<()> {
@@ -132,8 +194,9 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
132
194
  &app_resources_dir,
133
195
  &platform,
134
196
  )?;
197
+ let (signing, signing_warnings) = package_signing(root, &package_config, &platform)?;
135
198
 
136
- let mut warnings = Vec::new();
199
+ let mut warnings = package_config.warnings.clone();
137
200
  if snapshot.package_json.is_none() {
138
201
  warnings.push("No package.json found.".to_string());
139
202
  }
@@ -169,6 +232,7 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
169
232
 
170
233
  warnings.extend(runtime_dependency_warnings(root, &snapshot));
171
234
  warnings.extend(metadata_warnings);
235
+ warnings.extend(signing_warnings);
172
236
 
173
237
  let create_dirs = vec![package_root.clone(), app_resources_dir.clone()];
174
238
  let mut copy_steps = vec![
@@ -199,6 +263,7 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
199
263
  app_name,
200
264
  executable_name,
201
265
  metadata,
266
+ signing,
202
267
  platform,
203
268
  arch,
204
269
  electron_dist: utf8_path(electron_dist)?,
@@ -324,6 +389,33 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
324
389
  println!(" target: {} {}", report.platform, report.arch);
325
390
  println!(" status: {}", report.status.as_str());
326
391
 
392
+ if report.signing.macos.sign.configured || report.signing.macos.notarize.configured {
393
+ println!();
394
+ println!("Signing");
395
+ println!(
396
+ " macOS signing: {}",
397
+ if report.signing.macos.sign.enabled {
398
+ "configured"
399
+ } else {
400
+ "disabled"
401
+ }
402
+ );
403
+ if let Some(identity) = &report.signing.macos.sign.identity {
404
+ println!(" identity: {identity}");
405
+ }
406
+ println!(
407
+ " macOS notarization: {}",
408
+ if report.signing.macos.notarize.enabled {
409
+ "configured"
410
+ } else {
411
+ "disabled"
412
+ }
413
+ );
414
+ if let Some(method) = &report.signing.macos.notarize.auth_method {
415
+ println!(" notarization auth: {method}");
416
+ }
417
+ }
418
+
327
419
  println!();
328
420
  println!("Output");
329
421
  println!(" {}", report.bundle_dir);
@@ -346,44 +438,40 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
346
438
  }
347
439
 
348
440
  fn read_package_json_config(snapshot: &ProjectSnapshot) -> Result<PackageJsonConfig> {
349
- let Some(package_json_path) = &snapshot.package_json else {
350
- return Ok(PackageJsonConfig::default());
351
- };
352
-
353
- let package_json_path = Path::new(package_json_path.as_str());
354
- let raw = fs::read_to_string(package_json_path)
355
- .with_context(|| format!("Could not read {}", package_json_path.display()))?;
356
- let package = serde_json::from_str::<JsonValue>(&raw)
357
- .with_context(|| format!("Could not parse {}", package_json_path.display()))?;
441
+ let project_config = crate::forge_config::read(snapshot)?;
358
442
 
359
443
  let mut packager = PackagerConfig::default();
360
- if let Some(config) = package
361
- .get("config")
362
- .and_then(|config| config.get("forge"))
444
+ if let Some(config) = project_config
445
+ .forge()
363
446
  .and_then(|forge| forge.get("packagerConfig"))
364
447
  {
365
448
  packager.merge(parse_packager_config(config));
366
449
  }
367
- if let Some(config) = package.get("electronPackagerConfig") {
450
+ if let Some(config) = project_config
451
+ .package()
452
+ .and_then(|package| package.get("electronPackagerConfig"))
453
+ {
368
454
  packager.merge(parse_packager_config(config));
369
455
  }
370
- if let Some(config) = package
371
- .get("electronCli")
372
- .or_else(|| package.get("electron-cli"))
456
+ if let Some(config) = project_config
457
+ .electron_cli()
373
458
  .and_then(|config| config.get("packagerConfig"))
374
459
  {
375
460
  packager.merge(parse_packager_config(config));
376
461
  }
377
462
 
378
463
  Ok(PackageJsonConfig {
379
- product_name: package
380
- .get("productName")
464
+ product_name: project_config
465
+ .package()
466
+ .and_then(|package| package.get("productName"))
381
467
  .and_then(JsonValue::as_str)
382
468
  .map(ToOwned::to_owned),
383
- app_version: package
384
- .get("version")
469
+ app_version: project_config
470
+ .package()
471
+ .and_then(|package| package.get("version"))
385
472
  .and_then(JsonValue::as_str)
386
473
  .map(ToOwned::to_owned),
474
+ warnings: project_config.warnings().to_vec(),
387
475
  packager,
388
476
  })
389
477
  }
@@ -403,6 +491,114 @@ fn parse_packager_config(value: &JsonValue) -> PackagerConfig {
403
491
  .get("darwinDarkModeSupport")
404
492
  .and_then(JsonValue::as_bool)
405
493
  .unwrap_or(false),
494
+ osx_sign: parse_macos_sign_config(value.get("osxSign")),
495
+ osx_notarize: parse_macos_notarize_config(value.get("osxNotarize")),
496
+ }
497
+ }
498
+
499
+ fn parse_macos_sign_config(value: Option<&JsonValue>) -> MacosSignConfig {
500
+ match value {
501
+ None => MacosSignConfig::default(),
502
+ Some(JsonValue::Bool(false)) => MacosSignConfig {
503
+ configured: true,
504
+ enabled: false,
505
+ ..MacosSignConfig::default()
506
+ },
507
+ Some(JsonValue::Bool(true)) => MacosSignConfig {
508
+ configured: true,
509
+ enabled: true,
510
+ ..MacosSignConfig::default()
511
+ },
512
+ Some(JsonValue::Object(object)) => {
513
+ let entitlements = [
514
+ "entitlements",
515
+ "entitlementsInherit",
516
+ "entitlementsLoginHelper",
517
+ ]
518
+ .iter()
519
+ .filter_map(|key| {
520
+ object
521
+ .get(*key)
522
+ .and_then(JsonValue::as_str)
523
+ .map(ToOwned::to_owned)
524
+ })
525
+ .collect();
526
+
527
+ MacosSignConfig {
528
+ configured: true,
529
+ enabled: true,
530
+ invalid_type: false,
531
+ identity: object
532
+ .get("identity")
533
+ .or_else(|| object.get("identityName"))
534
+ .and_then(JsonValue::as_str)
535
+ .map(ToOwned::to_owned),
536
+ entitlements,
537
+ entitlements_inherit: object
538
+ .get("entitlementsInherit")
539
+ .and_then(JsonValue::as_str)
540
+ .map(ToOwned::to_owned),
541
+ hardened_runtime: object.get("hardenedRuntime").and_then(JsonValue::as_bool),
542
+ gatekeeper_assess: object.get("gatekeeperAssess").and_then(JsonValue::as_bool),
543
+ }
544
+ }
545
+ Some(_) => MacosSignConfig {
546
+ configured: true,
547
+ invalid_type: true,
548
+ ..MacosSignConfig::default()
549
+ },
550
+ }
551
+ }
552
+
553
+ fn parse_macos_notarize_config(value: Option<&JsonValue>) -> MacosNotarizeConfig {
554
+ match value {
555
+ None => MacosNotarizeConfig::default(),
556
+ Some(JsonValue::Bool(false)) => MacosNotarizeConfig {
557
+ configured: true,
558
+ enabled: false,
559
+ ..MacosNotarizeConfig::default()
560
+ },
561
+ Some(JsonValue::Bool(true)) => MacosNotarizeConfig {
562
+ configured: true,
563
+ enabled: true,
564
+ ..MacosNotarizeConfig::default()
565
+ },
566
+ Some(JsonValue::Object(object)) => MacosNotarizeConfig {
567
+ configured: true,
568
+ enabled: true,
569
+ invalid_type: false,
570
+ apple_id_set: object.get("appleId").and_then(JsonValue::as_str).is_some(),
571
+ apple_id_password_set: object
572
+ .get("appleIdPassword")
573
+ .and_then(JsonValue::as_str)
574
+ .is_some(),
575
+ team_id_set: object.get("teamId").and_then(JsonValue::as_str).is_some(),
576
+ apple_api_key: object
577
+ .get("appleApiKey")
578
+ .and_then(JsonValue::as_str)
579
+ .map(ToOwned::to_owned),
580
+ apple_api_key_id_set: object
581
+ .get("appleApiKeyId")
582
+ .and_then(JsonValue::as_str)
583
+ .is_some(),
584
+ apple_api_issuer_set: object
585
+ .get("appleApiIssuer")
586
+ .and_then(JsonValue::as_str)
587
+ .is_some(),
588
+ keychain_profile: object
589
+ .get("keychainProfile")
590
+ .and_then(JsonValue::as_str)
591
+ .map(ToOwned::to_owned),
592
+ keychain: object
593
+ .get("keychain")
594
+ .and_then(JsonValue::as_str)
595
+ .map(ToOwned::to_owned),
596
+ },
597
+ Some(_) => MacosNotarizeConfig {
598
+ configured: true,
599
+ invalid_type: true,
600
+ ..MacosNotarizeConfig::default()
601
+ },
406
602
  }
407
603
  }
408
604
 
@@ -476,6 +672,146 @@ fn package_metadata(
476
672
  ))
477
673
  }
478
674
 
675
+ fn package_signing(
676
+ root: &Path,
677
+ config: &PackageJsonConfig,
678
+ platform: &str,
679
+ ) -> Result<(PackageSigningPlan, Vec<String>)> {
680
+ let mut warnings = Vec::new();
681
+ let sign = macos_sign_plan(root, &config.packager.osx_sign, platform, &mut warnings)?;
682
+ let notarize = macos_notarize_plan(root, config, platform, &mut warnings);
683
+
684
+ Ok((
685
+ PackageSigningPlan {
686
+ macos: MacosSigningPlan { sign, notarize },
687
+ },
688
+ warnings,
689
+ ))
690
+ }
691
+
692
+ fn macos_sign_plan(
693
+ root: &Path,
694
+ config: &MacosSignConfig,
695
+ platform: &str,
696
+ warnings: &mut Vec<String>,
697
+ ) -> Result<MacosSignPlan> {
698
+ if config.invalid_type {
699
+ warnings.push("packagerConfig.osxSign must be false, true, or an object.".to_string());
700
+ }
701
+
702
+ let entitlements = config
703
+ .entitlements
704
+ .iter()
705
+ .filter(|path| !path.trim().is_empty())
706
+ .map(|path| {
707
+ let resolved = resolve_project_path(root, path);
708
+ if !resolved.exists() {
709
+ warnings.push(format!(
710
+ "Configured macOS entitlements file does not exist: {}.",
711
+ resolved.display()
712
+ ));
713
+ }
714
+ utf8_path(resolved)
715
+ })
716
+ .collect::<Result<Vec<_>>>()?;
717
+ let entitlements_inherit = config
718
+ .entitlements_inherit
719
+ .as_deref()
720
+ .filter(|path| !path.trim().is_empty())
721
+ .map(|path| utf8_path(resolve_project_path(root, path)))
722
+ .transpose()?;
723
+
724
+ if config.configured && platform != "darwin" {
725
+ warnings.push(format!(
726
+ "macOS signing is configured but ignored for target platform {platform}."
727
+ ));
728
+ } else if config.enabled {
729
+ warnings.push(
730
+ "macOS signing is configured, but Rust-native signing is not implemented yet; package output will be unsigned.".to_string(),
731
+ );
732
+ }
733
+
734
+ Ok(MacosSignPlan {
735
+ configured: config.configured,
736
+ enabled: config.enabled,
737
+ identity: config.identity.clone(),
738
+ entitlements,
739
+ entitlements_inherit,
740
+ hardened_runtime: config.hardened_runtime,
741
+ gatekeeper_assess: config.gatekeeper_assess,
742
+ })
743
+ }
744
+
745
+ fn macos_notarize_plan(
746
+ root: &Path,
747
+ package_config: &PackageJsonConfig,
748
+ platform: &str,
749
+ warnings: &mut Vec<String>,
750
+ ) -> MacosNotarizePlan {
751
+ let config = &package_config.packager.osx_notarize;
752
+ if config.invalid_type {
753
+ warnings.push("packagerConfig.osxNotarize must be false, true, or an object.".to_string());
754
+ }
755
+
756
+ let auth_method = macos_notarize_auth_method(config);
757
+ if config.configured && platform != "darwin" {
758
+ warnings.push(format!(
759
+ "macOS notarization is configured but ignored for target platform {platform}."
760
+ ));
761
+ } else if config.enabled {
762
+ warnings.push(
763
+ "macOS notarization is configured, but Rust-native notarization is not implemented yet.".to_string(),
764
+ );
765
+ }
766
+
767
+ if config.enabled && !package_config.packager.osx_sign.enabled {
768
+ warnings.push(
769
+ "macOS notarization requires packagerConfig.osxSign to be enabled first.".to_string(),
770
+ );
771
+ }
772
+ if config.enabled && auth_method.is_none() {
773
+ warnings.push(
774
+ "macOS notarization config is missing a complete notarytool authentication set: appleId/appleIdPassword/teamId, appleApiKey/appleApiKeyId/appleApiIssuer, or keychainProfile.".to_string(),
775
+ );
776
+ }
777
+ if let Some(api_key) = &config.apple_api_key {
778
+ let path = resolve_project_path(root, api_key);
779
+ if !path.exists() {
780
+ warnings.push(format!(
781
+ "Configured Apple API key file does not exist: {}.",
782
+ path.display()
783
+ ));
784
+ }
785
+ }
786
+
787
+ MacosNotarizePlan {
788
+ configured: config.configured,
789
+ enabled: config.enabled,
790
+ auth_method,
791
+ keychain_profile: config.keychain_profile.clone(),
792
+ keychain: config.keychain.clone(),
793
+ }
794
+ }
795
+
796
+ fn macos_notarize_auth_method(config: &MacosNotarizeConfig) -> Option<String> {
797
+ if config
798
+ .keychain_profile
799
+ .as_deref()
800
+ .is_some_and(|value| !value.trim().is_empty())
801
+ {
802
+ Some("keychain-profile".to_string())
803
+ } else if config.apple_api_key.is_some()
804
+ && config.apple_api_key_id_set
805
+ && config.apple_api_issuer_set
806
+ {
807
+ Some("app-store-connect-api-key".to_string())
808
+ } else if config.apple_id_set && config.apple_id_password_set && config.team_id_set {
809
+ Some("apple-id".to_string())
810
+ } else {
811
+ None
812
+ }
813
+ }
814
+
479
815
  fn resolve_icon_resource(
480
816
  root: &Path,
481
817
  configured_icons: &[String],
@@ -1161,6 +1497,12 @@ impl PackagerConfig {
1161
1497
  }
1162
1498
  self.darwin_dark_mode_support =
1163
1499
  other.darwin_dark_mode_support || self.darwin_dark_mode_support;
1500
+ if other.osx_sign.configured {
1501
+ self.osx_sign = other.osx_sign;
1502
+ }
1503
+ if other.osx_notarize.configured {
1504
+ self.osx_notarize = other.osx_notarize;
1505
+ }
1164
1506
  }
1165
1507
  }
1166
1508
 
@@ -1380,6 +1722,176 @@ mod tests {
1380
1722
  let _ = fs::remove_dir_all(root);
1381
1723
  }
1382
1724
 
1725
+ #[test]
1726
+ fn plans_packager_metadata_from_forge_config_js() {
1727
+ let root = unique_temp_dir("forge-config-metadata");
1728
+ write_package_json(&root);
1729
+ fs::write(
1730
+ root.join("forge.config.js"),
1731
+ r#"
1732
+ module.exports = {
1733
+ packagerConfig: {
1734
+ name: 'Forge Config App',
1735
+ executableName: 'ForgeExec',
1736
+ appBundleId: 'com.example.forge-config',
1737
+ },
1738
+ };
1739
+ "#,
1740
+ )
1741
+ .expect("forge config should be written");
1742
+ write_app_file(&root);
1743
+ write_fake_electron_dist(&root);
1744
+
1745
+ let args = PackageArgs {
1746
+ cwd: root.clone(),
1747
+ out_dir: PathBuf::from("out"),
1748
+ name: None,
1749
+ platform: None,
1750
+ arch: None,
1751
+ force: false,
1752
+ dry_run: true,
1753
+ json: true,
1754
+ };
1755
+ let snapshot = crate::project::inspect(&root).expect("project should inspect");
1756
+ let report = build_report(snapshot, &args).expect("report should build");
1757
+
1758
+ assert_eq!(report.app_name, "Forge Config App");
1759
+ assert_eq!(
1760
+ report.executable_name,
1761
+ executable_name("ForgeExec", &report.platform)
1762
+ );
1763
+ assert_eq!(
1764
+ report.metadata.bundle_identifier,
1765
+ "com.example.forge-config"
1766
+ );
1767
+
1768
+ let _ = fs::remove_dir_all(root);
1769
+ }
1770
+
1771
+ #[test]
1772
+ fn plans_macos_signing_and_notarization_without_serializing_secrets() {
1773
+ let root = unique_temp_dir("macos-signing-plan");
1774
+ write_package_json(&root);
1775
+ fs::write(root.join("entitlements.plist"), "<plist></plist>")
1776
+ .expect("entitlements should be written");
1777
+ fs::write(root.join("AuthKey_TEST.p8"), "secret api key")
1778
+ .expect("api key should be written");
1779
+ fs::write(
1780
+ root.join("forge.config.js"),
1781
+ r#"
1782
+ module.exports = {
1783
+ packagerConfig: {
1784
+ osxSign: {
1785
+ identity: 'Developer ID Application: Example, Inc. (TEAMID1234)',
1786
+ entitlements: 'entitlements.plist',
1787
+ entitlementsInherit: 'entitlements.plist',
1788
+ hardenedRuntime: true,
1789
+ gatekeeperAssess: false,
1790
+ },
1791
+ osxNotarize: {
1792
+ appleApiKey: 'AuthKey_TEST.p8',
1793
+ appleApiKeyId: 'SECRET_KEY_ID',
1794
+ appleApiIssuer: 'SECRET_ISSUER_ID',
1795
+ },
1796
+ },
1797
+ };
1798
+ "#,
1799
+ )
1800
+ .expect("forge config should be written");
1801
+ write_app_file(&root);
1802
+ write_fake_electron_dist(&root);
1803
+
1804
+ let args = PackageArgs {
1805
+ cwd: root.clone(),
1806
+ out_dir: PathBuf::from("out"),
1807
+ name: None,
1808
+ platform: Some("darwin".to_string()),
1809
+ arch: Some("arm64".to_string()),
1810
+ force: false,
1811
+ dry_run: true,
1812
+ json: true,
1813
+ };
1814
+ let snapshot = crate::project::inspect(&root).expect("project should inspect");
1815
+ let report = build_report(snapshot, &args).expect("report should build");
1816
+
1817
+ assert!(report.signing.macos.sign.configured);
1818
+ assert!(report.signing.macos.sign.enabled);
1819
+ assert_eq!(
1820
+ report.signing.macos.sign.identity.as_deref(),
1821
+ Some("Developer ID Application: Example, Inc. (TEAMID1234)")
1822
+ );
1823
+ assert_eq!(report.signing.macos.sign.hardened_runtime, Some(true));
1824
+ assert_eq!(report.signing.macos.sign.gatekeeper_assess, Some(false));
1825
+ assert_eq!(report.signing.macos.sign.entitlements.len(), 2);
1826
+ assert!(report.signing.macos.notarize.configured);
1827
+ assert_eq!(
1828
+ report.signing.macos.notarize.auth_method.as_deref(),
1829
+ Some("app-store-connect-api-key")
1830
+ );
1831
+ assert!(report
1832
+ .warnings
1833
+ .iter()
1834
+ .any(|warning| warning.contains("Rust-native signing is not implemented")));
1835
+ assert!(report
1836
+ .warnings
1837
+ .iter()
1838
+ .any(|warning| warning.contains("Rust-native notarization is not implemented")));
1839
+
1840
+ let json = serde_json::to_string(&report).expect("report should serialize");
1841
+ assert!(!json.contains("SECRET_KEY_ID"));
1842
+ assert!(!json.contains("SECRET_ISSUER_ID"));
1843
+ assert!(!json.contains("secret api key"));
1844
+
1845
+ let _ = fs::remove_dir_all(root);
1846
+ }
1847
+
1848
+ #[test]
1849
+ fn warns_when_macos_notarization_is_configured_without_signing() {
1850
+ let root = unique_temp_dir("notarize-without-sign");
1851
+ write_package_json(&root);
1852
+ fs::write(
1853
+ root.join("forge.config.js"),
1854
+ r#"
1855
+ module.exports = {
1856
+ packagerConfig: {
1857
+ osxSign: false,
1858
+ osxNotarize: {
1859
+ keychainProfile: 'notary-profile',
1860
+ },
1861
+ },
1862
+ };
1863
+ "#,
1864
+ )
1865
+ .expect("forge config should be written");
1866
+ write_app_file(&root);
1867
+ write_fake_electron_dist(&root);
1868
+
1869
+ let args = PackageArgs {
1870
+ cwd: root.clone(),
1871
+ out_dir: PathBuf::from("out"),
1872
+ name: None,
1873
+ platform: Some("darwin".to_string()),
1874
+ arch: Some("arm64".to_string()),
1875
+ force: false,
1876
+ dry_run: true,
1877
+ json: true,
1878
+ };
1879
+ let snapshot = crate::project::inspect(&root).expect("project should inspect");
1880
+ let report = build_report(snapshot, &args).expect("report should build");
1881
+
1882
+ assert!(report.signing.macos.sign.configured);
1883
+ assert!(!report.signing.macos.sign.enabled);
1884
+ assert_eq!(
1885
+ report.signing.macos.notarize.auth_method.as_deref(),
1886
+ Some("keychain-profile")
1887
+ );
1888
+ assert!(report.warnings.iter().any(|warning| {
1889
+ warning.contains("macOS notarization requires packagerConfig.osxSign")
1890
+ }));
1891
+
1892
+ let _ = fs::remove_dir_all(root);
1893
+ }
1894
+
1383
1895
  #[test]
1384
1896
  fn packages_macos_info_plist_metadata() {
1385
1897
  if current_platform() != "darwin" {