electron-cli 0.3.0-alpha.13 → 0.3.0-alpha.14
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 +1 -1
- package/Cargo.toml +1 -1
- package/README.md +18 -4
- package/package.json +1 -1
- package/src/cli.rs +13 -13
- package/src/commands/make.rs +3 -1
- package/src/commands/package.rs +5 -5
- package/src/commands/publish.rs +758 -83
package/Cargo.lock
CHANGED
package/Cargo.toml
CHANGED
package/README.md
CHANGED
|
@@ -37,9 +37,9 @@ The Rust-native flow currently owns:
|
|
|
37
37
|
- `start`: launches the installed Electron runtime directly.
|
|
38
38
|
- `package`: copies the installed Electron runtime, app files, installed production dependency closure, app metadata, macOS icon, and extra resources into a local app bundle for the current platform and architecture.
|
|
39
39
|
- `make`: runs `package` and writes distributables under `out/make/<target>/<platform>/<arch>/`; it reads JSON-shaped `config.forge.makers` / `electronCli.makers` arrays when `--target` is omitted, and `--target` still forces one maker. ZIP works on all platforms, `--target dmg` writes a basic macOS disk image, `--target deb` / `--target rpm` write Linux packages, and `--target msi` writes a basic Windows Installer package.
|
|
40
|
-
- `publish`: runs `make` and publishes
|
|
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 when `--publisher` is omitted, and `--publisher` still forces one publisher.
|
|
41
41
|
|
|
42
|
-
The GitHub publisher creates or reuses a release, uploads
|
|
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
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.
|
|
45
45
|
|
|
@@ -61,19 +61,33 @@ Package metadata can be configured in `package.json`:
|
|
|
61
61
|
{ "name": "@electron-forge/maker-deb", "platforms": ["linux"] },
|
|
62
62
|
{ "name": "@electron-forge/maker-rpm", "platforms": ["linux"] },
|
|
63
63
|
{ "name": "@electron-forge/maker-wix", "platforms": ["win32"] }
|
|
64
|
+
],
|
|
65
|
+
"publishers": [
|
|
66
|
+
{ "name": "local", "config": { "to": "out/publish/local", "channel": "alpha" } },
|
|
67
|
+
{
|
|
68
|
+
"name": "@electron-forge/publisher-github",
|
|
69
|
+
"config": {
|
|
70
|
+
"repository": { "owner": "example", "name": "my-app" },
|
|
71
|
+
"draft": true,
|
|
72
|
+
"prerelease": true
|
|
73
|
+
}
|
|
74
|
+
}
|
|
64
75
|
]
|
|
65
76
|
},
|
|
66
77
|
"config": {
|
|
67
78
|
"forge": {
|
|
68
79
|
"makers": [
|
|
69
80
|
{ "name": "@electron-forge/maker-zip" }
|
|
81
|
+
],
|
|
82
|
+
"publishers": [
|
|
83
|
+
{ "name": "@electron-forge/publisher-github" }
|
|
70
84
|
]
|
|
71
85
|
}
|
|
72
86
|
}
|
|
73
87
|
}
|
|
74
88
|
```
|
|
75
89
|
|
|
76
|
-
The package command also reads JSON-shaped `config.forge.packagerConfig` and `electronPackagerConfig` entries for the same fields. The make command maps JSON-shaped Forge maker names to the Rust-native targets it supports: zip, dmg, deb, rpm, and wix/msi. JavaScript Forge config files are not evaluated.
|
|
90
|
+
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. JavaScript Forge config files are not evaluated.
|
|
77
91
|
|
|
78
92
|
## Install
|
|
79
93
|
|
|
@@ -139,7 +153,7 @@ The inspection and planning commands support `--json` so agents and scripts can
|
|
|
139
153
|
`start --dry-run --json` shows the Electron executable that will be launched.
|
|
140
154
|
`package --dry-run --json` shows the runtime, app file, metadata, icon, and extra-resource copy plan.
|
|
141
155
|
`make --dry-run --json` shows the package prerequisite and selected maker artifact path.
|
|
142
|
-
`publish --dry-run --json` shows the make prerequisite plus either the local destination/manifest path or the GitHub release/upload plan.
|
|
156
|
+
`publish --dry-run --json` shows the make prerequisite plus either the local destination/manifest path or the GitHub release/upload plan. When multiple configured makers or publishers apply, the JSON output contains a `publishes` array.
|
|
143
157
|
|
|
144
158
|
```sh
|
|
145
159
|
electron-cli plan --json
|
package/package.json
CHANGED
package/src/cli.rs
CHANGED
|
@@ -207,17 +207,17 @@ pub struct PublishArgs {
|
|
|
207
207
|
#[arg(long)]
|
|
208
208
|
pub arch: Option<String>,
|
|
209
209
|
|
|
210
|
-
/// Maker target whose artifact should be published.
|
|
211
|
-
#[arg(long, value_enum
|
|
212
|
-
pub target: MakeTarget
|
|
210
|
+
/// Maker target whose artifact should be published. Overrides configured makers when provided.
|
|
211
|
+
#[arg(long, value_enum)]
|
|
212
|
+
pub target: Option<MakeTarget>,
|
|
213
213
|
|
|
214
|
-
/// Publisher target to use.
|
|
215
|
-
#[arg(long, value_enum
|
|
216
|
-
pub publisher: PublishTarget
|
|
214
|
+
/// Publisher target to use. Overrides configured publishers when provided.
|
|
215
|
+
#[arg(long, value_enum)]
|
|
216
|
+
pub publisher: Option<PublishTarget>,
|
|
217
217
|
|
|
218
218
|
/// Destination for local published artifacts.
|
|
219
|
-
#[arg(long,
|
|
220
|
-
pub to: PathBuf
|
|
219
|
+
#[arg(long, value_name = "PATH")]
|
|
220
|
+
pub to: Option<PathBuf>,
|
|
221
221
|
|
|
222
222
|
/// GitHub repository to publish to, in OWNER/REPO form.
|
|
223
223
|
#[arg(long, value_name = "OWNER/REPO")]
|
|
@@ -240,12 +240,12 @@ pub struct PublishArgs {
|
|
|
240
240
|
pub github_prerelease: bool,
|
|
241
241
|
|
|
242
242
|
/// GitHub API base URL, useful for GitHub Enterprise.
|
|
243
|
-
#[arg(long,
|
|
244
|
-
pub github_api_url: String
|
|
243
|
+
#[arg(long, value_name = "URL")]
|
|
244
|
+
pub github_api_url: Option<String>,
|
|
245
245
|
|
|
246
246
|
/// Release channel label written into the publish manifest.
|
|
247
|
-
#[arg(long
|
|
248
|
-
pub channel: String
|
|
247
|
+
#[arg(long)]
|
|
248
|
+
pub channel: Option<String>,
|
|
249
249
|
|
|
250
250
|
/// Reuse an existing make artifact instead of running package and make first.
|
|
251
251
|
#[arg(long)]
|
|
@@ -306,7 +306,7 @@ impl MakeTarget {
|
|
|
306
306
|
}
|
|
307
307
|
}
|
|
308
308
|
|
|
309
|
-
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
309
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, ValueEnum)]
|
|
310
310
|
#[value(rename_all = "lower")]
|
|
311
311
|
pub enum PublishTarget {
|
|
312
312
|
Github,
|
package/src/commands/make.rs
CHANGED
|
@@ -27,7 +27,7 @@ use crate::{
|
|
|
27
27
|
project::ProjectSnapshot,
|
|
28
28
|
};
|
|
29
29
|
|
|
30
|
-
#[derive(Debug, Serialize)]
|
|
30
|
+
#[derive(Clone, Debug, Serialize)]
|
|
31
31
|
pub(crate) struct MakeReport {
|
|
32
32
|
package: PackageReport,
|
|
33
33
|
target: String,
|
|
@@ -74,6 +74,7 @@ pub fn run(args: MakeArgs) -> Result<()> {
|
|
|
74
74
|
print_reports(&reports, args.json, MakeStatus::Made)
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
+
#[cfg(test)]
|
|
77
78
|
pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
|
|
78
79
|
let reports = build_reports(args)?;
|
|
79
80
|
if reports.len() != 1 {
|
|
@@ -345,6 +346,7 @@ fn current_platform_label() -> String {
|
|
|
345
346
|
}
|
|
346
347
|
}
|
|
347
348
|
|
|
349
|
+
#[cfg(test)]
|
|
348
350
|
pub(crate) fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
|
|
349
351
|
ensure_package_ready(std::slice::from_mut(report), args)?;
|
|
350
352
|
execute_make_artifact(report, args)?;
|
package/src/commands/package.rs
CHANGED
|
@@ -12,7 +12,7 @@ use serde_json::Value as JsonValue;
|
|
|
12
12
|
|
|
13
13
|
use crate::{cli::PackageArgs, output, project::ProjectSnapshot};
|
|
14
14
|
|
|
15
|
-
#[derive(Debug, Serialize)]
|
|
15
|
+
#[derive(Clone, Debug, Serialize)]
|
|
16
16
|
pub(crate) struct PackageReport {
|
|
17
17
|
project: ProjectSnapshot,
|
|
18
18
|
app_name: String,
|
|
@@ -31,13 +31,13 @@ pub(crate) struct PackageReport {
|
|
|
31
31
|
warnings: Vec<String>,
|
|
32
32
|
}
|
|
33
33
|
|
|
34
|
-
#[derive(Debug, Serialize)]
|
|
34
|
+
#[derive(Clone, Debug, Serialize)]
|
|
35
35
|
struct CopyStep {
|
|
36
36
|
from: Utf8PathBuf,
|
|
37
37
|
to: Utf8PathBuf,
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
-
#[derive(Debug, Serialize)]
|
|
40
|
+
#[derive(Clone, Debug, Serialize)]
|
|
41
41
|
struct PackageMetadata {
|
|
42
42
|
bundle_identifier: String,
|
|
43
43
|
app_version: Option<String>,
|
|
@@ -49,13 +49,13 @@ struct PackageMetadata {
|
|
|
49
49
|
darwin_dark_mode_support: bool,
|
|
50
50
|
}
|
|
51
51
|
|
|
52
|
-
#[derive(Debug, Serialize)]
|
|
52
|
+
#[derive(Clone, Debug, Serialize)]
|
|
53
53
|
struct IconResource {
|
|
54
54
|
from: Utf8PathBuf,
|
|
55
55
|
to: Utf8PathBuf,
|
|
56
56
|
}
|
|
57
57
|
|
|
58
|
-
#[derive(Debug, Serialize)]
|
|
58
|
+
#[derive(Clone, Copy, Debug, Serialize)]
|
|
59
59
|
#[serde(rename_all = "kebab-case")]
|
|
60
60
|
enum PackageStatus {
|
|
61
61
|
Planned,
|
package/src/commands/publish.rs
CHANGED
|
@@ -8,20 +8,28 @@ use std::{
|
|
|
8
8
|
use anyhow::{bail, Context, Result};
|
|
9
9
|
use camino::Utf8PathBuf;
|
|
10
10
|
use serde::{de::DeserializeOwned, Deserialize, Serialize};
|
|
11
|
+
use serde_json::{Map as JsonMap, Value as JsonValue};
|
|
11
12
|
|
|
12
13
|
use crate::{
|
|
13
14
|
cli::{MakeArgs, PublishArgs, PublishTarget},
|
|
14
15
|
commands::make::{self, MakeReport},
|
|
15
16
|
output,
|
|
17
|
+
project::ProjectSnapshot,
|
|
16
18
|
};
|
|
17
19
|
|
|
18
20
|
#[derive(Debug, Serialize)]
|
|
19
21
|
struct PublishReport {
|
|
20
22
|
make: MakeReport,
|
|
21
23
|
publisher: String,
|
|
24
|
+
#[serde(skip)]
|
|
25
|
+
publisher_kind: PublishTarget,
|
|
22
26
|
channel: String,
|
|
23
27
|
local: Option<LocalPublishPlan>,
|
|
24
28
|
github: Option<GithubPublishPlan>,
|
|
29
|
+
#[serde(skip)]
|
|
30
|
+
force_publish: bool,
|
|
31
|
+
#[serde(skip)]
|
|
32
|
+
github_auth_token: Option<String>,
|
|
25
33
|
skip_make: bool,
|
|
26
34
|
dry_run: bool,
|
|
27
35
|
status: PublishStatus,
|
|
@@ -49,13 +57,21 @@ struct GithubPublishPlan {
|
|
|
49
57
|
asset_url: Option<String>,
|
|
50
58
|
}
|
|
51
59
|
|
|
52
|
-
#[derive(Debug, Serialize)]
|
|
60
|
+
#[derive(Clone, Copy, Debug, Serialize)]
|
|
53
61
|
#[serde(rename_all = "kebab-case")]
|
|
54
62
|
enum PublishStatus {
|
|
55
63
|
Planned,
|
|
56
64
|
Published,
|
|
57
65
|
}
|
|
58
66
|
|
|
67
|
+
#[derive(Debug, Serialize)]
|
|
68
|
+
struct PublishRunReport<'a> {
|
|
69
|
+
publishes: &'a [PublishReport],
|
|
70
|
+
dry_run: bool,
|
|
71
|
+
status: PublishStatus,
|
|
72
|
+
warnings: Vec<String>,
|
|
73
|
+
}
|
|
74
|
+
|
|
59
75
|
#[derive(Debug, Serialize)]
|
|
60
76
|
struct PublishManifest {
|
|
61
77
|
schema_version: u8,
|
|
@@ -78,33 +94,112 @@ struct PublishedArtifact {
|
|
|
78
94
|
size: u64,
|
|
79
95
|
}
|
|
80
96
|
|
|
97
|
+
#[derive(Debug)]
|
|
98
|
+
struct ResolvedPublishers {
|
|
99
|
+
publishers: Vec<ResolvedPublisher>,
|
|
100
|
+
warnings: Vec<String>,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
#[derive(Clone, Debug)]
|
|
104
|
+
struct ResolvedPublisher {
|
|
105
|
+
target: PublishTarget,
|
|
106
|
+
to: PathBuf,
|
|
107
|
+
channel: String,
|
|
108
|
+
github_repo: Option<String>,
|
|
109
|
+
github_tag: Option<String>,
|
|
110
|
+
github_tag_prefix: Option<String>,
|
|
111
|
+
github_release_name: Option<String>,
|
|
112
|
+
github_draft: bool,
|
|
113
|
+
github_prerelease: bool,
|
|
114
|
+
github_api_url: String,
|
|
115
|
+
github_auth_token: Option<String>,
|
|
116
|
+
force_publish: bool,
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
#[derive(Debug)]
|
|
120
|
+
struct ConfiguredPublisher {
|
|
121
|
+
label: String,
|
|
122
|
+
target: Option<PublishTarget>,
|
|
123
|
+
platforms: Vec<String>,
|
|
124
|
+
to: Option<PathBuf>,
|
|
125
|
+
channel: Option<String>,
|
|
126
|
+
github_repo: Option<String>,
|
|
127
|
+
github_tag: Option<String>,
|
|
128
|
+
github_tag_prefix: Option<String>,
|
|
129
|
+
github_release_name: Option<String>,
|
|
130
|
+
github_draft: Option<bool>,
|
|
131
|
+
github_prerelease: Option<bool>,
|
|
132
|
+
github_api_url: Option<String>,
|
|
133
|
+
github_auth_token: Option<String>,
|
|
134
|
+
force_publish: Option<bool>,
|
|
135
|
+
}
|
|
136
|
+
|
|
81
137
|
pub fn run(args: PublishArgs) -> Result<()> {
|
|
82
|
-
let mut
|
|
138
|
+
let mut make_reports = make::build_reports(&make_args(&args))?;
|
|
139
|
+
let mut reports = build_reports_from_make_reports(&args, &make_reports)?;
|
|
83
140
|
|
|
84
141
|
if args.dry_run {
|
|
85
|
-
return
|
|
142
|
+
return print_reports(&reports, args.json, PublishStatus::Planned);
|
|
86
143
|
}
|
|
87
144
|
|
|
88
|
-
|
|
89
|
-
report.status = PublishStatus::Published;
|
|
145
|
+
execute_publish_reports(&mut reports, &mut make_reports, &args)?;
|
|
90
146
|
|
|
91
|
-
|
|
147
|
+
print_reports(&reports, args.json, PublishStatus::Published)
|
|
92
148
|
}
|
|
93
149
|
|
|
150
|
+
#[cfg(test)]
|
|
94
151
|
fn build_report(args: &PublishArgs) -> Result<PublishReport> {
|
|
95
|
-
let
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
152
|
+
let reports = build_reports(args)?;
|
|
153
|
+
if reports.len() != 1 {
|
|
154
|
+
bail!(
|
|
155
|
+
"Expected one publish target, but resolved {}. Pass --target and --publisher to select one target.",
|
|
156
|
+
reports.len()
|
|
157
|
+
);
|
|
158
|
+
}
|
|
159
|
+
Ok(reports
|
|
160
|
+
.into_iter()
|
|
161
|
+
.next()
|
|
162
|
+
.expect("length was checked above"))
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
#[cfg(test)]
|
|
166
|
+
fn build_reports(args: &PublishArgs) -> Result<Vec<PublishReport>> {
|
|
167
|
+
let make_reports = make::build_reports(&make_args(args))?;
|
|
168
|
+
build_reports_from_make_reports(args, &make_reports)
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
fn build_reports_from_make_reports(
|
|
172
|
+
args: &PublishArgs,
|
|
173
|
+
make_reports: &[MakeReport],
|
|
174
|
+
) -> Result<Vec<PublishReport>> {
|
|
175
|
+
let first_make = make_reports
|
|
176
|
+
.first()
|
|
177
|
+
.context("No make targets were resolved for publish.")?;
|
|
178
|
+
let project = first_make.package().project();
|
|
179
|
+
let platform = first_make.package().platform();
|
|
180
|
+
let resolved = resolve_publishers(project, args, platform)?;
|
|
181
|
+
let mut reports = Vec::new();
|
|
182
|
+
|
|
183
|
+
for publisher in &resolved.publishers {
|
|
184
|
+
for make in make_reports {
|
|
185
|
+
reports.push(build_report_for_publisher(
|
|
186
|
+
args,
|
|
187
|
+
make.clone(),
|
|
188
|
+
publisher,
|
|
189
|
+
&resolved.warnings,
|
|
190
|
+
)?);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
Ok(reports)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn build_report_for_publisher(
|
|
198
|
+
args: &PublishArgs,
|
|
199
|
+
make: MakeReport,
|
|
200
|
+
publisher: &ResolvedPublisher,
|
|
201
|
+
config_warnings: &[String],
|
|
202
|
+
) -> Result<PublishReport> {
|
|
108
203
|
let root = Path::new(make.package().project().root.as_str());
|
|
109
204
|
let artifact_name = make
|
|
110
205
|
.artifact()
|
|
@@ -113,22 +208,23 @@ fn build_report(args: &PublishArgs) -> Result<PublishReport> {
|
|
|
113
208
|
.to_string();
|
|
114
209
|
|
|
115
210
|
let mut warnings = make.warnings().to_vec();
|
|
211
|
+
warnings.extend(config_warnings.iter().cloned());
|
|
116
212
|
if args.skip_make && !Path::new(make.artifact().as_str()).exists() {
|
|
117
213
|
warnings.push(format!(
|
|
118
214
|
"Make artifact does not exist: {}.",
|
|
119
215
|
make.artifact()
|
|
120
216
|
));
|
|
121
217
|
}
|
|
122
|
-
let (local, github) = match
|
|
218
|
+
let (local, github) = match publisher.target {
|
|
123
219
|
PublishTarget::Local => {
|
|
124
|
-
let local = build_local_plan(root,
|
|
125
|
-
if Path::new(local.destination_artifact.as_str()).exists() && !
|
|
220
|
+
let local = build_local_plan(root, publisher, &make, &artifact_name)?;
|
|
221
|
+
if Path::new(local.destination_artifact.as_str()).exists() && !publisher.force_publish {
|
|
126
222
|
warnings.push(format!(
|
|
127
223
|
"Publish artifact already exists: {}. Use --force to overwrite it.",
|
|
128
224
|
local.destination_artifact
|
|
129
225
|
));
|
|
130
226
|
}
|
|
131
|
-
if Path::new(local.manifest.as_str()).exists() && !
|
|
227
|
+
if Path::new(local.manifest.as_str()).exists() && !publisher.force_publish {
|
|
132
228
|
warnings.push(format!(
|
|
133
229
|
"Publish manifest already exists: {}. Use --force to overwrite it.",
|
|
134
230
|
local.manifest
|
|
@@ -137,17 +233,20 @@ fn build_report(args: &PublishArgs) -> Result<PublishReport> {
|
|
|
137
233
|
(Some(local), None)
|
|
138
234
|
}
|
|
139
235
|
PublishTarget::Github => {
|
|
140
|
-
let github = build_github_plan(
|
|
236
|
+
let github = build_github_plan(publisher, &make, &artifact_name, &mut warnings)?;
|
|
141
237
|
(None, Some(github))
|
|
142
238
|
}
|
|
143
239
|
};
|
|
144
240
|
|
|
145
241
|
Ok(PublishReport {
|
|
146
242
|
make,
|
|
147
|
-
publisher:
|
|
148
|
-
|
|
243
|
+
publisher: publisher.target.as_str().to_string(),
|
|
244
|
+
publisher_kind: publisher.target,
|
|
245
|
+
channel: publisher.channel.clone(),
|
|
149
246
|
local,
|
|
150
247
|
github,
|
|
248
|
+
force_publish: publisher.force_publish,
|
|
249
|
+
github_auth_token: publisher.github_auth_token.clone(),
|
|
151
250
|
skip_make: args.skip_make,
|
|
152
251
|
dry_run: args.dry_run,
|
|
153
252
|
status: PublishStatus::Planned,
|
|
@@ -158,13 +257,13 @@ fn build_report(args: &PublishArgs) -> Result<PublishReport> {
|
|
|
158
257
|
|
|
159
258
|
fn build_local_plan(
|
|
160
259
|
root: &Path,
|
|
161
|
-
|
|
260
|
+
publisher: &ResolvedPublisher,
|
|
162
261
|
make: &MakeReport,
|
|
163
262
|
artifact_name: &str,
|
|
164
263
|
) -> Result<LocalPublishPlan> {
|
|
165
|
-
let publish_root = resolve_destination(root, &
|
|
264
|
+
let publish_root = resolve_destination(root, &publisher.to);
|
|
166
265
|
let destination_dir = publish_root
|
|
167
|
-
.join(&
|
|
266
|
+
.join(&publisher.channel)
|
|
168
267
|
.join(make.package().platform())
|
|
169
268
|
.join(make.package().arch());
|
|
170
269
|
let destination_artifact = destination_dir.join(artifact_name);
|
|
@@ -178,12 +277,12 @@ fn build_local_plan(
|
|
|
178
277
|
}
|
|
179
278
|
|
|
180
279
|
fn build_github_plan(
|
|
181
|
-
|
|
280
|
+
publisher: &ResolvedPublisher,
|
|
182
281
|
make: &MakeReport,
|
|
183
282
|
artifact_name: &str,
|
|
184
283
|
warnings: &mut Vec<String>,
|
|
185
284
|
) -> Result<GithubPublishPlan> {
|
|
186
|
-
let repo =
|
|
285
|
+
let repo = publisher
|
|
187
286
|
.github_repo
|
|
188
287
|
.clone()
|
|
189
288
|
.or_else(|| {
|
|
@@ -204,44 +303,359 @@ fn build_github_plan(
|
|
|
204
303
|
));
|
|
205
304
|
}
|
|
206
305
|
|
|
207
|
-
let tag =
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
306
|
+
let tag = publisher.github_tag.clone().unwrap_or_else(|| {
|
|
307
|
+
default_github_tag(
|
|
308
|
+
make,
|
|
309
|
+
&publisher.channel,
|
|
310
|
+
publisher.github_tag_prefix.as_deref(),
|
|
311
|
+
)
|
|
312
|
+
});
|
|
313
|
+
let release_name = publisher
|
|
212
314
|
.github_release_name
|
|
213
315
|
.clone()
|
|
214
316
|
.unwrap_or_else(|| tag.clone());
|
|
215
|
-
let prerelease =
|
|
317
|
+
let prerelease = publisher.github_prerelease || tag.contains('-');
|
|
216
318
|
|
|
217
319
|
Ok(GithubPublishPlan {
|
|
218
320
|
repo,
|
|
219
321
|
tag,
|
|
220
322
|
release_name,
|
|
221
|
-
draft:
|
|
323
|
+
draft: publisher.github_draft,
|
|
222
324
|
prerelease,
|
|
223
|
-
api_url:
|
|
325
|
+
api_url: publisher.github_api_url.trim_end_matches('/').to_string(),
|
|
224
326
|
artifact_name: artifact_name.to_string(),
|
|
225
327
|
release_url: None,
|
|
226
328
|
asset_url: None,
|
|
227
329
|
})
|
|
228
330
|
}
|
|
229
331
|
|
|
332
|
+
fn make_args(args: &PublishArgs) -> MakeArgs {
|
|
333
|
+
MakeArgs {
|
|
334
|
+
cwd: args.cwd.clone(),
|
|
335
|
+
out_dir: args.out_dir.clone(),
|
|
336
|
+
name: args.name.clone(),
|
|
337
|
+
platform: args.platform.clone(),
|
|
338
|
+
arch: args.arch.clone(),
|
|
339
|
+
target: args.target,
|
|
340
|
+
skip_package: false,
|
|
341
|
+
force: args.force,
|
|
342
|
+
dry_run: false,
|
|
343
|
+
json: false,
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
fn resolve_publishers(
|
|
348
|
+
project: &ProjectSnapshot,
|
|
349
|
+
args: &PublishArgs,
|
|
350
|
+
platform: &str,
|
|
351
|
+
) -> Result<ResolvedPublishers> {
|
|
352
|
+
if let Some(target) = args.publisher {
|
|
353
|
+
return Ok(ResolvedPublishers {
|
|
354
|
+
publishers: vec![resolved_publisher_from_args(args, target, None)],
|
|
355
|
+
warnings: Vec::new(),
|
|
356
|
+
});
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
let configured = configured_publishers(project)?;
|
|
360
|
+
let mut warnings = Vec::new();
|
|
361
|
+
let mut publishers = Vec::new();
|
|
362
|
+
|
|
363
|
+
for publisher in &configured {
|
|
364
|
+
let Some(target) = publisher.target else {
|
|
365
|
+
warnings.push(format!(
|
|
366
|
+
"Configured publisher is not implemented yet and will be skipped: {}.",
|
|
367
|
+
publisher.label
|
|
368
|
+
));
|
|
369
|
+
continue;
|
|
370
|
+
};
|
|
371
|
+
if !publisher_applies_to_platform(publisher, platform) {
|
|
372
|
+
continue;
|
|
373
|
+
}
|
|
374
|
+
publishers.push(resolved_publisher_from_args(args, target, Some(publisher)));
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if publishers.is_empty() {
|
|
378
|
+
if !configured.is_empty() {
|
|
379
|
+
warnings.push(format!(
|
|
380
|
+
"No supported configured publishers apply to {platform}; defaulting to local. Pass --publisher to override."
|
|
381
|
+
));
|
|
382
|
+
}
|
|
383
|
+
publishers.push(resolved_publisher_from_args(
|
|
384
|
+
args,
|
|
385
|
+
PublishTarget::Local,
|
|
386
|
+
None,
|
|
387
|
+
));
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
Ok(ResolvedPublishers {
|
|
391
|
+
publishers,
|
|
392
|
+
warnings,
|
|
393
|
+
})
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
fn resolved_publisher_from_args(
|
|
397
|
+
args: &PublishArgs,
|
|
398
|
+
target: PublishTarget,
|
|
399
|
+
configured: Option<&ConfiguredPublisher>,
|
|
400
|
+
) -> ResolvedPublisher {
|
|
401
|
+
let default_to = PathBuf::from("out/publish/local");
|
|
402
|
+
let default_channel = "default".to_string();
|
|
403
|
+
let default_github_api_url = "https://api.github.com".to_string();
|
|
404
|
+
|
|
405
|
+
ResolvedPublisher {
|
|
406
|
+
target,
|
|
407
|
+
to: args
|
|
408
|
+
.to
|
|
409
|
+
.clone()
|
|
410
|
+
.or_else(|| configured.and_then(|publisher| publisher.to.clone()))
|
|
411
|
+
.unwrap_or(default_to),
|
|
412
|
+
channel: args
|
|
413
|
+
.channel
|
|
414
|
+
.clone()
|
|
415
|
+
.or_else(|| configured.and_then(|publisher| publisher.channel.clone()))
|
|
416
|
+
.unwrap_or(default_channel),
|
|
417
|
+
github_repo: args
|
|
418
|
+
.github_repo
|
|
419
|
+
.clone()
|
|
420
|
+
.or_else(|| configured.and_then(|publisher| publisher.github_repo.clone())),
|
|
421
|
+
github_tag: args
|
|
422
|
+
.github_tag
|
|
423
|
+
.clone()
|
|
424
|
+
.or_else(|| configured.and_then(|publisher| publisher.github_tag.clone())),
|
|
425
|
+
github_tag_prefix: configured.and_then(|publisher| publisher.github_tag_prefix.clone()),
|
|
426
|
+
github_release_name: args
|
|
427
|
+
.github_release_name
|
|
428
|
+
.clone()
|
|
429
|
+
.or_else(|| configured.and_then(|publisher| publisher.github_release_name.clone())),
|
|
430
|
+
github_draft: args.github_draft
|
|
431
|
+
|| configured
|
|
432
|
+
.and_then(|publisher| publisher.github_draft)
|
|
433
|
+
.unwrap_or(false),
|
|
434
|
+
github_prerelease: args.github_prerelease
|
|
435
|
+
|| configured
|
|
436
|
+
.and_then(|publisher| publisher.github_prerelease)
|
|
437
|
+
.unwrap_or(false),
|
|
438
|
+
github_api_url: args
|
|
439
|
+
.github_api_url
|
|
440
|
+
.clone()
|
|
441
|
+
.or_else(|| configured.and_then(|publisher| publisher.github_api_url.clone()))
|
|
442
|
+
.unwrap_or(default_github_api_url),
|
|
443
|
+
github_auth_token: configured.and_then(|publisher| publisher.github_auth_token.clone()),
|
|
444
|
+
force_publish: args.force
|
|
445
|
+
|| configured
|
|
446
|
+
.and_then(|publisher| publisher.force_publish)
|
|
447
|
+
.unwrap_or(false),
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
fn configured_publishers(project: &ProjectSnapshot) -> Result<Vec<ConfiguredPublisher>> {
|
|
452
|
+
let Some(package_json_path) = &project.package_json else {
|
|
453
|
+
return Ok(Vec::new());
|
|
454
|
+
};
|
|
455
|
+
let package_json_path = Path::new(package_json_path.as_str());
|
|
456
|
+
let raw = fs::read_to_string(package_json_path)
|
|
457
|
+
.with_context(|| format!("Could not read {}", package_json_path.display()))?;
|
|
458
|
+
let package = serde_json::from_str::<JsonValue>(&raw)
|
|
459
|
+
.with_context(|| format!("Could not parse {}", package_json_path.display()))?;
|
|
460
|
+
|
|
461
|
+
let mut publishers = Vec::new();
|
|
462
|
+
for value in [
|
|
463
|
+
package
|
|
464
|
+
.get("config")
|
|
465
|
+
.and_then(|config| config.get("forge"))
|
|
466
|
+
.and_then(|forge| forge.get("publishers")),
|
|
467
|
+
package
|
|
468
|
+
.get("electronCli")
|
|
469
|
+
.or_else(|| package.get("electron-cli"))
|
|
470
|
+
.and_then(|config| config.get("publishers")),
|
|
471
|
+
]
|
|
472
|
+
.into_iter()
|
|
473
|
+
.flatten()
|
|
474
|
+
{
|
|
475
|
+
publishers.extend(parse_publisher_list(value));
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
Ok(publishers)
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
fn parse_publisher_list(value: &JsonValue) -> Vec<ConfiguredPublisher> {
|
|
482
|
+
match value {
|
|
483
|
+
JsonValue::Array(values) => values.iter().filter_map(parse_publisher).collect(),
|
|
484
|
+
_ => Vec::new(),
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fn parse_publisher(value: &JsonValue) -> Option<ConfiguredPublisher> {
|
|
489
|
+
match value {
|
|
490
|
+
JsonValue::String(label) => Some(ConfiguredPublisher {
|
|
491
|
+
label: label.clone(),
|
|
492
|
+
target: publisher_target(label),
|
|
493
|
+
platforms: Vec::new(),
|
|
494
|
+
to: None,
|
|
495
|
+
channel: None,
|
|
496
|
+
github_repo: None,
|
|
497
|
+
github_tag: None,
|
|
498
|
+
github_tag_prefix: None,
|
|
499
|
+
github_release_name: None,
|
|
500
|
+
github_draft: None,
|
|
501
|
+
github_prerelease: None,
|
|
502
|
+
github_api_url: None,
|
|
503
|
+
github_auth_token: None,
|
|
504
|
+
force_publish: None,
|
|
505
|
+
}),
|
|
506
|
+
JsonValue::Object(object) => {
|
|
507
|
+
let label = object
|
|
508
|
+
.get("name")
|
|
509
|
+
.or_else(|| object.get("publisher"))
|
|
510
|
+
.or_else(|| object.get("target"))
|
|
511
|
+
.and_then(JsonValue::as_str)?
|
|
512
|
+
.to_string();
|
|
513
|
+
Some(ConfiguredPublisher {
|
|
514
|
+
target: publisher_target(&label),
|
|
515
|
+
platforms: string_values(object.get("platforms")),
|
|
516
|
+
to: publisher_config_string(object, &["to", "path", "dir", "directory"])
|
|
517
|
+
.map(PathBuf::from),
|
|
518
|
+
channel: publisher_config_string(object, &["channel"]),
|
|
519
|
+
github_repo: publisher_config_github_repo(object),
|
|
520
|
+
github_tag: publisher_config_string(object, &["tag", "tagName", "tag_name"]),
|
|
521
|
+
github_tag_prefix: publisher_config_string(object, &["tagPrefix", "tag_prefix"]),
|
|
522
|
+
github_release_name: publisher_config_string(
|
|
523
|
+
object,
|
|
524
|
+
&["releaseName", "release_name"],
|
|
525
|
+
),
|
|
526
|
+
github_draft: publisher_config_bool(object, &["draft"]),
|
|
527
|
+
github_prerelease: publisher_config_bool(object, &["prerelease", "preRelease"]),
|
|
528
|
+
github_api_url: publisher_config_api_url(object),
|
|
529
|
+
github_auth_token: publisher_config_string(object, &["authToken", "auth_token"]),
|
|
530
|
+
force_publish: publisher_config_bool(object, &["force"]),
|
|
531
|
+
label,
|
|
532
|
+
})
|
|
533
|
+
}
|
|
534
|
+
_ => None,
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
fn publisher_target(label: &str) -> Option<PublishTarget> {
|
|
539
|
+
let label = label.trim().to_ascii_lowercase();
|
|
540
|
+
let compact = label
|
|
541
|
+
.trim_start_matches("@electron-forge/")
|
|
542
|
+
.trim_start_matches("electron-forge-")
|
|
543
|
+
.trim_start_matches("publisher-");
|
|
544
|
+
|
|
545
|
+
if compact == "github"
|
|
546
|
+
|| label.ends_with("/publisher-github")
|
|
547
|
+
|| label.ends_with("publisher-github")
|
|
548
|
+
{
|
|
549
|
+
Some(PublishTarget::Github)
|
|
550
|
+
} else if compact == "local"
|
|
551
|
+
|| label.ends_with("/publisher-local")
|
|
552
|
+
|| label.ends_with("publisher-local")
|
|
553
|
+
{
|
|
554
|
+
Some(PublishTarget::Local)
|
|
555
|
+
} else {
|
|
556
|
+
None
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
560
|
+
fn publisher_applies_to_platform(publisher: &ConfiguredPublisher, platform: &str) -> bool {
|
|
561
|
+
publisher.platforms.is_empty()
|
|
562
|
+
|| publisher
|
|
563
|
+
.platforms
|
|
564
|
+
.iter()
|
|
565
|
+
.any(|configured| configured == platform || configured == "*")
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
fn string_values(value: Option<&JsonValue>) -> Vec<String> {
|
|
569
|
+
match value {
|
|
570
|
+
Some(JsonValue::String(value)) => vec![value.clone()],
|
|
571
|
+
Some(JsonValue::Array(values)) => values
|
|
572
|
+
.iter()
|
|
573
|
+
.filter_map(JsonValue::as_str)
|
|
574
|
+
.map(ToOwned::to_owned)
|
|
575
|
+
.collect(),
|
|
576
|
+
_ => Vec::new(),
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
fn publisher_config_string(object: &JsonMap<String, JsonValue>, keys: &[&str]) -> Option<String> {
|
|
581
|
+
keys.iter().find_map(|key| {
|
|
582
|
+
publisher_config_value(object, key)
|
|
583
|
+
.and_then(JsonValue::as_str)
|
|
584
|
+
.map(str::trim)
|
|
585
|
+
.filter(|value| !value.is_empty())
|
|
586
|
+
.map(ToOwned::to_owned)
|
|
587
|
+
})
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
fn publisher_config_bool(object: &JsonMap<String, JsonValue>, keys: &[&str]) -> Option<bool> {
|
|
591
|
+
keys.iter()
|
|
592
|
+
.find_map(|key| publisher_config_value(object, key).and_then(JsonValue::as_bool))
|
|
593
|
+
}
|
|
594
|
+
|
|
595
|
+
fn publisher_config_value<'a>(
|
|
596
|
+
object: &'a JsonMap<String, JsonValue>,
|
|
597
|
+
key: &str,
|
|
598
|
+
) -> Option<&'a JsonValue> {
|
|
599
|
+
object
|
|
600
|
+
.get("config")
|
|
601
|
+
.and_then(JsonValue::as_object)
|
|
602
|
+
.and_then(|config| config.get(key))
|
|
603
|
+
.or_else(|| object.get(key))
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
fn publisher_config_github_repo(object: &JsonMap<String, JsonValue>) -> Option<String> {
|
|
607
|
+
["repository", "repo", "githubRepo", "github_repo"]
|
|
608
|
+
.iter()
|
|
609
|
+
.find_map(|key| publisher_config_value(object, key).and_then(github_repo_from_config_value))
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
fn github_repo_from_config_value(value: &JsonValue) -> Option<String> {
|
|
613
|
+
match value {
|
|
614
|
+
JsonValue::String(value) => {
|
|
615
|
+
let value = value.trim();
|
|
616
|
+
(!value.is_empty()).then(|| value.to_string())
|
|
617
|
+
}
|
|
618
|
+
JsonValue::Object(object) => {
|
|
619
|
+
let owner = object
|
|
620
|
+
.get("owner")
|
|
621
|
+
.or_else(|| object.get("user"))
|
|
622
|
+
.and_then(JsonValue::as_str)?
|
|
623
|
+
.trim();
|
|
624
|
+
let name = object
|
|
625
|
+
.get("name")
|
|
626
|
+
.or_else(|| object.get("repo"))
|
|
627
|
+
.and_then(JsonValue::as_str)?
|
|
628
|
+
.trim();
|
|
629
|
+
if owner.is_empty() || name.is_empty() {
|
|
630
|
+
None
|
|
631
|
+
} else {
|
|
632
|
+
Some(format!("{owner}/{name}"))
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
_ => None,
|
|
636
|
+
}
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
fn publisher_config_api_url(object: &JsonMap<String, JsonValue>) -> Option<String> {
|
|
640
|
+
publisher_config_string(object, &["apiUrl", "api_url", "baseUrl", "base_url"]).or_else(|| {
|
|
641
|
+
publisher_config_value(object, "octokitOptions")
|
|
642
|
+
.and_then(JsonValue::as_object)
|
|
643
|
+
.and_then(|octokit| {
|
|
644
|
+
octokit
|
|
645
|
+
.get("baseUrl")
|
|
646
|
+
.or_else(|| octokit.get("base_url"))
|
|
647
|
+
.and_then(JsonValue::as_str)
|
|
648
|
+
})
|
|
649
|
+
.map(str::trim)
|
|
650
|
+
.filter(|value| !value.is_empty())
|
|
651
|
+
.map(ToOwned::to_owned)
|
|
652
|
+
})
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
#[cfg(test)]
|
|
230
656
|
fn execute_publish(report: &mut PublishReport, args: &PublishArgs) -> Result<()> {
|
|
231
657
|
if !args.skip_make {
|
|
232
|
-
|
|
233
|
-
cwd: args.cwd.clone(),
|
|
234
|
-
out_dir: args.out_dir.clone(),
|
|
235
|
-
name: args.name.clone(),
|
|
236
|
-
platform: args.platform.clone(),
|
|
237
|
-
arch: args.arch.clone(),
|
|
238
|
-
target: Some(args.target),
|
|
239
|
-
skip_package: false,
|
|
240
|
-
force: args.force,
|
|
241
|
-
dry_run: false,
|
|
242
|
-
json: false,
|
|
243
|
-
};
|
|
244
|
-
make::execute_make(&mut report.make, &make_args)?;
|
|
658
|
+
make::execute_make(&mut report.make, &make_args(args))?;
|
|
245
659
|
report.make.mark_made()?;
|
|
246
660
|
} else if !Path::new(report.make.artifact().as_str()).exists() {
|
|
247
661
|
bail!(
|
|
@@ -252,18 +666,68 @@ fn execute_publish(report: &mut PublishReport, args: &PublishArgs) -> Result<()>
|
|
|
252
666
|
|
|
253
667
|
let published_at_unix_seconds = now_unix_seconds()?;
|
|
254
668
|
report.published_at_unix_seconds = Some(published_at_unix_seconds);
|
|
669
|
+
execute_publish_destination(report, published_at_unix_seconds)?;
|
|
670
|
+
report.status = PublishStatus::Published;
|
|
255
671
|
|
|
256
|
-
|
|
257
|
-
PublishTarget::Local => execute_local_publish(report, args, published_at_unix_seconds),
|
|
258
|
-
PublishTarget::Github => execute_github_publish(report, args),
|
|
259
|
-
}
|
|
672
|
+
Ok(())
|
|
260
673
|
}
|
|
261
674
|
|
|
262
|
-
fn
|
|
263
|
-
|
|
675
|
+
fn execute_publish_reports(
|
|
676
|
+
reports: &mut [PublishReport],
|
|
677
|
+
make_reports: &mut [MakeReport],
|
|
264
678
|
args: &PublishArgs,
|
|
679
|
+
) -> Result<()> {
|
|
680
|
+
if !args.skip_make {
|
|
681
|
+
make::execute_make_reports(make_reports, &make_args(args))?;
|
|
682
|
+
sync_make_reports(reports, make_reports);
|
|
683
|
+
} else {
|
|
684
|
+
ensure_make_artifacts_exist(make_reports)?;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
let published_at_unix_seconds = now_unix_seconds()?;
|
|
688
|
+
for report in reports {
|
|
689
|
+
report.published_at_unix_seconds = Some(published_at_unix_seconds);
|
|
690
|
+
execute_publish_destination(report, published_at_unix_seconds)?;
|
|
691
|
+
report.status = PublishStatus::Published;
|
|
692
|
+
}
|
|
693
|
+
|
|
694
|
+
Ok(())
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
fn sync_make_reports(reports: &mut [PublishReport], make_reports: &[MakeReport]) {
|
|
698
|
+
for report in reports {
|
|
699
|
+
if let Some(make) = make_reports
|
|
700
|
+
.iter()
|
|
701
|
+
.find(|make| make.artifact().as_str() == report.make.artifact().as_str())
|
|
702
|
+
{
|
|
703
|
+
report.make = make.clone();
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|
|
707
|
+
|
|
708
|
+
fn ensure_make_artifacts_exist(make_reports: &[MakeReport]) -> Result<()> {
|
|
709
|
+
for make in make_reports {
|
|
710
|
+
if !Path::new(make.artifact().as_str()).exists() {
|
|
711
|
+
bail!(
|
|
712
|
+
"Make artifact does not exist: {}. Run without --skip-make or run electron-cli make first.",
|
|
713
|
+
make.artifact()
|
|
714
|
+
);
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
Ok(())
|
|
718
|
+
}
|
|
719
|
+
|
|
720
|
+
fn execute_publish_destination(
|
|
721
|
+
report: &mut PublishReport,
|
|
265
722
|
published_at_unix_seconds: u64,
|
|
266
723
|
) -> Result<()> {
|
|
724
|
+
match report.publisher_kind {
|
|
725
|
+
PublishTarget::Local => execute_local_publish(report, published_at_unix_seconds),
|
|
726
|
+
PublishTarget::Github => execute_github_publish(report),
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
fn execute_local_publish(report: &PublishReport, published_at_unix_seconds: u64) -> Result<()> {
|
|
267
731
|
let local = report
|
|
268
732
|
.local
|
|
269
733
|
.as_ref()
|
|
@@ -273,7 +737,7 @@ fn execute_local_publish(
|
|
|
273
737
|
|
|
274
738
|
for path in [destination_artifact, manifest] {
|
|
275
739
|
if path.exists() {
|
|
276
|
-
if
|
|
740
|
+
if report.force_publish {
|
|
277
741
|
fs::remove_file(path)
|
|
278
742
|
.with_context(|| format!("Could not remove {}", path.display()))?;
|
|
279
743
|
} else {
|
|
@@ -303,10 +767,10 @@ fn execute_local_publish(
|
|
|
303
767
|
Ok(())
|
|
304
768
|
}
|
|
305
769
|
|
|
306
|
-
fn execute_github_publish(report: &mut PublishReport
|
|
307
|
-
let token = github_token()?;
|
|
770
|
+
fn execute_github_publish(report: &mut PublishReport) -> Result<()> {
|
|
771
|
+
let token = github_token(report.github_auth_token.as_deref())?;
|
|
308
772
|
let agent = github_agent();
|
|
309
|
-
publish_to_github(report,
|
|
773
|
+
publish_to_github(report, &token, &agent)
|
|
310
774
|
}
|
|
311
775
|
|
|
312
776
|
#[derive(Debug, Serialize)]
|
|
@@ -338,12 +802,7 @@ struct GithubErrorBody {
|
|
|
338
802
|
message: Option<String>,
|
|
339
803
|
}
|
|
340
804
|
|
|
341
|
-
fn publish_to_github(
|
|
342
|
-
report: &mut PublishReport,
|
|
343
|
-
args: &PublishArgs,
|
|
344
|
-
token: &str,
|
|
345
|
-
agent: &ureq::Agent,
|
|
346
|
-
) -> Result<()> {
|
|
805
|
+
fn publish_to_github(report: &mut PublishReport, token: &str, agent: &ureq::Agent) -> Result<()> {
|
|
347
806
|
let artifact_path = Path::new(report.make.artifact().as_str());
|
|
348
807
|
let github = report
|
|
349
808
|
.github
|
|
@@ -359,7 +818,7 @@ fn publish_to_github(
|
|
|
359
818
|
.iter()
|
|
360
819
|
.find(|asset| asset.name == github.artifact_name)
|
|
361
820
|
{
|
|
362
|
-
if
|
|
821
|
+
if report.force_publish {
|
|
363
822
|
delete_github_asset(agent, token, github, asset.id)?;
|
|
364
823
|
} else {
|
|
365
824
|
bail!(
|
|
@@ -511,9 +970,13 @@ fn github_agent() -> ureq::Agent {
|
|
|
511
970
|
.into()
|
|
512
971
|
}
|
|
513
972
|
|
|
514
|
-
fn github_token() -> Result<String> {
|
|
515
|
-
|
|
516
|
-
.
|
|
973
|
+
fn github_token(configured_token: Option<&str>) -> Result<String> {
|
|
974
|
+
configured_token
|
|
975
|
+
.map(str::trim)
|
|
976
|
+
.filter(|token| !token.is_empty())
|
|
977
|
+
.map(ToOwned::to_owned)
|
|
978
|
+
.or_else(|| std::env::var("GITHUB_TOKEN").ok())
|
|
979
|
+
.or_else(|| std::env::var("GH_TOKEN").ok())
|
|
517
980
|
.context("GitHub publisher requires GITHUB_TOKEN or GH_TOKEN")
|
|
518
981
|
}
|
|
519
982
|
|
|
@@ -608,6 +1071,85 @@ fn print_report(report: &PublishReport, json: bool) -> Result<()> {
|
|
|
608
1071
|
Ok(())
|
|
609
1072
|
}
|
|
610
1073
|
|
|
1074
|
+
fn print_reports(reports: &[PublishReport], json: bool, status: PublishStatus) -> Result<()> {
|
|
1075
|
+
if reports.len() == 1 {
|
|
1076
|
+
return print_report(&reports[0], json);
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1079
|
+
let warnings = combined_warnings(reports);
|
|
1080
|
+
if json {
|
|
1081
|
+
return output::json(&PublishRunReport {
|
|
1082
|
+
publishes: reports,
|
|
1083
|
+
dry_run: reports.iter().any(|report| report.dry_run),
|
|
1084
|
+
status,
|
|
1085
|
+
warnings,
|
|
1086
|
+
});
|
|
1087
|
+
}
|
|
1088
|
+
|
|
1089
|
+
println!("electron-cli publish");
|
|
1090
|
+
println!();
|
|
1091
|
+
if let Some(first) = reports.first() {
|
|
1092
|
+
println!("Project");
|
|
1093
|
+
println!(" root: {}", first.make.package().project().root);
|
|
1094
|
+
match first.make.package().project().package_label() {
|
|
1095
|
+
Some(label) => println!(" package: {label}"),
|
|
1096
|
+
None => println!(" package: not found"),
|
|
1097
|
+
}
|
|
1098
|
+
println!(" app name: {}", first.make.package().app_name());
|
|
1099
|
+
println!(
|
|
1100
|
+
" target platform: {} {}",
|
|
1101
|
+
first.make.package().platform(),
|
|
1102
|
+
first.make.package().arch()
|
|
1103
|
+
);
|
|
1104
|
+
println!(" status: {}", status.as_str());
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
println!();
|
|
1108
|
+
println!("Publishes");
|
|
1109
|
+
for report in reports {
|
|
1110
|
+
println!(
|
|
1111
|
+
" {} {}: {}",
|
|
1112
|
+
report.publisher,
|
|
1113
|
+
report.make.target(),
|
|
1114
|
+
report.make.artifact()
|
|
1115
|
+
);
|
|
1116
|
+
if let Some(local) = &report.local {
|
|
1117
|
+
println!(" artifact: {}", local.destination_artifact);
|
|
1118
|
+
println!(" manifest: {}", local.manifest);
|
|
1119
|
+
}
|
|
1120
|
+
if let Some(github) = &report.github {
|
|
1121
|
+
println!(" repository: {}", github.repo);
|
|
1122
|
+
println!(" tag: {}", github.tag);
|
|
1123
|
+
if let Some(url) = &github.release_url {
|
|
1124
|
+
println!(" release url: {url}");
|
|
1125
|
+
}
|
|
1126
|
+
if let Some(url) = &github.asset_url {
|
|
1127
|
+
println!(" asset url: {url}");
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
if !warnings.is_empty() {
|
|
1133
|
+
println!();
|
|
1134
|
+
println!("Warnings");
|
|
1135
|
+
for warning in warnings {
|
|
1136
|
+
println!(" {warning}");
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
Ok(())
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
fn combined_warnings(reports: &[PublishReport]) -> Vec<String> {
|
|
1144
|
+
let mut warnings = Vec::new();
|
|
1145
|
+
for warning in reports.iter().flat_map(|report| report.warnings.iter()) {
|
|
1146
|
+
if !warnings.contains(warning) {
|
|
1147
|
+
warnings.push(warning.clone());
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
warnings
|
|
1151
|
+
}
|
|
1152
|
+
|
|
611
1153
|
fn resolve_destination(root: &Path, destination: &Path) -> PathBuf {
|
|
612
1154
|
if destination.is_absolute() {
|
|
613
1155
|
destination.to_path_buf()
|
|
@@ -616,16 +1158,17 @@ fn resolve_destination(root: &Path, destination: &Path) -> PathBuf {
|
|
|
616
1158
|
}
|
|
617
1159
|
}
|
|
618
1160
|
|
|
619
|
-
fn default_github_tag(make: &MakeReport, channel: &str) -> String {
|
|
1161
|
+
fn default_github_tag(make: &MakeReport, channel: &str, tag_prefix: Option<&str>) -> String {
|
|
620
1162
|
make.package()
|
|
621
1163
|
.project()
|
|
622
1164
|
.version
|
|
623
1165
|
.as_deref()
|
|
624
1166
|
.map(|version| {
|
|
625
|
-
|
|
1167
|
+
let prefix = tag_prefix.unwrap_or("v");
|
|
1168
|
+
if prefix.is_empty() || version.starts_with(prefix) {
|
|
626
1169
|
version.to_string()
|
|
627
1170
|
} else {
|
|
628
|
-
format!("
|
|
1171
|
+
format!("{prefix}{version}")
|
|
629
1172
|
}
|
|
630
1173
|
})
|
|
631
1174
|
.unwrap_or_else(|| channel.to_string())
|
|
@@ -807,6 +1350,106 @@ mod tests {
|
|
|
807
1350
|
let _ = fs::remove_dir_all(root);
|
|
808
1351
|
}
|
|
809
1352
|
|
|
1353
|
+
#[test]
|
|
1354
|
+
fn builds_github_publish_report_from_configured_forge_publisher() {
|
|
1355
|
+
let root = unique_temp_dir("configured-github-plan");
|
|
1356
|
+
write_package_json_with_publishers(
|
|
1357
|
+
&root,
|
|
1358
|
+
r#"[
|
|
1359
|
+
{
|
|
1360
|
+
"name":"@electron-forge/publisher-github",
|
|
1361
|
+
"platforms":["*"],
|
|
1362
|
+
"config":{
|
|
1363
|
+
"repository":{"owner":"Ikana","name":"electron-cli"},
|
|
1364
|
+
"draft":true,
|
|
1365
|
+
"prerelease":true,
|
|
1366
|
+
"tagPrefix":"release-",
|
|
1367
|
+
"releaseName":"Configured Release",
|
|
1368
|
+
"baseUrl":"http://127.0.0.1:9"
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
]"#,
|
|
1372
|
+
);
|
|
1373
|
+
write_app_file(&root);
|
|
1374
|
+
write_fake_electron_dist(&root);
|
|
1375
|
+
|
|
1376
|
+
let mut args = publish_args(root.clone(), true);
|
|
1377
|
+
args.target = None;
|
|
1378
|
+
args.publisher = None;
|
|
1379
|
+
args.github_api_url = None;
|
|
1380
|
+
args.channel = None;
|
|
1381
|
+
let report = build_report(&args).expect("report should build");
|
|
1382
|
+
|
|
1383
|
+
assert_eq!(report.publisher, "github");
|
|
1384
|
+
assert!(report.local.is_none());
|
|
1385
|
+
assert_eq!(report.channel, "default");
|
|
1386
|
+
let github = report.github.as_ref().expect("github plan should exist");
|
|
1387
|
+
assert_eq!(github.repo, "Ikana/electron-cli");
|
|
1388
|
+
assert_eq!(github.tag, "release-0.1.0");
|
|
1389
|
+
assert_eq!(github.release_name, "Configured Release");
|
|
1390
|
+
assert!(github.draft);
|
|
1391
|
+
assert!(github.prerelease);
|
|
1392
|
+
assert_eq!(github.api_url, "http://127.0.0.1:9");
|
|
1393
|
+
|
|
1394
|
+
let _ = fs::remove_dir_all(root);
|
|
1395
|
+
}
|
|
1396
|
+
|
|
1397
|
+
#[test]
|
|
1398
|
+
fn builds_publish_reports_from_configured_makers_and_publishers() {
|
|
1399
|
+
let root = unique_temp_dir("configured-publishers");
|
|
1400
|
+
write_package_json_with_makers_and_publishers(
|
|
1401
|
+
&root,
|
|
1402
|
+
r#"[
|
|
1403
|
+
{"name":"@electron-forge/maker-zip"},
|
|
1404
|
+
{"name":"@electron-forge/maker-deb","platforms":["linux"]}
|
|
1405
|
+
]"#,
|
|
1406
|
+
r#"[
|
|
1407
|
+
{"name":"@electron-forge/publisher-github","config":{"repository":"Ikana/electron-cli"}},
|
|
1408
|
+
{"name":"local","config":{"to":"dist/publish","channel":"beta"}},
|
|
1409
|
+
{"name":"@electron-forge/publisher-s3"}
|
|
1410
|
+
]"#,
|
|
1411
|
+
);
|
|
1412
|
+
write_app_file(&root);
|
|
1413
|
+
write_fake_electron_dist(&root);
|
|
1414
|
+
|
|
1415
|
+
let mut args = publish_args(root.clone(), true);
|
|
1416
|
+
args.platform = Some("linux".to_string());
|
|
1417
|
+
args.arch = Some("x64".to_string());
|
|
1418
|
+
args.target = None;
|
|
1419
|
+
args.publisher = None;
|
|
1420
|
+
args.to = None;
|
|
1421
|
+
args.channel = None;
|
|
1422
|
+
args.github_api_url = None;
|
|
1423
|
+
let reports = build_reports(&args).expect("reports should build");
|
|
1424
|
+
|
|
1425
|
+
assert_eq!(reports.len(), 4);
|
|
1426
|
+
assert_eq!(reports[0].publisher, "github");
|
|
1427
|
+
assert_eq!(reports[0].make.target(), "zip");
|
|
1428
|
+
assert_eq!(reports[1].publisher, "github");
|
|
1429
|
+
assert_eq!(reports[1].make.target(), "deb");
|
|
1430
|
+
assert_eq!(reports[2].publisher, "local");
|
|
1431
|
+
assert_eq!(reports[2].make.target(), "zip");
|
|
1432
|
+
assert_eq!(reports[3].publisher, "local");
|
|
1433
|
+
assert_eq!(reports[3].make.target(), "deb");
|
|
1434
|
+
let local = reports[2].local.as_ref().expect("local plan should exist");
|
|
1435
|
+
let local_parent = Path::new(local.destination_artifact.as_str())
|
|
1436
|
+
.parent()
|
|
1437
|
+
.expect("local artifact should have parent");
|
|
1438
|
+
assert!(local_parent.ends_with(
|
|
1439
|
+
PathBuf::from("dist")
|
|
1440
|
+
.join("publish")
|
|
1441
|
+
.join("beta")
|
|
1442
|
+
.join("linux")
|
|
1443
|
+
.join("x64")
|
|
1444
|
+
));
|
|
1445
|
+
assert!(reports[0]
|
|
1446
|
+
.warnings
|
|
1447
|
+
.iter()
|
|
1448
|
+
.any(|warning| warning.contains("@electron-forge/publisher-s3")));
|
|
1449
|
+
|
|
1450
|
+
let _ = fs::remove_dir_all(root);
|
|
1451
|
+
}
|
|
1452
|
+
|
|
810
1453
|
#[test]
|
|
811
1454
|
fn publishes_make_artifact_to_github_release() {
|
|
812
1455
|
let server = MockGithubServer::new(3);
|
|
@@ -824,7 +1467,7 @@ mod tests {
|
|
|
824
1467
|
fs::write(artifact, b"artifact bytes").expect("artifact should be written");
|
|
825
1468
|
|
|
826
1469
|
let agent = github_agent();
|
|
827
|
-
publish_to_github(&mut report,
|
|
1470
|
+
publish_to_github(&mut report, "test-token", &agent)
|
|
828
1471
|
.expect("github publish should succeed");
|
|
829
1472
|
|
|
830
1473
|
let github = report.github.as_ref().expect("github plan should exist");
|
|
@@ -903,16 +1546,16 @@ mod tests {
|
|
|
903
1546
|
name: None,
|
|
904
1547
|
platform: None,
|
|
905
1548
|
arch: None,
|
|
906
|
-
target: crate::cli::MakeTarget::Zip,
|
|
907
|
-
publisher: crate::cli::PublishTarget::Local,
|
|
908
|
-
to: PathBuf::from("out/publish/local"),
|
|
1549
|
+
target: Some(crate::cli::MakeTarget::Zip),
|
|
1550
|
+
publisher: Some(crate::cli::PublishTarget::Local),
|
|
1551
|
+
to: Some(PathBuf::from("out/publish/local")),
|
|
909
1552
|
github_repo: None,
|
|
910
1553
|
github_tag: None,
|
|
911
1554
|
github_release_name: None,
|
|
912
1555
|
github_draft: false,
|
|
913
1556
|
github_prerelease: false,
|
|
914
|
-
github_api_url: "https://api.github.com".to_string(),
|
|
915
|
-
channel: "default".to_string(),
|
|
1557
|
+
github_api_url: Some("https://api.github.com".to_string()),
|
|
1558
|
+
channel: Some("default".to_string()),
|
|
916
1559
|
skip_make: false,
|
|
917
1560
|
force: false,
|
|
918
1561
|
dry_run,
|
|
@@ -922,8 +1565,8 @@ mod tests {
|
|
|
922
1565
|
|
|
923
1566
|
fn github_publish_args(root: PathBuf, dry_run: bool, api_url: &str) -> PublishArgs {
|
|
924
1567
|
let mut args = publish_args(root, dry_run);
|
|
925
|
-
args.publisher = crate::cli::PublishTarget::Github;
|
|
926
|
-
args.github_api_url = api_url.to_string();
|
|
1568
|
+
args.publisher = Some(crate::cli::PublishTarget::Github);
|
|
1569
|
+
args.github_api_url = Some(api_url.to_string());
|
|
927
1570
|
args
|
|
928
1571
|
}
|
|
929
1572
|
|
|
@@ -943,6 +1586,38 @@ mod tests {
|
|
|
943
1586
|
.expect("package.json should be written");
|
|
944
1587
|
}
|
|
945
1588
|
|
|
1589
|
+
fn write_package_json_with_publishers(root: &Path, publishers: &str) {
|
|
1590
|
+
fs::write(
|
|
1591
|
+
root.join("package.json"),
|
|
1592
|
+
format!(
|
|
1593
|
+
r#"{{
|
|
1594
|
+
"name":"starter-app",
|
|
1595
|
+
"version":"0.1.0",
|
|
1596
|
+
"main":"src/main.js",
|
|
1597
|
+
"devDependencies":{{"electron":"30.0.0"}},
|
|
1598
|
+
"config":{{"forge":{{"publishers":{publishers}}}}}
|
|
1599
|
+
}}"#
|
|
1600
|
+
),
|
|
1601
|
+
)
|
|
1602
|
+
.expect("package.json with publishers should be written");
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
fn write_package_json_with_makers_and_publishers(root: &Path, makers: &str, publishers: &str) {
|
|
1606
|
+
fs::write(
|
|
1607
|
+
root.join("package.json"),
|
|
1608
|
+
format!(
|
|
1609
|
+
r#"{{
|
|
1610
|
+
"name":"starter-app",
|
|
1611
|
+
"version":"0.1.0",
|
|
1612
|
+
"main":"src/main.js",
|
|
1613
|
+
"devDependencies":{{"electron":"30.0.0"}},
|
|
1614
|
+
"config":{{"forge":{{"makers":{makers},"publishers":{publishers}}}}}
|
|
1615
|
+
}}"#
|
|
1616
|
+
),
|
|
1617
|
+
)
|
|
1618
|
+
.expect("package.json with makers and publishers should be written");
|
|
1619
|
+
}
|
|
1620
|
+
|
|
946
1621
|
fn write_app_file(root: &Path) {
|
|
947
1622
|
fs::create_dir_all(root.join("src")).expect("src should be created");
|
|
948
1623
|
fs::write(root.join("src/main.js"), "console.log('hello');")
|