electron-cli 0.3.0-alpha.19 → 0.3.0-alpha.20
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 +1050 -72
- package/Cargo.toml +3 -2
- package/README.md +7 -4
- package/package.json +1 -1
- package/src/commands/package.rs +294 -23
package/Cargo.toml
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[package]
|
|
2
2
|
name = "electron-cli"
|
|
3
|
-
version = "0.3.0-alpha.
|
|
3
|
+
version = "0.3.0-alpha.20"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
|
|
6
6
|
license = "MIT"
|
|
@@ -8,7 +8,8 @@ repository = "https://github.com/Ikana/electron-cli"
|
|
|
8
8
|
|
|
9
9
|
[dependencies]
|
|
10
10
|
anyhow = "1.0"
|
|
11
|
-
|
|
11
|
+
app-store-connect = "0.7"
|
|
12
|
+
apple-codesign = { version = "0.29", default-features = false, features = ["notarize"] }
|
|
12
13
|
apple-dmg = "0.5"
|
|
13
14
|
cab = "0.6"
|
|
14
15
|
camino = { version = "1.1", features = ["serde1"] }
|
package/README.md
CHANGED
|
@@ -41,9 +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 package command recognizes macOS `packagerConfig.osxSign` and `packagerConfig.osxNotarize` options and reports the signing/notarization plan without serializing credential values. When `osxSign` is enabled on macOS and no certificate identity is configured, or the identity is `"-"`, `package` writes an experimental Rust-native ad-hoc signature for the generated `.app` bundle. When `osxSign.p12File` points at a `.p12`/PFX certificate export, `package` can sign the bundle with that certificate and can request a CMS timestamp token for notarization-compatible signatures. macOS keychain identity lookup and notarization
|
|
44
|
+
The package command recognizes macOS `packagerConfig.osxSign` and `packagerConfig.osxNotarize` options and reports the signing/notarization plan without serializing credential values. When `osxSign` is enabled on macOS and no certificate identity is configured, or the identity is `"-"`, `package` writes an experimental Rust-native ad-hoc signature for the generated `.app` bundle. When `osxSign.p12File` points at a `.p12`/PFX certificate export, `package` can sign the bundle with that certificate and can request a CMS timestamp token for notarization-compatible signatures. With p12 signing and App Store Connect API key auth, `package` can submit to Apple notarization, wait for the result, and staple the ticket natively in Rust. macOS keychain identity lookup and keychain/Apple ID notarization auth are not implemented yet.
|
|
45
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, macOS keychain signing, and notarization
|
|
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, macOS keychain signing, and additional notarization auth modes are still TODO.
|
|
47
47
|
|
|
48
48
|
Package metadata can be configured in `package.json`:
|
|
49
49
|
|
|
@@ -64,7 +64,10 @@ Package metadata can be configured in `package.json`:
|
|
|
64
64
|
"hardenedRuntime": true
|
|
65
65
|
},
|
|
66
66
|
"osxNotarize": {
|
|
67
|
-
"
|
|
67
|
+
"appleApiKey": "certs/AuthKey_ABC123DEFG.p8",
|
|
68
|
+
"appleApiKeyId": "ABC123DEFG",
|
|
69
|
+
"appleApiIssuer": "00000000-0000-0000-0000-000000000000",
|
|
70
|
+
"maxWaitSeconds": 600
|
|
68
71
|
}
|
|
69
72
|
},
|
|
70
73
|
"makers": [
|
|
@@ -99,7 +102,7 @@ Package metadata can be configured in `package.json`:
|
|
|
99
102
|
}
|
|
100
103
|
```
|
|
101
104
|
|
|
102
|
-
Use `p12PasswordEnv`, `p12PasswordFile`, or `p12Password` for the `.p12` password; password values are not serialized in package reports. Set `osxSign.timestamp` to a timestamp server URL, `true` for Apple's default `http://timestamp.apple.com/ts01`, or `"none"` / `false` to disable timestamping. When `osxNotarize` is enabled with p12 signing, `electron-cli` automatically enables notarization-compatible signing and uses Apple's timestamp server unless timestamping is disabled explicitly. Set `identity` to a Developer ID certificate name when you want the plan to reflect Forge-style keychain release signing, but this project will report it as not executable until Rust-native keychain lookup exists. Use `identity: "-"` or omit `identity` for the current ad-hoc signing path.
|
|
105
|
+
Use `p12PasswordEnv`, `p12PasswordFile`, or `p12Password` for the `.p12` password; password values are not serialized in package reports. Set `osxSign.timestamp` to a timestamp server URL, `true` for Apple's default `http://timestamp.apple.com/ts01`, or `"none"` / `false` to disable timestamping. When `osxNotarize` is enabled with p12 signing, `electron-cli` automatically enables notarization-compatible signing and uses Apple's timestamp server unless timestamping is disabled explicitly. Rust-native notarization execution currently requires `appleApiKey`, `appleApiKeyId`, and `appleApiIssuer`; `appleApiKey` may point at the `.p8` file from App Store Connect or a unified API key JSON file. It waits up to `maxWaitSeconds` or 600 seconds by default and staples by default. Set `staple: false` to skip stapling, or set both `staple: false` and `wait: false` to submit without waiting. Set `identity` to a Developer ID certificate name when you want the plan to reflect Forge-style keychain release signing, but this project will report it as not executable until Rust-native keychain lookup exists. Use `identity: "-"` or omit `identity` for the current ad-hoc signing path.
|
|
103
106
|
|
|
104
107
|
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. The publish command maps JSON-shaped publisher names to local and GitHub.
|
|
105
108
|
|
package/package.json
CHANGED
package/src/commands/package.rs
CHANGED
|
@@ -5,9 +5,12 @@ use std::{
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
use anyhow::{bail, Context, Result};
|
|
8
|
+
use app_store_connect::UnifiedApiKey;
|
|
8
9
|
use apple_codesign::{
|
|
9
10
|
cryptography::{parse_pfx_data, PrivateKey},
|
|
10
|
-
|
|
11
|
+
stapling::Stapler,
|
|
12
|
+
BundleSigner, CodeSignatureFlags, NotarizationUpload, Notarizer, SettingsScope,
|
|
13
|
+
SigningSettings,
|
|
11
14
|
};
|
|
12
15
|
use camino::Utf8PathBuf;
|
|
13
16
|
use plist::{Dictionary as PlistDictionary, Value as PlistValue};
|
|
@@ -17,6 +20,7 @@ use serde_json::Value as JsonValue;
|
|
|
17
20
|
use crate::{cli::PackageArgs, output, project::ProjectSnapshot};
|
|
18
21
|
|
|
19
22
|
const APPLE_TIMESTAMP_URL: &str = "http://timestamp.apple.com/ts01";
|
|
23
|
+
const MACOS_NOTARIZATION_WAIT_TIMEOUT_SECONDS: u64 = 600;
|
|
20
24
|
|
|
21
25
|
#[derive(Clone, Debug, Serialize)]
|
|
22
26
|
pub(crate) struct PackageReport {
|
|
@@ -98,9 +102,18 @@ struct MacosSignPlan {
|
|
|
98
102
|
struct MacosNotarizePlan {
|
|
99
103
|
configured: bool,
|
|
100
104
|
enabled: bool,
|
|
105
|
+
will_execute: bool,
|
|
101
106
|
auth_method: Option<String>,
|
|
107
|
+
apple_api_key: Option<Utf8PathBuf>,
|
|
108
|
+
#[serde(skip)]
|
|
109
|
+
apple_api_key_id: RedactedSecret,
|
|
110
|
+
#[serde(skip)]
|
|
111
|
+
apple_api_issuer: RedactedSecret,
|
|
102
112
|
keychain_profile: Option<String>,
|
|
103
113
|
keychain: Option<String>,
|
|
114
|
+
wait: bool,
|
|
115
|
+
wait_timeout_seconds: u64,
|
|
116
|
+
staple: bool,
|
|
104
117
|
}
|
|
105
118
|
|
|
106
119
|
#[derive(Clone, Default)]
|
|
@@ -190,10 +203,13 @@ struct MacosNotarizeConfig {
|
|
|
190
203
|
apple_id_password_set: bool,
|
|
191
204
|
team_id_set: bool,
|
|
192
205
|
apple_api_key: Option<String>,
|
|
193
|
-
|
|
194
|
-
|
|
206
|
+
apple_api_key_id: Option<String>,
|
|
207
|
+
apple_api_issuer: Option<String>,
|
|
195
208
|
keychain_profile: Option<String>,
|
|
196
209
|
keychain: Option<String>,
|
|
210
|
+
wait: Option<bool>,
|
|
211
|
+
wait_timeout_seconds: Option<u64>,
|
|
212
|
+
staple: Option<bool>,
|
|
197
213
|
}
|
|
198
214
|
|
|
199
215
|
pub fn run(args: PackageArgs) -> Result<()> {
|
|
@@ -415,6 +431,7 @@ pub(crate) fn execute_package(report: &PackageReport, force: bool) -> Result<()>
|
|
|
415
431
|
&report.project,
|
|
416
432
|
)?;
|
|
417
433
|
execute_macos_signing(report)?;
|
|
434
|
+
execute_macos_notarization(report)?;
|
|
418
435
|
|
|
419
436
|
Ok(())
|
|
420
437
|
}
|
|
@@ -513,6 +530,106 @@ fn execute_macos_signing(report: &PackageReport) -> Result<()> {
|
|
|
513
530
|
Ok(())
|
|
514
531
|
}
|
|
515
532
|
|
|
533
|
+
fn execute_macos_notarization(report: &PackageReport) -> Result<()> {
|
|
534
|
+
if report.platform != "darwin" || !report.signing.macos.notarize.will_execute {
|
|
535
|
+
return Ok(());
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
let notarize = &report.signing.macos.notarize;
|
|
539
|
+
let bundle_dir = Path::new(report.bundle_dir.as_str());
|
|
540
|
+
let wait_limit = notarize.wait.then_some(std::time::Duration::from_secs(
|
|
541
|
+
notarize.wait_timeout_seconds,
|
|
542
|
+
));
|
|
543
|
+
let notarizer = macos_notarizer(notarize)?;
|
|
544
|
+
let upload = notarizer
|
|
545
|
+
.notarize_path(bundle_dir, wait_limit)
|
|
546
|
+
.with_context(|| format!("Could not notarize macOS bundle {}", bundle_dir.display()))?;
|
|
547
|
+
|
|
548
|
+
if notarize.staple {
|
|
549
|
+
match upload {
|
|
550
|
+
NotarizationUpload::NotaryResponse(_) => {
|
|
551
|
+
let stapler =
|
|
552
|
+
Stapler::new().context("Could not prepare macOS notarization stapler")?;
|
|
553
|
+
stapler.staple_path(bundle_dir).with_context(|| {
|
|
554
|
+
format!(
|
|
555
|
+
"Could not staple notarization ticket to {}",
|
|
556
|
+
bundle_dir.display()
|
|
557
|
+
)
|
|
558
|
+
})?;
|
|
559
|
+
}
|
|
560
|
+
NotarizationUpload::UploadId(upload_id) => {
|
|
561
|
+
bail!(
|
|
562
|
+
"macOS notarization upload {upload_id} was submitted without waiting; stapling requires a completed notarization result."
|
|
563
|
+
);
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
Ok(())
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
fn macos_notarizer(notarize: &MacosNotarizePlan) -> Result<Notarizer> {
|
|
572
|
+
let api_key_path = notarize
|
|
573
|
+
.apple_api_key
|
|
574
|
+
.as_ref()
|
|
575
|
+
.context("macOS notarization requires appleApiKey")?;
|
|
576
|
+
let api_key_path = Path::new(api_key_path.as_str());
|
|
577
|
+
let key_id = notarize
|
|
578
|
+
.apple_api_key_id
|
|
579
|
+
.as_deref()
|
|
580
|
+
.context("macOS notarization requires appleApiKeyId")?;
|
|
581
|
+
let issuer = notarize
|
|
582
|
+
.apple_api_issuer
|
|
583
|
+
.as_deref()
|
|
584
|
+
.context("macOS notarization requires appleApiIssuer")?;
|
|
585
|
+
|
|
586
|
+
if path_extension(api_key_path) == Some("json") {
|
|
587
|
+
return Notarizer::from_api_key(api_key_path)
|
|
588
|
+
.with_context(|| format!("Could not load Apple API key {}", api_key_path.display()));
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
let temp_api_key = temporary_unified_api_key(issuer, key_id, api_key_path)?;
|
|
592
|
+
Notarizer::from_api_key(&temp_api_key.path)
|
|
593
|
+
.with_context(|| format!("Could not load Apple API key {}", api_key_path.display()))
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
struct TemporaryFile {
|
|
597
|
+
path: PathBuf,
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
impl Drop for TemporaryFile {
|
|
601
|
+
fn drop(&mut self) {
|
|
602
|
+
let _ = fs::remove_file(&self.path);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fn temporary_unified_api_key(
|
|
607
|
+
issuer: &str,
|
|
608
|
+
key_id: &str,
|
|
609
|
+
private_key_path: &Path,
|
|
610
|
+
) -> Result<TemporaryFile> {
|
|
611
|
+
let unique_suffix = std::time::SystemTime::now()
|
|
612
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
613
|
+
.context("system clock is before the Unix epoch")?
|
|
614
|
+
.as_nanos();
|
|
615
|
+
let path = std::env::temp_dir().join(format!(
|
|
616
|
+
"electron-cli-notary-key-{}-{unique_suffix}.json",
|
|
617
|
+
std::process::id()
|
|
618
|
+
));
|
|
619
|
+
let unified = UnifiedApiKey::from_ecdsa_pem_path(issuer, key_id, private_key_path)
|
|
620
|
+
.with_context(|| {
|
|
621
|
+
format!(
|
|
622
|
+
"Could not read Apple API private key {}",
|
|
623
|
+
private_key_path.display()
|
|
624
|
+
)
|
|
625
|
+
})?;
|
|
626
|
+
unified
|
|
627
|
+
.write_json_file(&path)
|
|
628
|
+
.with_context(|| format!("Could not write temporary Apple API key {}", path.display()))?;
|
|
629
|
+
|
|
630
|
+
Ok(TemporaryFile { path })
|
|
631
|
+
}
|
|
632
|
+
|
|
516
633
|
fn macos_signing_settings<'key>(report: &PackageReport) -> Result<SigningSettings<'key>> {
|
|
517
634
|
let sign = &report.signing.macos.sign;
|
|
518
635
|
let mut settings = SigningSettings::default();
|
|
@@ -651,6 +768,35 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
|
|
|
651
768
|
if let Some(method) = &report.signing.macos.notarize.auth_method {
|
|
652
769
|
println!(" notarization auth: {method}");
|
|
653
770
|
}
|
|
771
|
+
if let Some(path) = &report.signing.macos.notarize.apple_api_key {
|
|
772
|
+
println!(" Apple API key: {path}");
|
|
773
|
+
}
|
|
774
|
+
println!(
|
|
775
|
+
" notarization execution: {}",
|
|
776
|
+
if report.signing.macos.notarize.will_execute {
|
|
777
|
+
"enabled"
|
|
778
|
+
} else {
|
|
779
|
+
"not available"
|
|
780
|
+
}
|
|
781
|
+
);
|
|
782
|
+
if report.signing.macos.notarize.will_execute {
|
|
783
|
+
println!(
|
|
784
|
+
" notarization wait: {}",
|
|
785
|
+
if report.signing.macos.notarize.wait {
|
|
786
|
+
format!("{}s", report.signing.macos.notarize.wait_timeout_seconds)
|
|
787
|
+
} else {
|
|
788
|
+
"disabled".to_string()
|
|
789
|
+
}
|
|
790
|
+
);
|
|
791
|
+
println!(
|
|
792
|
+
" notarization stapling: {}",
|
|
793
|
+
if report.signing.macos.notarize.staple {
|
|
794
|
+
"enabled"
|
|
795
|
+
} else {
|
|
796
|
+
"disabled"
|
|
797
|
+
}
|
|
798
|
+
);
|
|
799
|
+
}
|
|
654
800
|
}
|
|
655
801
|
|
|
656
802
|
println!();
|
|
@@ -856,14 +1002,14 @@ fn parse_macos_notarize_config(value: Option<&JsonValue>) -> MacosNotarizeConfig
|
|
|
856
1002
|
.get("appleApiKey")
|
|
857
1003
|
.and_then(JsonValue::as_str)
|
|
858
1004
|
.map(ToOwned::to_owned),
|
|
859
|
-
|
|
1005
|
+
apple_api_key_id: object
|
|
860
1006
|
.get("appleApiKeyId")
|
|
861
1007
|
.and_then(JsonValue::as_str)
|
|
862
|
-
.
|
|
863
|
-
|
|
1008
|
+
.map(ToOwned::to_owned),
|
|
1009
|
+
apple_api_issuer: object
|
|
864
1010
|
.get("appleApiIssuer")
|
|
865
1011
|
.and_then(JsonValue::as_str)
|
|
866
|
-
.
|
|
1012
|
+
.map(ToOwned::to_owned),
|
|
867
1013
|
keychain_profile: object
|
|
868
1014
|
.get("keychainProfile")
|
|
869
1015
|
.and_then(JsonValue::as_str)
|
|
@@ -872,6 +1018,12 @@ fn parse_macos_notarize_config(value: Option<&JsonValue>) -> MacosNotarizeConfig
|
|
|
872
1018
|
.get("keychain")
|
|
873
1019
|
.and_then(JsonValue::as_str)
|
|
874
1020
|
.map(ToOwned::to_owned),
|
|
1021
|
+
wait: object.get("wait").and_then(JsonValue::as_bool),
|
|
1022
|
+
wait_timeout_seconds: object
|
|
1023
|
+
.get("maxWaitSeconds")
|
|
1024
|
+
.or_else(|| object.get("waitTimeoutSeconds"))
|
|
1025
|
+
.and_then(JsonValue::as_u64),
|
|
1026
|
+
staple: object.get("staple").and_then(JsonValue::as_bool),
|
|
875
1027
|
},
|
|
876
1028
|
Some(_) => MacosNotarizeConfig {
|
|
877
1029
|
configured: true,
|
|
@@ -964,7 +1116,7 @@ fn package_signing(
|
|
|
964
1116
|
platform,
|
|
965
1117
|
&mut warnings,
|
|
966
1118
|
)?;
|
|
967
|
-
let notarize = macos_notarize_plan(root, config, platform, &mut warnings)
|
|
1119
|
+
let notarize = macos_notarize_plan(root, config, platform, &sign, &mut warnings)?;
|
|
968
1120
|
|
|
969
1121
|
Ok((
|
|
970
1122
|
PackageSigningPlan {
|
|
@@ -1163,22 +1315,34 @@ fn macos_notarize_plan(
|
|
|
1163
1315
|
root: &Path,
|
|
1164
1316
|
package_config: &PackageJsonConfig,
|
|
1165
1317
|
platform: &str,
|
|
1318
|
+
sign: &MacosSignPlan,
|
|
1166
1319
|
warnings: &mut Vec<String>,
|
|
1167
|
-
) -> MacosNotarizePlan {
|
|
1320
|
+
) -> Result<MacosNotarizePlan> {
|
|
1168
1321
|
let config = &package_config.packager.osx_notarize;
|
|
1169
1322
|
if config.invalid_type {
|
|
1170
1323
|
warnings.push("packagerConfig.osxNotarize must be false, true, or an object.".to_string());
|
|
1171
1324
|
}
|
|
1172
1325
|
|
|
1173
1326
|
let auth_method = macos_notarize_auth_method(config);
|
|
1327
|
+
let apple_api_key = config
|
|
1328
|
+
.apple_api_key
|
|
1329
|
+
.as_deref()
|
|
1330
|
+
.filter(|path| !path.trim().is_empty())
|
|
1331
|
+
.map(|path| utf8_path(resolve_project_path(root, path)))
|
|
1332
|
+
.transpose()?;
|
|
1333
|
+
let api_key_auth = auth_method.as_deref() == Some("app-store-connect-api-key");
|
|
1334
|
+
let staple = config.staple.unwrap_or(true);
|
|
1335
|
+
let wait = staple || config.wait.unwrap_or(true);
|
|
1336
|
+
let wait_timeout_seconds = config
|
|
1337
|
+
.wait_timeout_seconds
|
|
1338
|
+
.unwrap_or(MACOS_NOTARIZATION_WAIT_TIMEOUT_SECONDS);
|
|
1339
|
+
let will_execute =
|
|
1340
|
+
config.enabled && platform == "darwin" && sign.for_notarization && api_key_auth;
|
|
1341
|
+
|
|
1174
1342
|
if config.configured && platform != "darwin" {
|
|
1175
1343
|
warnings.push(format!(
|
|
1176
1344
|
"macOS notarization is configured but ignored for target platform {platform}."
|
|
1177
1345
|
));
|
|
1178
|
-
} else if config.enabled {
|
|
1179
|
-
warnings.push(
|
|
1180
|
-
"macOS notarization is configured, but Rust-native notarization is not implemented yet.".to_string(),
|
|
1181
|
-
);
|
|
1182
1346
|
}
|
|
1183
1347
|
|
|
1184
1348
|
if config.enabled && !package_config.packager.osx_sign.enabled {
|
|
@@ -1204,28 +1368,53 @@ fn macos_notarize_plan(
|
|
|
1204
1368
|
"macOS notarization requires a Developer ID signature; Rust-native ad-hoc signing is not notarizable.".to_string(),
|
|
1205
1369
|
);
|
|
1206
1370
|
}
|
|
1371
|
+
if config.enabled
|
|
1372
|
+
&& platform == "darwin"
|
|
1373
|
+
&& package_config.packager.osx_sign.enabled
|
|
1374
|
+
&& !sign.for_notarization
|
|
1375
|
+
{
|
|
1376
|
+
warnings.push(
|
|
1377
|
+
"macOS notarization execution requires Rust-native p12File Developer ID signing with a secure timestamp.".to_string(),
|
|
1378
|
+
);
|
|
1379
|
+
}
|
|
1207
1380
|
if config.enabled && auth_method.is_none() {
|
|
1208
1381
|
warnings.push(
|
|
1209
1382
|
"macOS notarization config is missing a complete notarytool authentication set: appleId/appleIdPassword/teamId, appleApiKey/appleApiKeyId/appleApiIssuer, or keychainProfile.".to_string(),
|
|
1210
1383
|
);
|
|
1211
1384
|
}
|
|
1212
|
-
if
|
|
1213
|
-
|
|
1214
|
-
|
|
1385
|
+
if config.enabled
|
|
1386
|
+
&& platform == "darwin"
|
|
1387
|
+
&& matches!(
|
|
1388
|
+
auth_method.as_deref(),
|
|
1389
|
+
Some("keychain-profile") | Some("apple-id")
|
|
1390
|
+
)
|
|
1391
|
+
{
|
|
1392
|
+
warnings.push(
|
|
1393
|
+
"Rust-native macOS notarization execution currently requires appleApiKey, appleApiKeyId, and appleApiIssuer; keychain profile and Apple ID auth are recognized for planning only.".to_string(),
|
|
1394
|
+
);
|
|
1395
|
+
}
|
|
1396
|
+
if let Some(path) = &apple_api_key {
|
|
1397
|
+
if !Path::new(path.as_str()).exists() {
|
|
1215
1398
|
warnings.push(format!(
|
|
1216
1399
|
"Configured Apple API key file does not exist: {}.",
|
|
1217
|
-
path
|
|
1400
|
+
path
|
|
1218
1401
|
));
|
|
1219
1402
|
}
|
|
1220
1403
|
}
|
|
1221
|
-
|
|
1222
|
-
MacosNotarizePlan {
|
|
1404
|
+
Ok(MacosNotarizePlan {
|
|
1223
1405
|
configured: config.configured,
|
|
1224
1406
|
enabled: config.enabled,
|
|
1407
|
+
will_execute,
|
|
1225
1408
|
auth_method,
|
|
1409
|
+
apple_api_key,
|
|
1410
|
+
apple_api_key_id: RedactedSecret::new(config.apple_api_key_id.clone()),
|
|
1411
|
+
apple_api_issuer: RedactedSecret::new(config.apple_api_issuer.clone()),
|
|
1226
1412
|
keychain_profile: config.keychain_profile.clone(),
|
|
1227
1413
|
keychain: config.keychain.clone(),
|
|
1228
|
-
|
|
1414
|
+
wait,
|
|
1415
|
+
wait_timeout_seconds,
|
|
1416
|
+
staple,
|
|
1417
|
+
})
|
|
1229
1418
|
}
|
|
1230
1419
|
|
|
1231
1420
|
fn macos_notarize_auth_method(config: &MacosNotarizeConfig) -> Option<String> {
|
|
@@ -1236,8 +1425,14 @@ fn macos_notarize_auth_method(config: &MacosNotarizeConfig) -> Option<String> {
|
|
|
1236
1425
|
{
|
|
1237
1426
|
Some("keychain-profile".to_string())
|
|
1238
1427
|
} else if config.apple_api_key.is_some()
|
|
1239
|
-
&& config
|
|
1240
|
-
|
|
1428
|
+
&& config
|
|
1429
|
+
.apple_api_key_id
|
|
1430
|
+
.as_deref()
|
|
1431
|
+
.is_some_and(|value| !value.trim().is_empty())
|
|
1432
|
+
&& config
|
|
1433
|
+
.apple_api_issuer
|
|
1434
|
+
.as_deref()
|
|
1435
|
+
.is_some_and(|value| !value.trim().is_empty())
|
|
1241
1436
|
{
|
|
1242
1437
|
Some("app-store-connect-api-key".to_string())
|
|
1243
1438
|
} else if config.apple_id_set && config.apple_id_password_set && config.team_id_set {
|
|
@@ -2265,10 +2460,12 @@ mod tests {
|
|
|
2265
2460
|
assert_eq!(report.signing.macos.sign.gatekeeper_assess, Some(false));
|
|
2266
2461
|
assert_eq!(report.signing.macos.sign.entitlements.len(), 2);
|
|
2267
2462
|
assert!(report.signing.macos.notarize.configured);
|
|
2463
|
+
assert!(!report.signing.macos.notarize.will_execute);
|
|
2268
2464
|
assert_eq!(
|
|
2269
2465
|
report.signing.macos.notarize.auth_method.as_deref(),
|
|
2270
2466
|
Some("app-store-connect-api-key")
|
|
2271
2467
|
);
|
|
2468
|
+
assert!(report.signing.macos.notarize.apple_api_key.is_some());
|
|
2272
2469
|
assert!(report
|
|
2273
2470
|
.warnings
|
|
2274
2471
|
.iter()
|
|
@@ -2276,7 +2473,7 @@ mod tests {
|
|
|
2276
2473
|
assert!(report
|
|
2277
2474
|
.warnings
|
|
2278
2475
|
.iter()
|
|
2279
|
-
.any(|warning| warning.contains("
|
|
2476
|
+
.any(|warning| warning.contains("p12File Developer ID signing")));
|
|
2280
2477
|
|
|
2281
2478
|
let json = serde_json::to_string(&report).expect("report should serialize");
|
|
2282
2479
|
assert!(!json.contains("SECRET_KEY_ID"));
|
|
@@ -2451,10 +2648,15 @@ mod tests {
|
|
|
2451
2648
|
report.signing.macos.notarize.auth_method.as_deref(),
|
|
2452
2649
|
Some("keychain-profile")
|
|
2453
2650
|
);
|
|
2651
|
+
assert!(!report.signing.macos.notarize.will_execute);
|
|
2454
2652
|
assert!(!report
|
|
2455
2653
|
.warnings
|
|
2456
2654
|
.iter()
|
|
2457
2655
|
.any(|warning| warning.contains("ad-hoc signing is not notarizable")));
|
|
2656
|
+
assert!(report
|
|
2657
|
+
.warnings
|
|
2658
|
+
.iter()
|
|
2659
|
+
.any(|warning| warning.contains("requires appleApiKey")));
|
|
2458
2660
|
|
|
2459
2661
|
let settings = macos_signing_settings(&report).expect("signing settings should build");
|
|
2460
2662
|
assert!(settings.for_notarization());
|
|
@@ -2469,6 +2671,75 @@ mod tests {
|
|
|
2469
2671
|
let _ = fs::remove_dir_all(root);
|
|
2470
2672
|
}
|
|
2471
2673
|
|
|
2674
|
+
#[test]
|
|
2675
|
+
fn plans_macos_native_notarization_execution_with_api_key_auth() {
|
|
2676
|
+
let root = unique_temp_dir("macos-native-notarization-plan");
|
|
2677
|
+
write_package_json(&root);
|
|
2678
|
+
fs::write(root.join("developer-id.p12"), "not a real p12")
|
|
2679
|
+
.expect("p12 placeholder should be written");
|
|
2680
|
+
fs::write(root.join("AuthKey_TEST.p8"), "not a real api key")
|
|
2681
|
+
.expect("api key placeholder should be written");
|
|
2682
|
+
fs::write(
|
|
2683
|
+
root.join("forge.config.js"),
|
|
2684
|
+
r#"
|
|
2685
|
+
module.exports = {
|
|
2686
|
+
packagerConfig: {
|
|
2687
|
+
appBundleId: 'com.example.native-notarized',
|
|
2688
|
+
osxSign: {
|
|
2689
|
+
p12File: 'developer-id.p12',
|
|
2690
|
+
p12Password: 'p12-secret',
|
|
2691
|
+
hardenedRuntime: true,
|
|
2692
|
+
},
|
|
2693
|
+
osxNotarize: {
|
|
2694
|
+
appleApiKey: 'AuthKey_TEST.p8',
|
|
2695
|
+
appleApiKeyId: 'SECRET_KEY_ID',
|
|
2696
|
+
appleApiIssuer: 'SECRET_ISSUER_ID',
|
|
2697
|
+
maxWaitSeconds: 120,
|
|
2698
|
+
},
|
|
2699
|
+
},
|
|
2700
|
+
};
|
|
2701
|
+
"#,
|
|
2702
|
+
)
|
|
2703
|
+
.expect("forge config should be written");
|
|
2704
|
+
write_app_file(&root);
|
|
2705
|
+
write_fake_electron_dist(&root);
|
|
2706
|
+
|
|
2707
|
+
let args = PackageArgs {
|
|
2708
|
+
cwd: root.clone(),
|
|
2709
|
+
out_dir: PathBuf::from("out"),
|
|
2710
|
+
name: None,
|
|
2711
|
+
platform: Some("darwin".to_string()),
|
|
2712
|
+
arch: Some("arm64".to_string()),
|
|
2713
|
+
force: false,
|
|
2714
|
+
dry_run: true,
|
|
2715
|
+
json: true,
|
|
2716
|
+
};
|
|
2717
|
+
let snapshot = crate::project::inspect(&root).expect("project should inspect");
|
|
2718
|
+
let report = build_report(snapshot, &args).expect("report should build");
|
|
2719
|
+
|
|
2720
|
+
assert!(report.signing.macos.sign.for_notarization);
|
|
2721
|
+
assert!(report.signing.macos.notarize.will_execute);
|
|
2722
|
+
assert_eq!(
|
|
2723
|
+
report.signing.macos.notarize.auth_method.as_deref(),
|
|
2724
|
+
Some("app-store-connect-api-key")
|
|
2725
|
+
);
|
|
2726
|
+
assert!(report.signing.macos.notarize.wait);
|
|
2727
|
+
assert_eq!(report.signing.macos.notarize.wait_timeout_seconds, 120);
|
|
2728
|
+
assert!(report.signing.macos.notarize.staple);
|
|
2729
|
+
assert!(!report
|
|
2730
|
+
.warnings
|
|
2731
|
+
.iter()
|
|
2732
|
+
.any(|warning| warning.contains("not implemented")));
|
|
2733
|
+
|
|
2734
|
+
let json = serde_json::to_string(&report).expect("report should serialize");
|
|
2735
|
+
assert!(!json.contains("SECRET_KEY_ID"));
|
|
2736
|
+
assert!(!json.contains("SECRET_ISSUER_ID"));
|
|
2737
|
+
assert!(!json.contains("p12-secret"));
|
|
2738
|
+
assert!(!json.contains("not a real api key"));
|
|
2739
|
+
|
|
2740
|
+
let _ = fs::remove_dir_all(root);
|
|
2741
|
+
}
|
|
2742
|
+
|
|
2472
2743
|
#[test]
|
|
2473
2744
|
fn warns_when_macos_notarization_timestamp_is_disabled() {
|
|
2474
2745
|
let root = unique_temp_dir("macos-p12-notarization-no-timestamp");
|