electron-cli 0.3.0-alpha.4 → 0.3.0-alpha.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/Cargo.lock CHANGED
@@ -136,7 +136,7 @@ dependencies = [
136
136
 
137
137
  [[package]]
138
138
  name = "electron-cli"
139
- version = "0.3.0-alpha.4"
139
+ version = "0.3.0-alpha.5"
140
140
  dependencies = [
141
141
  "anyhow",
142
142
  "camino",
package/Cargo.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "electron-cli"
3
- version = "0.3.0-alpha.4"
3
+ version = "0.3.0-alpha.5"
4
4
  edition = "2021"
5
5
  description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
6
6
  license = "MIT"
package/README.md CHANGED
@@ -18,6 +18,7 @@ electron-cli init my-app
18
18
  electron-cli start
19
19
  electron-cli package
20
20
  electron-cli make
21
+ electron-cli publish
21
22
  electron-cli inspect --json
22
23
  electron-cli doctor --json
23
24
  electron-cli plan --json
@@ -25,12 +26,13 @@ electron-cli init my-app --dry-run --json
25
26
  electron-cli start --dry-run --json
26
27
  electron-cli package --dry-run --json
27
28
  electron-cli make --dry-run --json
29
+ electron-cli publish --dry-run --json
28
30
  ```
29
31
 
30
32
  Planned commands:
31
33
 
32
34
  ```sh
33
- electron-cli publish
35
+ electron-cli publish --publisher github
34
36
  ```
35
37
 
36
38
  The default `init` template is `minimal`, a built-in starter written by this project. Non-native template names are still passed to `create-electron-app` as an escape hatch while this project grows.
@@ -41,8 +43,9 @@ The Rust-native flow currently owns:
41
43
  - `start`: launches the installed Electron runtime directly.
42
44
  - `package`: copies the installed Electron runtime and app files into a local app bundle for the current platform and architecture. The first package pass supports apps without production `dependencies`; dependency pruning and bundled runtime dependencies are still TODO.
43
45
  - `make`: runs `package` and writes a ZIP distributable under `out/make/zip/<platform>/<arch>/`.
46
+ - `publish`: runs `make` and publishes the distributable to a local directory with a manifest.
44
47
 
45
- `publish` is not implemented yet. It is the next piece of the Forge lifecycle to replace.
48
+ Remote publishers such as GitHub Releases are not implemented yet. They are the next publisher targets to replace.
46
49
 
47
50
  ## Install
48
51
 
@@ -77,6 +80,7 @@ cargo run -- init my-app
77
80
  cargo run -- start --dry-run
78
81
  cargo run -- package --dry-run
79
82
  cargo run -- make --dry-run
83
+ cargo run -- publish --dry-run
80
84
  ```
81
85
 
82
86
  ## Design Goals
@@ -101,6 +105,7 @@ The inspection and planning commands support `--json` so agents and scripts can
101
105
  `start --dry-run --json` shows the Electron executable that will be launched.
102
106
  `package --dry-run --json` shows the runtime and app file copy plan.
103
107
  `make --dry-run --json` shows the package prerequisite and ZIP artifact path.
108
+ `publish --dry-run --json` shows the make prerequisite, destination artifact, and manifest path.
104
109
 
105
110
  ```sh
106
111
  electron-cli plan --json
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electron-cli",
3
- "version": "0.3.0-alpha.4",
3
+ "version": "0.3.0-alpha.5",
4
4
  "description": "Experimental Rust CLI for Electron project diagnostics and workflow automation",
5
5
  "license": "MIT",
6
6
  "repository": {
package/src/cli.rs CHANGED
@@ -28,6 +28,8 @@ pub enum Commands {
28
28
  Package(PackageArgs),
29
29
  /// Recommend next commands and risks from the project snapshot.
30
30
  Plan(CommandArgs),
31
+ /// Publish made artifacts to a distribution target.
32
+ Publish(PublishArgs),
31
33
  /// Launch the current Electron app without Electron Forge.
32
34
  Start(StartArgs),
33
35
  }
@@ -183,6 +185,61 @@ pub struct MakeArgs {
183
185
  pub json: bool,
184
186
  }
185
187
 
188
+ #[derive(Debug, Clone, Args)]
189
+ pub struct PublishArgs {
190
+ /// Project directory to publish from.
191
+ #[arg(long, default_value = ".", value_name = "PATH")]
192
+ pub cwd: PathBuf,
193
+
194
+ /// Output directory used for package, make, and publish artifacts.
195
+ #[arg(long, default_value = "out", value_name = "PATH")]
196
+ pub out_dir: PathBuf,
197
+
198
+ /// Override the application name.
199
+ #[arg(long)]
200
+ pub name: Option<String>,
201
+
202
+ /// Target platform label. Defaults to the current platform.
203
+ #[arg(long)]
204
+ pub platform: Option<String>,
205
+
206
+ /// Target architecture label. Defaults to the current architecture.
207
+ #[arg(long)]
208
+ pub arch: Option<String>,
209
+
210
+ /// Maker target whose artifact should be published.
211
+ #[arg(long, value_enum, default_value_t = MakeTarget::Zip)]
212
+ pub target: MakeTarget,
213
+
214
+ /// Publisher target to use.
215
+ #[arg(long, value_enum, default_value_t = PublishTarget::Local)]
216
+ pub publisher: PublishTarget,
217
+
218
+ /// Destination for local published artifacts.
219
+ #[arg(long, default_value = "out/publish/local", value_name = "PATH")]
220
+ pub to: PathBuf,
221
+
222
+ /// Release channel label written into the publish manifest.
223
+ #[arg(long, default_value = "default")]
224
+ pub channel: String,
225
+
226
+ /// Reuse an existing make artifact instead of running package and make first.
227
+ #[arg(long)]
228
+ pub skip_make: bool,
229
+
230
+ /// Overwrite existing publish artifacts.
231
+ #[arg(long)]
232
+ pub force: bool,
233
+
234
+ /// Print the publish plan without creating files.
235
+ #[arg(long)]
236
+ pub dry_run: bool,
237
+
238
+ /// Emit machine-readable JSON.
239
+ #[arg(long)]
240
+ pub json: bool,
241
+ }
242
+
186
243
  #[derive(Debug, Clone, Copy, ValueEnum)]
187
244
  #[value(rename_all = "lower")]
188
245
  pub enum PackageManager {
@@ -216,3 +273,17 @@ impl MakeTarget {
216
273
  }
217
274
  }
218
275
  }
276
+
277
+ #[derive(Debug, Clone, Copy, ValueEnum)]
278
+ #[value(rename_all = "lower")]
279
+ pub enum PublishTarget {
280
+ Local,
281
+ }
282
+
283
+ impl PublishTarget {
284
+ pub fn as_str(self) -> &'static str {
285
+ match self {
286
+ PublishTarget::Local => "local",
287
+ }
288
+ }
289
+ }
@@ -17,7 +17,7 @@ use crate::{
17
17
  };
18
18
 
19
19
  #[derive(Debug, Serialize)]
20
- struct MakeReport {
20
+ pub(crate) struct MakeReport {
21
21
  package: PackageReport,
22
22
  target: String,
23
23
  skip_package: bool,
@@ -44,17 +44,12 @@ pub fn run(args: MakeArgs) -> Result<()> {
44
44
  }
45
45
 
46
46
  execute_make(&mut report, &args)?;
47
- report.status = MakeStatus::Made;
48
- report.artifact_size = Some(
49
- fs::metadata(report.artifact.as_str())
50
- .with_context(|| format!("Could not stat {}", report.artifact))?
51
- .len(),
52
- );
47
+ report.mark_made()?;
53
48
 
54
49
  print_report(&report, args.json)
55
50
  }
56
51
 
57
- fn build_report(args: &MakeArgs) -> Result<MakeReport> {
52
+ pub(crate) fn build_report(args: &MakeArgs) -> Result<MakeReport> {
58
53
  let package_args = PackageArgs {
59
54
  cwd: args.cwd.clone(),
60
55
  out_dir: args.out_dir.clone(),
@@ -107,7 +102,7 @@ fn build_report(args: &MakeArgs) -> Result<MakeReport> {
107
102
  })
108
103
  }
109
104
 
110
- fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
105
+ pub(crate) fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
111
106
  if !args.skip_package {
112
107
  package::execute_package(&report.package, args.force)?;
113
108
  report.package.mark_packaged();
@@ -298,6 +293,34 @@ impl MakeStatus {
298
293
  }
299
294
  }
300
295
 
296
+ impl MakeReport {
297
+ pub(crate) fn mark_made(&mut self) -> Result<()> {
298
+ self.status = MakeStatus::Made;
299
+ self.artifact_size = Some(
300
+ fs::metadata(self.artifact.as_str())
301
+ .with_context(|| format!("Could not stat {}", self.artifact))?
302
+ .len(),
303
+ );
304
+ Ok(())
305
+ }
306
+
307
+ pub(crate) fn package(&self) -> &PackageReport {
308
+ &self.package
309
+ }
310
+
311
+ pub(crate) fn target(&self) -> &str {
312
+ &self.target
313
+ }
314
+
315
+ pub(crate) fn artifact(&self) -> &Utf8PathBuf {
316
+ &self.artifact
317
+ }
318
+
319
+ pub(crate) fn warnings(&self) -> &[String] {
320
+ &self.warnings
321
+ }
322
+ }
323
+
301
324
  #[cfg(test)]
302
325
  mod tests {
303
326
  use super::*;
@@ -4,4 +4,5 @@ pub mod inspect;
4
4
  pub mod make;
5
5
  pub mod package;
6
6
  pub mod plan;
7
+ pub mod publish;
7
8
  pub mod start;
@@ -112,6 +112,12 @@ fn build_report(snapshot: &project::ProjectSnapshot) -> PlanReport {
112
112
  recommended_commands.insert("make".to_string(), run_script(snapshot, script));
113
113
  }
114
114
 
115
+ if matches!(project_type, ProjectType::Electron) && snapshot.main.is_some() {
116
+ recommended_commands.insert("publish".to_string(), "electron-cli publish".to_string());
117
+ } else if let Some(script) = first_script(snapshot, &["publish", "release"]) {
118
+ recommended_commands.insert("publish".to_string(), run_script(snapshot, script));
119
+ }
120
+
115
121
  recommended_commands.insert(
116
122
  "diagnostics".to_string(),
117
123
  "electron-cli doctor --json".to_string(),
@@ -142,7 +148,7 @@ fn build_report(snapshot: &project::ProjectSnapshot) -> PlanReport {
142
148
  if matches!(project_type, ProjectType::ElectronForge) {
143
149
  notes.push("Electron Forge was detected; its scripts remain the safest path for Forge-managed apps today.".to_string());
144
150
  } else if snapshot.electron_dependency.is_some() {
145
- notes.push("Electron was detected without Forge; electron-cli can start and package the current-platform app directly.".to_string());
151
+ notes.push("Electron was detected without Forge; electron-cli can start, package, make, and publish local artifacts directly.".to_string());
146
152
  } else {
147
153
  notes.push("This does not currently look like an Electron app.".to_string());
148
154
  }
@@ -276,5 +282,12 @@ mod tests {
276
282
  report.recommended_commands.get("make").map(String::as_str),
277
283
  Some("electron-cli make")
278
284
  );
285
+ assert_eq!(
286
+ report
287
+ .recommended_commands
288
+ .get("publish")
289
+ .map(String::as_str),
290
+ Some("electron-cli publish")
291
+ );
279
292
  }
280
293
  }
@@ -0,0 +1,432 @@
1
+ use std::{
2
+ fs,
3
+ path::{Path, PathBuf},
4
+ time::{SystemTime, UNIX_EPOCH},
5
+ };
6
+
7
+ use anyhow::{bail, Context, Result};
8
+ use camino::Utf8PathBuf;
9
+ use serde::Serialize;
10
+
11
+ use crate::{
12
+ cli::{MakeArgs, PublishArgs},
13
+ commands::make::{self, MakeReport},
14
+ output,
15
+ };
16
+
17
+ #[derive(Debug, Serialize)]
18
+ struct PublishReport {
19
+ make: MakeReport,
20
+ publisher: String,
21
+ channel: String,
22
+ destination_dir: Utf8PathBuf,
23
+ destination_artifact: Utf8PathBuf,
24
+ manifest: Utf8PathBuf,
25
+ skip_make: bool,
26
+ dry_run: bool,
27
+ status: PublishStatus,
28
+ published_at_unix_seconds: Option<u64>,
29
+ warnings: Vec<String>,
30
+ }
31
+
32
+ #[derive(Debug, Serialize)]
33
+ #[serde(rename_all = "kebab-case")]
34
+ enum PublishStatus {
35
+ Planned,
36
+ Published,
37
+ }
38
+
39
+ #[derive(Debug, Serialize)]
40
+ struct PublishManifest {
41
+ schema_version: u8,
42
+ publisher: String,
43
+ channel: String,
44
+ app_name: String,
45
+ package_name: Option<String>,
46
+ package_version: Option<String>,
47
+ platform: String,
48
+ arch: String,
49
+ target: String,
50
+ published_at_unix_seconds: u64,
51
+ artifacts: Vec<PublishedArtifact>,
52
+ }
53
+
54
+ #[derive(Debug, Serialize)]
55
+ struct PublishedArtifact {
56
+ file: String,
57
+ path: Utf8PathBuf,
58
+ size: u64,
59
+ }
60
+
61
+ pub fn run(args: PublishArgs) -> Result<()> {
62
+ let mut report = build_report(&args)?;
63
+
64
+ if args.dry_run {
65
+ return print_report(&report, args.json);
66
+ }
67
+
68
+ execute_publish(&mut report, &args)?;
69
+ report.status = PublishStatus::Published;
70
+
71
+ print_report(&report, args.json)
72
+ }
73
+
74
+ fn build_report(args: &PublishArgs) -> Result<PublishReport> {
75
+ let make_args = MakeArgs {
76
+ cwd: args.cwd.clone(),
77
+ out_dir: args.out_dir.clone(),
78
+ name: args.name.clone(),
79
+ platform: args.platform.clone(),
80
+ arch: args.arch.clone(),
81
+ target: args.target,
82
+ skip_package: false,
83
+ force: args.force,
84
+ dry_run: false,
85
+ json: false,
86
+ };
87
+ let make = make::build_report(&make_args)?;
88
+ let root = Path::new(make.package().project().root.as_str());
89
+ let publish_root = resolve_destination(root, &args.to);
90
+ let destination_dir = publish_root
91
+ .join(&args.channel)
92
+ .join(make.package().platform())
93
+ .join(make.package().arch());
94
+ let artifact_name = make
95
+ .artifact()
96
+ .file_name()
97
+ .context("Make artifact path has no file name")?;
98
+ let destination_artifact = destination_dir.join(artifact_name);
99
+ let manifest = destination_dir.join("manifest.json");
100
+
101
+ let mut warnings = make.warnings().to_vec();
102
+ if args.skip_make && !Path::new(make.artifact().as_str()).exists() {
103
+ warnings.push(format!(
104
+ "Make artifact does not exist: {}.",
105
+ make.artifact()
106
+ ));
107
+ }
108
+ if destination_artifact.exists() && !args.force {
109
+ warnings.push(format!(
110
+ "Publish artifact already exists: {}. Use --force to overwrite it.",
111
+ destination_artifact.display()
112
+ ));
113
+ }
114
+ if manifest.exists() && !args.force {
115
+ warnings.push(format!(
116
+ "Publish manifest already exists: {}. Use --force to overwrite it.",
117
+ manifest.display()
118
+ ));
119
+ }
120
+
121
+ Ok(PublishReport {
122
+ make,
123
+ publisher: args.publisher.as_str().to_string(),
124
+ channel: args.channel.clone(),
125
+ destination_dir: utf8_path(destination_dir)?,
126
+ destination_artifact: utf8_path(destination_artifact)?,
127
+ manifest: utf8_path(manifest)?,
128
+ skip_make: args.skip_make,
129
+ dry_run: args.dry_run,
130
+ status: PublishStatus::Planned,
131
+ published_at_unix_seconds: None,
132
+ warnings,
133
+ })
134
+ }
135
+
136
+ fn execute_publish(report: &mut PublishReport, args: &PublishArgs) -> Result<()> {
137
+ if !args.skip_make {
138
+ let make_args = MakeArgs {
139
+ cwd: args.cwd.clone(),
140
+ out_dir: args.out_dir.clone(),
141
+ name: args.name.clone(),
142
+ platform: args.platform.clone(),
143
+ arch: args.arch.clone(),
144
+ target: args.target,
145
+ skip_package: false,
146
+ force: args.force,
147
+ dry_run: false,
148
+ json: false,
149
+ };
150
+ make::execute_make(&mut report.make, &make_args)?;
151
+ report.make.mark_made()?;
152
+ } else if !Path::new(report.make.artifact().as_str()).exists() {
153
+ bail!(
154
+ "Make artifact does not exist: {}. Run without --skip-make or run electron-cli make first.",
155
+ report.make.artifact()
156
+ );
157
+ }
158
+
159
+ let destination_artifact = Path::new(report.destination_artifact.as_str());
160
+ let manifest = Path::new(report.manifest.as_str());
161
+
162
+ for path in [destination_artifact, manifest] {
163
+ if path.exists() {
164
+ if args.force {
165
+ fs::remove_file(path)
166
+ .with_context(|| format!("Could not remove {}", path.display()))?;
167
+ } else {
168
+ bail!(
169
+ "Publish output already exists: {}. Use --force to overwrite it.",
170
+ path.display()
171
+ );
172
+ }
173
+ }
174
+ }
175
+
176
+ fs::create_dir_all(report.destination_dir.as_str())
177
+ .with_context(|| format!("Could not create {}", report.destination_dir))?;
178
+ fs::copy(report.make.artifact().as_str(), destination_artifact).with_context(|| {
179
+ format!(
180
+ "Could not publish {} to {}",
181
+ report.make.artifact(),
182
+ destination_artifact.display()
183
+ )
184
+ })?;
185
+
186
+ let published_at_unix_seconds = now_unix_seconds()?;
187
+ report.published_at_unix_seconds = Some(published_at_unix_seconds);
188
+ let manifest_json =
189
+ serde_json::to_string_pretty(&build_manifest(report, published_at_unix_seconds)?)?;
190
+ fs::write(manifest, format!("{manifest_json}\n"))
191
+ .with_context(|| format!("Could not write {}", manifest.display()))?;
192
+
193
+ Ok(())
194
+ }
195
+
196
+ fn build_manifest(
197
+ report: &PublishReport,
198
+ published_at_unix_seconds: u64,
199
+ ) -> Result<PublishManifest> {
200
+ let destination_artifact = Path::new(report.destination_artifact.as_str());
201
+ let artifact_size = fs::metadata(destination_artifact)
202
+ .with_context(|| format!("Could not stat {}", destination_artifact.display()))?
203
+ .len();
204
+ let artifact_file = destination_artifact
205
+ .file_name()
206
+ .and_then(|name| name.to_str())
207
+ .context("Published artifact path has no UTF-8 file name")?
208
+ .to_string();
209
+
210
+ Ok(PublishManifest {
211
+ schema_version: 1,
212
+ publisher: report.publisher.clone(),
213
+ channel: report.channel.clone(),
214
+ app_name: report.make.package().app_name().to_string(),
215
+ package_name: report.make.package().project().name.clone(),
216
+ package_version: report.make.package().project().version.clone(),
217
+ platform: report.make.package().platform().to_string(),
218
+ arch: report.make.package().arch().to_string(),
219
+ target: report.make.target().to_string(),
220
+ published_at_unix_seconds,
221
+ artifacts: vec![PublishedArtifact {
222
+ file: artifact_file,
223
+ path: report.destination_artifact.clone(),
224
+ size: artifact_size,
225
+ }],
226
+ })
227
+ }
228
+
229
+ fn print_report(report: &PublishReport, json: bool) -> Result<()> {
230
+ if json {
231
+ return output::json(report);
232
+ }
233
+
234
+ println!("electron-cli publish");
235
+ println!();
236
+ println!("Project");
237
+ println!(" root: {}", report.make.package().project().root);
238
+ match report.make.package().project().package_label() {
239
+ Some(label) => println!(" package: {label}"),
240
+ None => println!(" package: not found"),
241
+ }
242
+ println!(" app name: {}", report.make.package().app_name());
243
+ println!(
244
+ " target: {} {} {}",
245
+ report.make.target(),
246
+ report.make.package().platform(),
247
+ report.make.package().arch()
248
+ );
249
+ println!(" publisher: {}", report.publisher);
250
+ println!(" channel: {}", report.channel);
251
+ println!(" status: {}", report.status.as_str());
252
+
253
+ println!();
254
+ println!("Publish");
255
+ println!(" artifact: {}", report.destination_artifact);
256
+ println!(" manifest: {}", report.manifest);
257
+
258
+ if !report.warnings.is_empty() {
259
+ println!();
260
+ println!("Warnings");
261
+ for warning in &report.warnings {
262
+ println!(" {warning}");
263
+ }
264
+ }
265
+
266
+ Ok(())
267
+ }
268
+
269
+ fn resolve_destination(root: &Path, destination: &Path) -> PathBuf {
270
+ if destination.is_absolute() {
271
+ destination.to_path_buf()
272
+ } else {
273
+ root.join(destination)
274
+ }
275
+ }
276
+
277
+ fn now_unix_seconds() -> Result<u64> {
278
+ Ok(SystemTime::now()
279
+ .duration_since(UNIX_EPOCH)
280
+ .context("System clock is before the Unix epoch")?
281
+ .as_secs())
282
+ }
283
+
284
+ fn utf8_path(path: PathBuf) -> Result<Utf8PathBuf> {
285
+ Utf8PathBuf::from_path_buf(path).map_err(|path| {
286
+ anyhow::anyhow!(
287
+ "Path contains invalid UTF-8 and cannot be represented in JSON: {}",
288
+ path.display()
289
+ )
290
+ })
291
+ }
292
+
293
+ impl PublishStatus {
294
+ fn as_str(&self) -> &'static str {
295
+ match self {
296
+ PublishStatus::Planned => "planned",
297
+ PublishStatus::Published => "published",
298
+ }
299
+ }
300
+ }
301
+
302
+ #[cfg(test)]
303
+ mod tests {
304
+ use super::*;
305
+
306
+ #[test]
307
+ fn builds_local_publish_report() {
308
+ let root = unique_temp_dir("plan");
309
+ write_package_json(&root);
310
+ write_app_file(&root);
311
+ write_fake_electron_dist(&root);
312
+
313
+ let args = publish_args(root.clone(), true);
314
+ let report = build_report(&args).expect("report should build");
315
+
316
+ assert_eq!(report.publisher, "local");
317
+ assert_eq!(report.channel, "default");
318
+ assert!(Path::new(report.destination_artifact.as_str()).ends_with(
319
+ PathBuf::from("out")
320
+ .join("publish")
321
+ .join("local")
322
+ .join("default")
323
+ .join(report.make.package().platform())
324
+ .join(report.make.package().arch())
325
+ .join(format!(
326
+ "starter-app-{}-{}.zip",
327
+ report.make.package().platform(),
328
+ report.make.package().arch()
329
+ ))
330
+ ));
331
+
332
+ let _ = fs::remove_dir_all(root);
333
+ }
334
+
335
+ #[test]
336
+ fn publishes_make_artifact_to_local_directory() {
337
+ let root = unique_temp_dir("execute");
338
+ write_package_json(&root);
339
+ write_app_file(&root);
340
+ write_fake_electron_dist(&root);
341
+
342
+ let args = publish_args(root.clone(), false);
343
+ let mut report = build_report(&args).expect("report should build");
344
+
345
+ execute_publish(&mut report, &args).expect("publish should succeed");
346
+
347
+ assert!(Path::new(report.destination_artifact.as_str()).exists());
348
+ assert!(Path::new(report.manifest.as_str()).exists());
349
+ let manifest =
350
+ fs::read_to_string(report.manifest.as_str()).expect("manifest should be readable");
351
+ assert!(manifest.contains("\"publisher\": \"local\""));
352
+ assert!(manifest.contains("\"app_name\": \"starter-app\""));
353
+
354
+ let _ = fs::remove_dir_all(root);
355
+ }
356
+
357
+ #[test]
358
+ fn skip_make_requires_existing_artifact() {
359
+ let root = unique_temp_dir("skip-make");
360
+ write_package_json(&root);
361
+ write_app_file(&root);
362
+ write_fake_electron_dist(&root);
363
+
364
+ let mut args = publish_args(root.clone(), false);
365
+ args.skip_make = true;
366
+ let mut report = build_report(&args).expect("report should build");
367
+
368
+ assert!(execute_publish(&mut report, &args).is_err());
369
+
370
+ let _ = fs::remove_dir_all(root);
371
+ }
372
+
373
+ fn publish_args(root: PathBuf, dry_run: bool) -> PublishArgs {
374
+ PublishArgs {
375
+ cwd: root,
376
+ out_dir: PathBuf::from("out"),
377
+ name: None,
378
+ platform: None,
379
+ arch: None,
380
+ target: crate::cli::MakeTarget::Zip,
381
+ publisher: crate::cli::PublishTarget::Local,
382
+ to: PathBuf::from("out/publish/local"),
383
+ channel: "default".to_string(),
384
+ skip_make: false,
385
+ force: false,
386
+ dry_run,
387
+ json: true,
388
+ }
389
+ }
390
+
391
+ fn write_package_json(root: &Path) {
392
+ fs::write(
393
+ root.join("package.json"),
394
+ r#"{"name":"starter-app","version":"0.1.0","main":"src/main.js","devDependencies":{"electron":"30.0.0"}}"#,
395
+ )
396
+ .expect("package.json should be written");
397
+ }
398
+
399
+ fn write_app_file(root: &Path) {
400
+ fs::create_dir_all(root.join("src")).expect("src should be created");
401
+ fs::write(root.join("src/main.js"), "console.log('hello');")
402
+ .expect("main file should be written");
403
+ }
404
+
405
+ fn write_fake_electron_dist(root: &Path) {
406
+ let dist = root.join("node_modules/electron/dist");
407
+ if cfg!(target_os = "macos") {
408
+ let app = dist.join("Electron.app/Contents/MacOS");
409
+ fs::create_dir_all(&app).expect("fake macOS electron app should be created");
410
+ fs::write(app.join("Electron"), "").expect("fake macOS binary should be written");
411
+ } else if cfg!(target_os = "windows") {
412
+ fs::create_dir_all(&dist).expect("fake electron dist should be created");
413
+ fs::write(dist.join("electron.exe"), "").expect("fake exe should be written");
414
+ } else {
415
+ fs::create_dir_all(&dist).expect("fake electron dist should be created");
416
+ fs::write(dist.join("electron"), "").expect("fake binary should be written");
417
+ }
418
+ }
419
+
420
+ fn unique_temp_dir(label: &str) -> PathBuf {
421
+ let nanos = std::time::SystemTime::now()
422
+ .duration_since(std::time::UNIX_EPOCH)
423
+ .expect("clock should be after epoch")
424
+ .as_nanos();
425
+ let path = std::env::temp_dir().join(format!(
426
+ "electron-cli-publish-{label}-{}-{nanos}",
427
+ std::process::id()
428
+ ));
429
+ fs::create_dir_all(&path).expect("temp dir should be created");
430
+ path
431
+ }
432
+ }
package/src/main.rs CHANGED
@@ -24,6 +24,7 @@ fn run() -> Result<()> {
24
24
  Commands::Make(args) => commands::make::run(args),
25
25
  Commands::Package(args) => commands::package::run(args),
26
26
  Commands::Plan(args) => commands::plan::run(args),
27
+ Commands::Publish(args) => commands::publish::run(args),
27
28
  Commands::Start(args) => commands::start::run(args),
28
29
  }
29
30
  }