electron-cli 0.3.0-alpha.15 → 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.
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.15"
379
+ version = "0.3.0-alpha.16"
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.15"
3
+ version = "0.3.0-alpha.16"
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
@@ -41,7 +41,9 @@ The Rust-native flow currently owns:
41
41
 
42
42
  The GitHub publisher creates or reuses a release, uploads selected make artifacts, and can replace an existing asset with `--force`. It reads `GITHUB_TOKEN` or `GH_TOKEN` and can infer `OWNER/REPO` from package metadata, Forge GitHub publisher config, or `package.json` `repository`. You can also pass `--github-repo`.
43
43
 
44
- The DMG maker is currently a pure-Rust FAT32 image with the app bundle and an Applications entry. The MSI maker writes a compressed embedded CAB, Windows Installer database tables, and a Start Menu shortcut when the packaged executable is present. HFS+/APFS DMG layout customization, installer UI customization, Windows/Linux icon embedding, signing, and notarization are still TODO.
44
+ The package command recognizes macOS `packagerConfig.osxSign` and `packagerConfig.osxNotarize` options and reports the signing/notarization plan without serializing credential values. Actual Rust-native signing and notarization execution is not implemented yet.
45
+
46
+ The DMG maker is currently a pure-Rust FAT32 image with the app bundle and an Applications entry. The MSI maker writes a compressed embedded CAB, Windows Installer database tables, and a Start Menu shortcut when the packaged executable is present. HFS+/APFS DMG layout customization, installer UI customization, Windows/Linux icon embedding, signing execution, and notarization execution are still TODO.
45
47
 
46
48
  Package metadata can be configured in `package.json`:
47
49
 
@@ -53,7 +55,15 @@ Package metadata can be configured in `package.json`:
53
55
  "appBundleId": "com.example.my-app",
54
56
  "appCategoryType": "public.app-category.developer-tools",
55
57
  "icon": "assets/icon",
56
- "extraResource": "assets/config.json"
58
+ "extraResource": "assets/config.json",
59
+ "osxSign": {
60
+ "identity": "Developer ID Application: Example, Inc. (TEAMID1234)",
61
+ "entitlements": "assets/entitlements.plist",
62
+ "hardenedRuntime": true
63
+ },
64
+ "osxNotarize": {
65
+ "keychainProfile": "notary-profile"
66
+ }
57
67
  },
58
68
  "makers": [
59
69
  { "name": "@electron-forge/maker-zip" },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-cli",
3
- "version": "0.3.0-alpha.15",
3
+ "version": "0.3.0-alpha.16",
4
4
  "description": "Experimental Rust CLI for Electron project diagnostics and workflow automation",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -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 {
@@ -82,6 +114,35 @@ struct PackagerConfig {
82
114
  icon: Vec<String>,
83
115
  extra_resource: Vec<String>,
84
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>,
85
146
  }
86
147
 
87
148
  pub fn run(args: PackageArgs) -> Result<()> {
@@ -133,6 +194,7 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
133
194
  &app_resources_dir,
134
195
  &platform,
135
196
  )?;
197
+ let (signing, signing_warnings) = package_signing(root, &package_config, &platform)?;
136
198
 
137
199
  let mut warnings = package_config.warnings.clone();
138
200
  if snapshot.package_json.is_none() {
@@ -170,6 +232,7 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
170
232
 
171
233
  warnings.extend(runtime_dependency_warnings(root, &snapshot));
172
234
  warnings.extend(metadata_warnings);
235
+ warnings.extend(signing_warnings);
173
236
 
174
237
  let create_dirs = vec![package_root.clone(), app_resources_dir.clone()];
175
238
  let mut copy_steps = vec![
@@ -200,6 +263,7 @@ pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Res
200
263
  app_name,
201
264
  executable_name,
202
265
  metadata,
266
+ signing,
203
267
  platform,
204
268
  arch,
205
269
  electron_dist: utf8_path(electron_dist)?,
@@ -325,6 +389,33 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
325
389
  println!(" target: {} {}", report.platform, report.arch);
326
390
  println!(" status: {}", report.status.as_str());
327
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
+
328
419
  println!();
329
420
  println!("Output");
330
421
  println!(" {}", report.bundle_dir);
@@ -400,6 +491,114 @@ fn parse_packager_config(value: &JsonValue) -> PackagerConfig {
400
491
  .get("darwinDarkModeSupport")
401
492
  .and_then(JsonValue::as_bool)
402
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
+ },
403
602
  }
404
603
  }
405
604
 
@@ -473,6 +672,146 @@ fn package_metadata(
473
672
  ))
474
673
  }
475
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
+
476
815
  fn resolve_icon_resource(
477
816
  root: &Path,
478
817
  configured_icons: &[String],
@@ -1158,6 +1497,12 @@ impl PackagerConfig {
1158
1497
  }
1159
1498
  self.darwin_dark_mode_support =
1160
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
+ }
1161
1506
  }
1162
1507
  }
1163
1508
 
@@ -1423,6 +1768,130 @@ mod tests {
1423
1768
  let _ = fs::remove_dir_all(root);
1424
1769
  }
1425
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
+
1426
1895
  #[test]
1427
1896
  fn packages_macos_info_plist_metadata() {
1428
1897
  if current_platform() != "darwin" {