electron-cli 0.3.0-alpha.16 → 0.3.0-alpha.17
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 +2610 -88
- package/Cargo.toml +2 -1
- package/README.md +6 -4
- package/package.json +1 -1
- package/src/commands/package.rs +295 -3
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.17"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
|
|
6
6
|
license = "MIT"
|
|
@@ -8,6 +8,7 @@ repository = "https://github.com/Ikana/electron-cli"
|
|
|
8
8
|
|
|
9
9
|
[dependencies]
|
|
10
10
|
anyhow = "1.0"
|
|
11
|
+
apple-codesign = { version = "0.29", default-features = false }
|
|
11
12
|
apple-dmg = "0.5"
|
|
12
13
|
cab = "0.6"
|
|
13
14
|
camino = { version = "1.1", features = ["serde1"] }
|
package/README.md
CHANGED
|
@@ -35,15 +35,15 @@ The Rust-native flow currently owns:
|
|
|
35
35
|
|
|
36
36
|
- `init --template minimal`: writes a local Electron starter without Electron Forge.
|
|
37
37
|
- `start`: launches the installed Electron runtime directly.
|
|
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; it reads package metadata from `package.json`, JSON-shaped Forge config, and static Forge config files.
|
|
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; it reads package metadata from `package.json`, JSON-shaped Forge config, and static Forge config files, and can apply experimental Rust-native ad-hoc macOS bundle signatures.
|
|
39
39
|
- `make`: runs `package` and writes distributables under `out/make/<target>/<platform>/<arch>/`; it reads JSON-shaped `config.forge.makers` / `electronCli.makers` arrays and static Forge config files 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 distributables to a local directory with a manifest or to GitHub Releases; it reads JSON-shaped `config.forge.publishers` / `electronCli.publishers` arrays and static Forge config files when `--publisher` is omitted, and `--publisher` still forces one publisher.
|
|
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.
|
|
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. Developer ID certificate/keychain signing and notarization execution 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, signing execution, and notarization execution are still TODO.
|
|
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, Developer ID signing execution, and notarization execution are still TODO.
|
|
47
47
|
|
|
48
48
|
Package metadata can be configured in `package.json`:
|
|
49
49
|
|
|
@@ -57,7 +57,7 @@ Package metadata can be configured in `package.json`:
|
|
|
57
57
|
"icon": "assets/icon",
|
|
58
58
|
"extraResource": "assets/config.json",
|
|
59
59
|
"osxSign": {
|
|
60
|
-
"identity": "
|
|
60
|
+
"identity": "-",
|
|
61
61
|
"entitlements": "assets/entitlements.plist",
|
|
62
62
|
"hardenedRuntime": true
|
|
63
63
|
},
|
|
@@ -97,6 +97,8 @@ Package metadata can be configured in `package.json`:
|
|
|
97
97
|
}
|
|
98
98
|
```
|
|
99
99
|
|
|
100
|
+
Set `identity` to a Developer ID certificate name when you want the plan to reflect Forge-style release signing, but this project will report it as not executable until Rust-native certificate/keychain signing exists. Use `identity: "-"` or omit `identity` for the current ad-hoc signing path.
|
|
101
|
+
|
|
100
102
|
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.
|
|
101
103
|
|
|
102
104
|
Static `forge.config.js`, `forge.config.cjs`, `forge.config.mjs`, and `forge.config.ts` files are parsed in Rust when they export an object literal directly or via a local `const`/`let`/`var` identifier. Dynamic JavaScript config that calls functions, reads environment state, or computes the config at runtime is not evaluated.
|
package/package.json
CHANGED
package/src/commands/package.rs
CHANGED
|
@@ -5,6 +5,7 @@ use std::{
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
use anyhow::{bail, Context, Result};
|
|
8
|
+
use apple_codesign::{BundleSigner, CodeSignatureFlags, SettingsScope, SigningSettings};
|
|
8
9
|
use camino::Utf8PathBuf;
|
|
9
10
|
use plist::{Dictionary as PlistDictionary, Value as PlistValue};
|
|
10
11
|
use serde::Serialize;
|
|
@@ -71,6 +72,8 @@ struct MacosSigningPlan {
|
|
|
71
72
|
struct MacosSignPlan {
|
|
72
73
|
configured: bool,
|
|
73
74
|
enabled: bool,
|
|
75
|
+
will_execute: bool,
|
|
76
|
+
method: Option<String>,
|
|
74
77
|
identity: Option<String>,
|
|
75
78
|
entitlements: Vec<Utf8PathBuf>,
|
|
76
79
|
entitlements_inherit: Option<Utf8PathBuf>,
|
|
@@ -363,10 +366,111 @@ pub(crate) fn execute_package(report: &PackageReport, force: bool) -> Result<()>
|
|
|
363
366
|
&app_dir,
|
|
364
367
|
&report.project,
|
|
365
368
|
)?;
|
|
369
|
+
execute_macos_signing(report)?;
|
|
366
370
|
|
|
367
371
|
Ok(())
|
|
368
372
|
}
|
|
369
373
|
|
|
374
|
+
fn execute_macos_signing(report: &PackageReport) -> Result<()> {
|
|
375
|
+
if report.platform != "darwin" || !report.signing.macos.sign.will_execute {
|
|
376
|
+
return Ok(());
|
|
377
|
+
}
|
|
378
|
+
|
|
379
|
+
let bundle_dir = Path::new(report.bundle_dir.as_str());
|
|
380
|
+
let bundle_parent = bundle_dir
|
|
381
|
+
.parent()
|
|
382
|
+
.context("macOS bundle output has no parent directory")?;
|
|
383
|
+
let bundle_name = bundle_dir
|
|
384
|
+
.file_name()
|
|
385
|
+
.context("macOS bundle output has no bundle directory name")?;
|
|
386
|
+
let unique_suffix = std::time::SystemTime::now()
|
|
387
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
388
|
+
.context("system clock is before the Unix epoch")?
|
|
389
|
+
.as_nanos();
|
|
390
|
+
let signing_parent = bundle_parent.join(format!(
|
|
391
|
+
".electron-cli-signing-{}-{unique_suffix}",
|
|
392
|
+
std::process::id()
|
|
393
|
+
));
|
|
394
|
+
let signed_bundle_dir = signing_parent.join(bundle_name);
|
|
395
|
+
|
|
396
|
+
if signing_parent.exists() {
|
|
397
|
+
fs::remove_dir_all(&signing_parent)
|
|
398
|
+
.with_context(|| format!("Could not remove {}", signing_parent.display()))?;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
let signing_result = (|| -> Result<()> {
|
|
402
|
+
let mut signer = BundleSigner::new_from_path(bundle_dir).with_context(|| {
|
|
403
|
+
format!(
|
|
404
|
+
"Could not prepare macOS bundle signing for {}",
|
|
405
|
+
bundle_dir.display()
|
|
406
|
+
)
|
|
407
|
+
})?;
|
|
408
|
+
signer
|
|
409
|
+
.collect_nested_bundles()
|
|
410
|
+
.context("Could not discover nested macOS bundles for signing")?;
|
|
411
|
+
|
|
412
|
+
let settings = macos_signing_settings(report)?;
|
|
413
|
+
signer
|
|
414
|
+
.write_signed_bundle(&signed_bundle_dir, &settings)
|
|
415
|
+
.with_context(|| {
|
|
416
|
+
format!(
|
|
417
|
+
"Could not write signed macOS bundle to {}",
|
|
418
|
+
signed_bundle_dir.display()
|
|
419
|
+
)
|
|
420
|
+
})?;
|
|
421
|
+
|
|
422
|
+
Ok(())
|
|
423
|
+
})();
|
|
424
|
+
|
|
425
|
+
if let Err(error) = signing_result {
|
|
426
|
+
let _ = fs::remove_dir_all(&signing_parent);
|
|
427
|
+
return Err(error);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
fs::remove_dir_all(bundle_dir)
|
|
431
|
+
.with_context(|| format!("Could not remove {}", bundle_dir.display()))?;
|
|
432
|
+
fs::rename(&signed_bundle_dir, bundle_dir).with_context(|| {
|
|
433
|
+
format!(
|
|
434
|
+
"Could not move signed macOS bundle from {} to {}",
|
|
435
|
+
signed_bundle_dir.display(),
|
|
436
|
+
bundle_dir.display()
|
|
437
|
+
)
|
|
438
|
+
})?;
|
|
439
|
+
let _ = fs::remove_dir_all(&signing_parent);
|
|
440
|
+
|
|
441
|
+
Ok(())
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
fn macos_signing_settings(report: &PackageReport) -> Result<SigningSettings<'static>> {
|
|
445
|
+
let sign = &report.signing.macos.sign;
|
|
446
|
+
let mut settings = SigningSettings::default();
|
|
447
|
+
settings.set_binary_identifier(SettingsScope::Main, &report.metadata.bundle_identifier);
|
|
448
|
+
|
|
449
|
+
if sign.hardened_runtime.unwrap_or(false) {
|
|
450
|
+
settings.add_code_signature_flags(SettingsScope::Main, CodeSignatureFlags::RUNTIME);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
if let Some(entitlements) = sign.entitlements.first() {
|
|
454
|
+
let entitlements_path = Path::new(entitlements.as_str());
|
|
455
|
+
let entitlements_xml = fs::read_to_string(entitlements_path).with_context(|| {
|
|
456
|
+
format!(
|
|
457
|
+
"Could not read macOS entitlements file {}",
|
|
458
|
+
entitlements_path.display()
|
|
459
|
+
)
|
|
460
|
+
})?;
|
|
461
|
+
settings
|
|
462
|
+
.set_entitlements_xml(SettingsScope::Main, entitlements_xml)
|
|
463
|
+
.with_context(|| {
|
|
464
|
+
format!(
|
|
465
|
+
"Could not parse macOS entitlements file {}",
|
|
466
|
+
entitlements_path.display()
|
|
467
|
+
)
|
|
468
|
+
})?;
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
Ok(settings)
|
|
472
|
+
}
|
|
473
|
+
|
|
370
474
|
fn print_report(report: &PackageReport, json: bool) -> Result<()> {
|
|
371
475
|
if json {
|
|
372
476
|
return output::json(report);
|
|
@@ -403,6 +507,17 @@ fn print_report(report: &PackageReport, json: bool) -> Result<()> {
|
|
|
403
507
|
if let Some(identity) = &report.signing.macos.sign.identity {
|
|
404
508
|
println!(" identity: {identity}");
|
|
405
509
|
}
|
|
510
|
+
if let Some(method) = &report.signing.macos.sign.method {
|
|
511
|
+
println!(" signing method: {method}");
|
|
512
|
+
}
|
|
513
|
+
println!(
|
|
514
|
+
" signing execution: {}",
|
|
515
|
+
if report.signing.macos.sign.will_execute {
|
|
516
|
+
"enabled"
|
|
517
|
+
} else {
|
|
518
|
+
"not available"
|
|
519
|
+
}
|
|
520
|
+
);
|
|
406
521
|
println!(
|
|
407
522
|
" macOS notarization: {}",
|
|
408
523
|
if report.signing.macos.notarize.enabled {
|
|
@@ -720,20 +835,59 @@ fn macos_sign_plan(
|
|
|
720
835
|
.filter(|path| !path.trim().is_empty())
|
|
721
836
|
.map(|path| utf8_path(resolve_project_path(root, path)))
|
|
722
837
|
.transpose()?;
|
|
838
|
+
if let Some(path) = &entitlements_inherit {
|
|
839
|
+
if !Path::new(path.as_str()).exists() {
|
|
840
|
+
warnings.push(format!(
|
|
841
|
+
"Configured macOS inherited entitlements file does not exist: {}.",
|
|
842
|
+
path
|
|
843
|
+
));
|
|
844
|
+
}
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
let identity = config.identity.as_deref().map(str::trim);
|
|
848
|
+
let ad_hoc_identity = matches!(identity, None | Some("-"));
|
|
849
|
+
let will_execute = config.enabled && platform == "darwin" && ad_hoc_identity;
|
|
850
|
+
let method = if config.enabled && platform == "darwin" {
|
|
851
|
+
if ad_hoc_identity {
|
|
852
|
+
Some("ad-hoc".to_string())
|
|
853
|
+
} else {
|
|
854
|
+
Some("certificate-identity".to_string())
|
|
855
|
+
}
|
|
856
|
+
} else {
|
|
857
|
+
None
|
|
858
|
+
};
|
|
723
859
|
|
|
724
860
|
if config.configured && platform != "darwin" {
|
|
725
861
|
warnings.push(format!(
|
|
726
862
|
"macOS signing is configured but ignored for target platform {platform}."
|
|
727
863
|
));
|
|
728
|
-
} else if config.enabled {
|
|
864
|
+
} else if config.enabled && !will_execute {
|
|
729
865
|
warnings.push(
|
|
730
|
-
"macOS signing is configured, but Rust-native signing is not implemented yet; package output will be unsigned.".to_string(),
|
|
866
|
+
"macOS signing identity is configured, but Rust-native certificate/keychain signing is not implemented yet; package output will be unsigned. Use identity '-' or omit identity for experimental ad-hoc signing.".to_string(),
|
|
731
867
|
);
|
|
868
|
+
} else if will_execute {
|
|
869
|
+
if config.entitlements.len() > 1 {
|
|
870
|
+
warnings.push(
|
|
871
|
+
"Rust-native ad-hoc signing applies the first macOS entitlements file only; inherited/login-helper entitlement scoping is not implemented yet.".to_string(),
|
|
872
|
+
);
|
|
873
|
+
}
|
|
874
|
+
if config.entitlements_inherit.is_some() {
|
|
875
|
+
warnings.push(
|
|
876
|
+
"packagerConfig.osxSign.entitlementsInherit is recognized but not applied to nested bundles by Rust-native ad-hoc signing yet.".to_string(),
|
|
877
|
+
);
|
|
878
|
+
}
|
|
879
|
+
if config.gatekeeper_assess.is_some() {
|
|
880
|
+
warnings.push(
|
|
881
|
+
"packagerConfig.osxSign.gatekeeperAssess is recognized but Gatekeeper assessment is not implemented yet.".to_string(),
|
|
882
|
+
);
|
|
883
|
+
}
|
|
732
884
|
}
|
|
733
885
|
|
|
734
886
|
Ok(MacosSignPlan {
|
|
735
887
|
configured: config.configured,
|
|
736
888
|
enabled: config.enabled,
|
|
889
|
+
will_execute,
|
|
890
|
+
method,
|
|
737
891
|
identity: config.identity.clone(),
|
|
738
892
|
entitlements,
|
|
739
893
|
entitlements_inherit,
|
|
@@ -769,6 +923,23 @@ fn macos_notarize_plan(
|
|
|
769
923
|
"macOS notarization requires packagerConfig.osxSign to be enabled first.".to_string(),
|
|
770
924
|
);
|
|
771
925
|
}
|
|
926
|
+
if config.enabled
|
|
927
|
+
&& platform == "darwin"
|
|
928
|
+
&& package_config.packager.osx_sign.enabled
|
|
929
|
+
&& matches!(
|
|
930
|
+
package_config
|
|
931
|
+
.packager
|
|
932
|
+
.osx_sign
|
|
933
|
+
.identity
|
|
934
|
+
.as_deref()
|
|
935
|
+
.map(str::trim),
|
|
936
|
+
None | Some("-")
|
|
937
|
+
)
|
|
938
|
+
{
|
|
939
|
+
warnings.push(
|
|
940
|
+
"macOS notarization requires a Developer ID signature; Rust-native ad-hoc signing is not notarizable.".to_string(),
|
|
941
|
+
);
|
|
942
|
+
}
|
|
772
943
|
if config.enabled && auth_method.is_none() {
|
|
773
944
|
warnings.push(
|
|
774
945
|
"macOS notarization config is missing a complete notarytool authentication set: appleId/appleIdPassword/teamId, appleApiKey/appleApiKeyId/appleApiIssuer, or keychainProfile.".to_string(),
|
|
@@ -1134,6 +1305,7 @@ fn apply_macos_metadata(report: &PackageReport) -> Result<()> {
|
|
|
1134
1305
|
"CFBundleIdentifier",
|
|
1135
1306
|
&report.metadata.bundle_identifier,
|
|
1136
1307
|
);
|
|
1308
|
+
set_plist_string(&mut dictionary, "CFBundlePackageType", "APPL");
|
|
1137
1309
|
|
|
1138
1310
|
if let Some(version) = &report.metadata.app_version {
|
|
1139
1311
|
set_plist_string(&mut dictionary, "CFBundleShortVersionString", version);
|
|
@@ -1816,6 +1988,11 @@ mod tests {
|
|
|
1816
1988
|
|
|
1817
1989
|
assert!(report.signing.macos.sign.configured);
|
|
1818
1990
|
assert!(report.signing.macos.sign.enabled);
|
|
1991
|
+
assert!(!report.signing.macos.sign.will_execute);
|
|
1992
|
+
assert_eq!(
|
|
1993
|
+
report.signing.macos.sign.method.as_deref(),
|
|
1994
|
+
Some("certificate-identity")
|
|
1995
|
+
);
|
|
1819
1996
|
assert_eq!(
|
|
1820
1997
|
report.signing.macos.sign.identity.as_deref(),
|
|
1821
1998
|
Some("Developer ID Application: Example, Inc. (TEAMID1234)")
|
|
@@ -1831,7 +2008,7 @@ mod tests {
|
|
|
1831
2008
|
assert!(report
|
|
1832
2009
|
.warnings
|
|
1833
2010
|
.iter()
|
|
1834
|
-
.any(|warning| warning.contains("Rust-native signing
|
|
2011
|
+
.any(|warning| warning.contains("Rust-native certificate/keychain signing")));
|
|
1835
2012
|
assert!(report
|
|
1836
2013
|
.warnings
|
|
1837
2014
|
.iter()
|
|
@@ -1845,6 +2022,54 @@ mod tests {
|
|
|
1845
2022
|
let _ = fs::remove_dir_all(root);
|
|
1846
2023
|
}
|
|
1847
2024
|
|
|
2025
|
+
#[test]
|
|
2026
|
+
fn plans_macos_ad_hoc_signing_execution() {
|
|
2027
|
+
let root = unique_temp_dir("macos-ad-hoc-signing-plan");
|
|
2028
|
+
write_package_json(&root);
|
|
2029
|
+
fs::write(
|
|
2030
|
+
root.join("forge.config.js"),
|
|
2031
|
+
r#"
|
|
2032
|
+
module.exports = {
|
|
2033
|
+
packagerConfig: {
|
|
2034
|
+
osxSign: {
|
|
2035
|
+
identity: '-',
|
|
2036
|
+
hardenedRuntime: true,
|
|
2037
|
+
},
|
|
2038
|
+
},
|
|
2039
|
+
};
|
|
2040
|
+
"#,
|
|
2041
|
+
)
|
|
2042
|
+
.expect("forge config should be written");
|
|
2043
|
+
write_app_file(&root);
|
|
2044
|
+
write_fake_electron_dist(&root);
|
|
2045
|
+
|
|
2046
|
+
let args = PackageArgs {
|
|
2047
|
+
cwd: root.clone(),
|
|
2048
|
+
out_dir: PathBuf::from("out"),
|
|
2049
|
+
name: None,
|
|
2050
|
+
platform: Some("darwin".to_string()),
|
|
2051
|
+
arch: Some("arm64".to_string()),
|
|
2052
|
+
force: false,
|
|
2053
|
+
dry_run: true,
|
|
2054
|
+
json: true,
|
|
2055
|
+
};
|
|
2056
|
+
let snapshot = crate::project::inspect(&root).expect("project should inspect");
|
|
2057
|
+
let report = build_report(snapshot, &args).expect("report should build");
|
|
2058
|
+
|
|
2059
|
+
assert!(report.signing.macos.sign.configured);
|
|
2060
|
+
assert!(report.signing.macos.sign.enabled);
|
|
2061
|
+
assert!(report.signing.macos.sign.will_execute);
|
|
2062
|
+
assert_eq!(report.signing.macos.sign.method.as_deref(), Some("ad-hoc"));
|
|
2063
|
+
assert_eq!(report.signing.macos.sign.identity.as_deref(), Some("-"));
|
|
2064
|
+
assert_eq!(report.signing.macos.sign.hardened_runtime, Some(true));
|
|
2065
|
+
assert!(!report.warnings.iter().any(|warning| {
|
|
2066
|
+
warning.contains("Rust-native certificate/keychain signing")
|
|
2067
|
+
|| warning.contains("Rust-native signing is not implemented")
|
|
2068
|
+
}));
|
|
2069
|
+
|
|
2070
|
+
let _ = fs::remove_dir_all(root);
|
|
2071
|
+
}
|
|
2072
|
+
|
|
1848
2073
|
#[test]
|
|
1849
2074
|
fn warns_when_macos_notarization_is_configured_without_signing() {
|
|
1850
2075
|
let root = unique_temp_dir("notarize-without-sign");
|
|
@@ -1947,6 +2172,10 @@ mod tests {
|
|
|
1947
2172
|
plist_string(dictionary, "CFBundleIdentifier"),
|
|
1948
2173
|
Some("com.example.starter")
|
|
1949
2174
|
);
|
|
2175
|
+
assert_eq!(
|
|
2176
|
+
plist_string(dictionary, "CFBundlePackageType"),
|
|
2177
|
+
Some("APPL")
|
|
2178
|
+
);
|
|
1950
2179
|
assert_eq!(
|
|
1951
2180
|
plist_string(dictionary, "CFBundleShortVersionString"),
|
|
1952
2181
|
Some("2.3.4")
|
|
@@ -1970,6 +2199,59 @@ mod tests {
|
|
|
1970
2199
|
let _ = fs::remove_dir_all(root);
|
|
1971
2200
|
}
|
|
1972
2201
|
|
|
2202
|
+
#[test]
|
|
2203
|
+
fn packages_macos_bundle_with_ad_hoc_signature() {
|
|
2204
|
+
if current_platform() != "darwin" {
|
|
2205
|
+
return;
|
|
2206
|
+
}
|
|
2207
|
+
|
|
2208
|
+
let root = unique_temp_dir("macos-ad-hoc-signing-execute");
|
|
2209
|
+
fs::write(
|
|
2210
|
+
root.join("package.json"),
|
|
2211
|
+
r#"{"name":"starter-app","version":"0.1.0","main":"src/main.js","devDependencies":{"electron":"30.0.0"},"electronCli":{"packagerConfig":{"appBundleId":"com.example.signed","osxSign":true}}}"#,
|
|
2212
|
+
)
|
|
2213
|
+
.expect("package.json should be written");
|
|
2214
|
+
write_app_file(&root);
|
|
2215
|
+
write_macho_electron_dist(&root);
|
|
2216
|
+
|
|
2217
|
+
let args = PackageArgs {
|
|
2218
|
+
cwd: root.clone(),
|
|
2219
|
+
out_dir: PathBuf::from("out"),
|
|
2220
|
+
name: None,
|
|
2221
|
+
platform: None,
|
|
2222
|
+
arch: None,
|
|
2223
|
+
force: false,
|
|
2224
|
+
dry_run: false,
|
|
2225
|
+
json: false,
|
|
2226
|
+
};
|
|
2227
|
+
let snapshot = crate::project::inspect(&root).expect("project should inspect");
|
|
2228
|
+
let report = build_report(snapshot, &args).expect("report should build");
|
|
2229
|
+
|
|
2230
|
+
assert!(report.signing.macos.sign.will_execute);
|
|
2231
|
+
assert_eq!(report.signing.macos.sign.method.as_deref(), Some("ad-hoc"));
|
|
2232
|
+
assert!(report.warnings.is_empty());
|
|
2233
|
+
|
|
2234
|
+
execute_package(&report, false).expect("package should succeed");
|
|
2235
|
+
|
|
2236
|
+
let bundle_dir = Path::new(report.bundle_dir.as_str());
|
|
2237
|
+
assert!(bundle_dir
|
|
2238
|
+
.join("Contents/_CodeSignature/CodeResources")
|
|
2239
|
+
.exists());
|
|
2240
|
+
|
|
2241
|
+
let executable = bundle_dir
|
|
2242
|
+
.join("Contents/MacOS")
|
|
2243
|
+
.join(&report.executable_name);
|
|
2244
|
+
let executable_data = fs::read(executable).expect("signed executable should read");
|
|
2245
|
+
let macho = apple_codesign::MachFile::parse(&executable_data)
|
|
2246
|
+
.expect("signed executable should parse as Mach-O");
|
|
2247
|
+
assert!(macho.iter_macho().all(|binary| binary
|
|
2248
|
+
.code_signature()
|
|
2249
|
+
.expect("code signature should parse")
|
|
2250
|
+
.is_some()));
|
|
2251
|
+
|
|
2252
|
+
let _ = fs::remove_dir_all(root);
|
|
2253
|
+
}
|
|
2254
|
+
|
|
1973
2255
|
#[test]
|
|
1974
2256
|
fn missing_required_runtime_dependency_fails() {
|
|
1975
2257
|
let root = unique_temp_dir("runtime-deps");
|
|
@@ -2123,6 +2405,16 @@ mod tests {
|
|
|
2123
2405
|
}
|
|
2124
2406
|
}
|
|
2125
2407
|
|
|
2408
|
+
fn write_macho_electron_dist(root: &Path) {
|
|
2409
|
+
let app = root.join("node_modules/electron/dist/Electron.app/Contents/MacOS");
|
|
2410
|
+
fs::create_dir_all(&app).expect("macOS Electron app should be created");
|
|
2411
|
+
fs::copy(
|
|
2412
|
+
std::env::current_exe().expect("current test executable should resolve"),
|
|
2413
|
+
app.join("Electron"),
|
|
2414
|
+
)
|
|
2415
|
+
.expect("Mach-O test executable should be copied");
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2126
2418
|
fn unique_temp_dir(label: &str) -> PathBuf {
|
|
2127
2419
|
let nanos = std::time::SystemTime::now()
|
|
2128
2420
|
.duration_since(std::time::UNIX_EPOCH)
|