electron-cli 0.3.0-alpha.3 → 0.3.0-alpha.4
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 +96 -1
- package/Cargo.toml +2 -1
- package/README.md +6 -2
- package/package.json +1 -1
- package/src/cli.rs +59 -0
- package/src/commands/make.rs +426 -0
- package/src/commands/mod.rs +1 -0
- package/src/commands/package.rs +41 -3
- package/src/commands/plan.rs +7 -1
- package/src/main.rs +1 -0
package/Cargo.lock
CHANGED
|
@@ -2,6 +2,12 @@
|
|
|
2
2
|
# It is not intended for manual editing.
|
|
3
3
|
version = 4
|
|
4
4
|
|
|
5
|
+
[[package]]
|
|
6
|
+
name = "adler2"
|
|
7
|
+
version = "2.0.1"
|
|
8
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
9
|
+
checksum = "320119579fcad9c21884f5c4861d16174d0e06250625266f50fe6898340abefa"
|
|
10
|
+
|
|
5
11
|
[[package]]
|
|
6
12
|
name = "anstream"
|
|
7
13
|
version = "1.0.0"
|
|
@@ -67,6 +73,12 @@ dependencies = [
|
|
|
67
73
|
"serde_core",
|
|
68
74
|
]
|
|
69
75
|
|
|
76
|
+
[[package]]
|
|
77
|
+
name = "cfg-if"
|
|
78
|
+
version = "1.0.4"
|
|
79
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
80
|
+
checksum = "9330f8b2ff13f34540b44e946ef35111825727b38d33286ef986142615121801"
|
|
81
|
+
|
|
70
82
|
[[package]]
|
|
71
83
|
name = "clap"
|
|
72
84
|
version = "4.6.1"
|
|
@@ -113,23 +125,65 @@ version = "1.0.5"
|
|
|
113
125
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
114
126
|
checksum = "1d07550c9036bf2ae0c684c4297d503f838287c83c53686d05370d0e139ae570"
|
|
115
127
|
|
|
128
|
+
[[package]]
|
|
129
|
+
name = "crc32fast"
|
|
130
|
+
version = "1.5.0"
|
|
131
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
132
|
+
checksum = "9481c1c90cbf2ac953f07c8d4a58aa3945c425b7185c9154d67a65e4230da511"
|
|
133
|
+
dependencies = [
|
|
134
|
+
"cfg-if",
|
|
135
|
+
]
|
|
136
|
+
|
|
116
137
|
[[package]]
|
|
117
138
|
name = "electron-cli"
|
|
118
|
-
version = "0.3.0-alpha.
|
|
139
|
+
version = "0.3.0-alpha.4"
|
|
119
140
|
dependencies = [
|
|
120
141
|
"anyhow",
|
|
121
142
|
"camino",
|
|
122
143
|
"clap",
|
|
123
144
|
"serde",
|
|
124
145
|
"serde_json",
|
|
146
|
+
"zip",
|
|
147
|
+
]
|
|
148
|
+
|
|
149
|
+
[[package]]
|
|
150
|
+
name = "equivalent"
|
|
151
|
+
version = "1.0.2"
|
|
152
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
153
|
+
checksum = "877a4ace8713b0bcf2a4e7eec82529c029f1d0619886d18145fea96c3ffe5c0f"
|
|
154
|
+
|
|
155
|
+
[[package]]
|
|
156
|
+
name = "flate2"
|
|
157
|
+
version = "1.1.9"
|
|
158
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
159
|
+
checksum = "843fba2746e448b37e26a819579957415c8cef339bf08564fe8b7ddbd959573c"
|
|
160
|
+
dependencies = [
|
|
161
|
+
"miniz_oxide",
|
|
162
|
+
"zlib-rs",
|
|
125
163
|
]
|
|
126
164
|
|
|
165
|
+
[[package]]
|
|
166
|
+
name = "hashbrown"
|
|
167
|
+
version = "0.17.1"
|
|
168
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
169
|
+
checksum = "ed5909b6e89a2db4456e54cd5f673791d7eca6732202bbf2a9cc504fe2f9b84a"
|
|
170
|
+
|
|
127
171
|
[[package]]
|
|
128
172
|
name = "heck"
|
|
129
173
|
version = "0.5.0"
|
|
130
174
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
131
175
|
checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea"
|
|
132
176
|
|
|
177
|
+
[[package]]
|
|
178
|
+
name = "indexmap"
|
|
179
|
+
version = "2.14.0"
|
|
180
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
181
|
+
checksum = "d466e9454f08e4a911e14806c24e16fba1b4c121d1ea474396f396069cf949d9"
|
|
182
|
+
dependencies = [
|
|
183
|
+
"equivalent",
|
|
184
|
+
"hashbrown",
|
|
185
|
+
]
|
|
186
|
+
|
|
133
187
|
[[package]]
|
|
134
188
|
name = "is_terminal_polyfill"
|
|
135
189
|
version = "1.70.2"
|
|
@@ -148,6 +202,16 @@ version = "2.8.1"
|
|
|
148
202
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
149
203
|
checksum = "6b947ae49db0d222b1dbc6b113ce7248a3fc3a6ca21b696717bfc000ba4484d8"
|
|
150
204
|
|
|
205
|
+
[[package]]
|
|
206
|
+
name = "miniz_oxide"
|
|
207
|
+
version = "0.8.9"
|
|
208
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
209
|
+
checksum = "1fa76a2c86f704bdb222d66965fb3d63269ce38518b83cb0575fca855ebb6316"
|
|
210
|
+
dependencies = [
|
|
211
|
+
"adler2",
|
|
212
|
+
"simd-adler32",
|
|
213
|
+
]
|
|
214
|
+
|
|
151
215
|
[[package]]
|
|
152
216
|
name = "once_cell_polyfill"
|
|
153
217
|
version = "1.70.2"
|
|
@@ -215,6 +279,12 @@ dependencies = [
|
|
|
215
279
|
"zmij",
|
|
216
280
|
]
|
|
217
281
|
|
|
282
|
+
[[package]]
|
|
283
|
+
name = "simd-adler32"
|
|
284
|
+
version = "0.3.9"
|
|
285
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
286
|
+
checksum = "703d5c7ef118737c72f1af64ad2f6f8c5e1921f818cdcb97b8fe6fc69bf66214"
|
|
287
|
+
|
|
218
288
|
[[package]]
|
|
219
289
|
name = "strsim"
|
|
220
290
|
version = "0.11.1"
|
|
@@ -232,6 +302,12 @@ dependencies = [
|
|
|
232
302
|
"unicode-ident",
|
|
233
303
|
]
|
|
234
304
|
|
|
305
|
+
[[package]]
|
|
306
|
+
name = "typed-path"
|
|
307
|
+
version = "0.12.3"
|
|
308
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
309
|
+
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
|
|
310
|
+
|
|
235
311
|
[[package]]
|
|
236
312
|
name = "unicode-ident"
|
|
237
313
|
version = "1.0.24"
|
|
@@ -259,6 +335,25 @@ dependencies = [
|
|
|
259
335
|
"windows-link",
|
|
260
336
|
]
|
|
261
337
|
|
|
338
|
+
[[package]]
|
|
339
|
+
name = "zip"
|
|
340
|
+
version = "8.6.0"
|
|
341
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
342
|
+
checksum = "2d04a6b5381502aa6087c94c669499eb1602eb9c5e8198e534de571f7154809b"
|
|
343
|
+
dependencies = [
|
|
344
|
+
"crc32fast",
|
|
345
|
+
"flate2",
|
|
346
|
+
"indexmap",
|
|
347
|
+
"memchr",
|
|
348
|
+
"typed-path",
|
|
349
|
+
]
|
|
350
|
+
|
|
351
|
+
[[package]]
|
|
352
|
+
name = "zlib-rs"
|
|
353
|
+
version = "0.6.3"
|
|
354
|
+
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
355
|
+
checksum = "3be3d40e40a133f9c916ee3f9f4fa2d9d63435b5fbe1bfc6d9dae0aa0ada1513"
|
|
356
|
+
|
|
262
357
|
[[package]]
|
|
263
358
|
name = "zmij"
|
|
264
359
|
version = "1.0.21"
|
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.4"
|
|
4
4
|
edition = "2021"
|
|
5
5
|
description = "Experimental Rust CLI for Electron project diagnostics and workflow automation"
|
|
6
6
|
license = "MIT"
|
|
@@ -12,3 +12,4 @@ camino = { version = "1.1", features = ["serde1"] }
|
|
|
12
12
|
clap = { version = "4.6", features = ["derive"] }
|
|
13
13
|
serde = { version = "1.0", features = ["derive"] }
|
|
14
14
|
serde_json = "1.0"
|
|
15
|
+
zip = { version = "8.6.0", default-features = false, features = ["deflate-flate2-zlib-rs"] }
|
package/README.md
CHANGED
|
@@ -17,18 +17,19 @@ electron-cli plan
|
|
|
17
17
|
electron-cli init my-app
|
|
18
18
|
electron-cli start
|
|
19
19
|
electron-cli package
|
|
20
|
+
electron-cli make
|
|
20
21
|
electron-cli inspect --json
|
|
21
22
|
electron-cli doctor --json
|
|
22
23
|
electron-cli plan --json
|
|
23
24
|
electron-cli init my-app --dry-run --json
|
|
24
25
|
electron-cli start --dry-run --json
|
|
25
26
|
electron-cli package --dry-run --json
|
|
27
|
+
electron-cli make --dry-run --json
|
|
26
28
|
```
|
|
27
29
|
|
|
28
30
|
Planned commands:
|
|
29
31
|
|
|
30
32
|
```sh
|
|
31
|
-
electron-cli make
|
|
32
33
|
electron-cli publish
|
|
33
34
|
```
|
|
34
35
|
|
|
@@ -39,8 +40,9 @@ The Rust-native flow currently owns:
|
|
|
39
40
|
- `init --template minimal`: writes a local Electron starter without Electron Forge.
|
|
40
41
|
- `start`: launches the installed Electron runtime directly.
|
|
41
42
|
- `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
|
+
- `make`: runs `package` and writes a ZIP distributable under `out/make/zip/<platform>/<arch>/`.
|
|
42
44
|
|
|
43
|
-
`
|
|
45
|
+
`publish` is not implemented yet. It is the next piece of the Forge lifecycle to replace.
|
|
44
46
|
|
|
45
47
|
## Install
|
|
46
48
|
|
|
@@ -74,6 +76,7 @@ cargo run -- inspect --json
|
|
|
74
76
|
cargo run -- init my-app
|
|
75
77
|
cargo run -- start --dry-run
|
|
76
78
|
cargo run -- package --dry-run
|
|
79
|
+
cargo run -- make --dry-run
|
|
77
80
|
```
|
|
78
81
|
|
|
79
82
|
## Design Goals
|
|
@@ -97,6 +100,7 @@ The inspection and planning commands support `--json` so agents and scripts can
|
|
|
97
100
|
`init --dry-run --json` shows whether the CLI will write native template files or delegate to `create-electron-app`.
|
|
98
101
|
`start --dry-run --json` shows the Electron executable that will be launched.
|
|
99
102
|
`package --dry-run --json` shows the runtime and app file copy plan.
|
|
103
|
+
`make --dry-run --json` shows the package prerequisite and ZIP artifact path.
|
|
100
104
|
|
|
101
105
|
```sh
|
|
102
106
|
electron-cli plan --json
|
package/package.json
CHANGED
package/src/cli.rs
CHANGED
|
@@ -22,6 +22,8 @@ pub enum Commands {
|
|
|
22
22
|
Init(InitArgs),
|
|
23
23
|
/// Print a structured snapshot of the current JavaScript/Electron project.
|
|
24
24
|
Inspect(CommandArgs),
|
|
25
|
+
/// Create distributable artifacts from a packaged Electron app.
|
|
26
|
+
Make(MakeArgs),
|
|
25
27
|
/// Create a local Electron application bundle without Electron Forge.
|
|
26
28
|
Package(PackageArgs),
|
|
27
29
|
/// Recommend next commands and risks from the project snapshot.
|
|
@@ -138,6 +140,49 @@ pub struct PackageArgs {
|
|
|
138
140
|
pub json: bool,
|
|
139
141
|
}
|
|
140
142
|
|
|
143
|
+
#[derive(Debug, Clone, Args)]
|
|
144
|
+
pub struct MakeArgs {
|
|
145
|
+
/// Project directory to make distributables for.
|
|
146
|
+
#[arg(long, default_value = ".", value_name = "PATH")]
|
|
147
|
+
pub cwd: PathBuf,
|
|
148
|
+
|
|
149
|
+
/// Output directory used for package and make artifacts.
|
|
150
|
+
#[arg(long, default_value = "out", value_name = "PATH")]
|
|
151
|
+
pub out_dir: PathBuf,
|
|
152
|
+
|
|
153
|
+
/// Override the application name.
|
|
154
|
+
#[arg(long)]
|
|
155
|
+
pub name: Option<String>,
|
|
156
|
+
|
|
157
|
+
/// Target platform label. Defaults to the current platform.
|
|
158
|
+
#[arg(long)]
|
|
159
|
+
pub platform: Option<String>,
|
|
160
|
+
|
|
161
|
+
/// Target architecture label. Defaults to the current architecture.
|
|
162
|
+
#[arg(long)]
|
|
163
|
+
pub arch: Option<String>,
|
|
164
|
+
|
|
165
|
+
/// Maker target to run.
|
|
166
|
+
#[arg(long, value_enum, default_value_t = MakeTarget::Zip)]
|
|
167
|
+
pub target: MakeTarget,
|
|
168
|
+
|
|
169
|
+
/// Reuse an existing package output instead of running package first.
|
|
170
|
+
#[arg(long)]
|
|
171
|
+
pub skip_package: bool,
|
|
172
|
+
|
|
173
|
+
/// Overwrite existing package and make artifacts.
|
|
174
|
+
#[arg(long)]
|
|
175
|
+
pub force: bool,
|
|
176
|
+
|
|
177
|
+
/// Print the make plan without creating files.
|
|
178
|
+
#[arg(long)]
|
|
179
|
+
pub dry_run: bool,
|
|
180
|
+
|
|
181
|
+
/// Emit machine-readable JSON.
|
|
182
|
+
#[arg(long)]
|
|
183
|
+
pub json: bool,
|
|
184
|
+
}
|
|
185
|
+
|
|
141
186
|
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
142
187
|
#[value(rename_all = "lower")]
|
|
143
188
|
pub enum PackageManager {
|
|
@@ -157,3 +202,17 @@ impl PackageManager {
|
|
|
157
202
|
}
|
|
158
203
|
}
|
|
159
204
|
}
|
|
205
|
+
|
|
206
|
+
#[derive(Debug, Clone, Copy, ValueEnum)]
|
|
207
|
+
#[value(rename_all = "lower")]
|
|
208
|
+
pub enum MakeTarget {
|
|
209
|
+
Zip,
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
impl MakeTarget {
|
|
213
|
+
pub fn as_str(self) -> &'static str {
|
|
214
|
+
match self {
|
|
215
|
+
MakeTarget::Zip => "zip",
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
}
|
|
@@ -0,0 +1,426 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
fs,
|
|
3
|
+
fs::File,
|
|
4
|
+
io::{self, BufWriter},
|
|
5
|
+
path::{Path, PathBuf},
|
|
6
|
+
};
|
|
7
|
+
|
|
8
|
+
use anyhow::{bail, Context, Result};
|
|
9
|
+
use camino::Utf8PathBuf;
|
|
10
|
+
use serde::Serialize;
|
|
11
|
+
use zip::{write::SimpleFileOptions, CompressionMethod, ZipWriter};
|
|
12
|
+
|
|
13
|
+
use crate::{
|
|
14
|
+
cli::{MakeArgs, PackageArgs},
|
|
15
|
+
commands::package::{self, PackageReport},
|
|
16
|
+
output,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
#[derive(Debug, Serialize)]
|
|
20
|
+
struct MakeReport {
|
|
21
|
+
package: PackageReport,
|
|
22
|
+
target: String,
|
|
23
|
+
skip_package: bool,
|
|
24
|
+
dry_run: bool,
|
|
25
|
+
make_dir: Utf8PathBuf,
|
|
26
|
+
artifact: Utf8PathBuf,
|
|
27
|
+
artifact_size: Option<u64>,
|
|
28
|
+
status: MakeStatus,
|
|
29
|
+
warnings: Vec<String>,
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
#[derive(Debug, Serialize)]
|
|
33
|
+
#[serde(rename_all = "kebab-case")]
|
|
34
|
+
enum MakeStatus {
|
|
35
|
+
Planned,
|
|
36
|
+
Made,
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
pub fn run(args: MakeArgs) -> Result<()> {
|
|
40
|
+
let mut report = build_report(&args)?;
|
|
41
|
+
|
|
42
|
+
if args.dry_run {
|
|
43
|
+
return print_report(&report, args.json);
|
|
44
|
+
}
|
|
45
|
+
|
|
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
|
+
);
|
|
53
|
+
|
|
54
|
+
print_report(&report, args.json)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
fn build_report(args: &MakeArgs) -> Result<MakeReport> {
|
|
58
|
+
let package_args = PackageArgs {
|
|
59
|
+
cwd: args.cwd.clone(),
|
|
60
|
+
out_dir: args.out_dir.clone(),
|
|
61
|
+
name: args.name.clone(),
|
|
62
|
+
platform: args.platform.clone(),
|
|
63
|
+
arch: args.arch.clone(),
|
|
64
|
+
force: args.force,
|
|
65
|
+
dry_run: false,
|
|
66
|
+
json: false,
|
|
67
|
+
};
|
|
68
|
+
let snapshot = crate::project::inspect(&package_args.cwd)?;
|
|
69
|
+
let package = package::build_report(snapshot, &package_args)?;
|
|
70
|
+
let make_dir = Path::new(package.output_dir().as_str())
|
|
71
|
+
.join("make")
|
|
72
|
+
.join(args.target.as_str())
|
|
73
|
+
.join(package.platform())
|
|
74
|
+
.join(package.arch());
|
|
75
|
+
let artifact = make_dir.join(format!(
|
|
76
|
+
"{}-{}-{}.zip",
|
|
77
|
+
package.artifact_stem(),
|
|
78
|
+
package.platform(),
|
|
79
|
+
package.arch()
|
|
80
|
+
));
|
|
81
|
+
|
|
82
|
+
let mut warnings = package.warnings().to_vec();
|
|
83
|
+
if args.skip_package && !Path::new(package.bundle_dir().as_str()).exists() {
|
|
84
|
+
warnings.push(format!(
|
|
85
|
+
"Package output does not exist: {}.",
|
|
86
|
+
package.bundle_dir()
|
|
87
|
+
));
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
if artifact.exists() && !args.force {
|
|
91
|
+
warnings.push(format!(
|
|
92
|
+
"Make artifact already exists: {}. Use --force to overwrite it.",
|
|
93
|
+
artifact.display()
|
|
94
|
+
));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
Ok(MakeReport {
|
|
98
|
+
package,
|
|
99
|
+
target: args.target.as_str().to_string(),
|
|
100
|
+
skip_package: args.skip_package,
|
|
101
|
+
dry_run: args.dry_run,
|
|
102
|
+
make_dir: utf8_path(make_dir)?,
|
|
103
|
+
artifact: utf8_path(artifact)?,
|
|
104
|
+
artifact_size: None,
|
|
105
|
+
status: MakeStatus::Planned,
|
|
106
|
+
warnings,
|
|
107
|
+
})
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
fn execute_make(report: &mut MakeReport, args: &MakeArgs) -> Result<()> {
|
|
111
|
+
if !args.skip_package {
|
|
112
|
+
package::execute_package(&report.package, args.force)?;
|
|
113
|
+
report.package.mark_packaged();
|
|
114
|
+
} else if !Path::new(report.package.bundle_dir().as_str()).exists() {
|
|
115
|
+
bail!(
|
|
116
|
+
"Package output does not exist: {}. Run without --skip-package or run electron-cli package first.",
|
|
117
|
+
report.package.bundle_dir()
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
let artifact = Path::new(report.artifact.as_str());
|
|
122
|
+
if artifact.exists() {
|
|
123
|
+
if args.force {
|
|
124
|
+
fs::remove_file(artifact)
|
|
125
|
+
.with_context(|| format!("Could not remove {}", artifact.display()))?;
|
|
126
|
+
} else {
|
|
127
|
+
bail!(
|
|
128
|
+
"Make artifact already exists: {}. Use --force to overwrite it.",
|
|
129
|
+
artifact.display()
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
fs::create_dir_all(report.make_dir.as_str())
|
|
135
|
+
.with_context(|| format!("Could not create {}", report.make_dir))?;
|
|
136
|
+
write_zip_archive(Path::new(report.package.bundle_dir().as_str()), artifact)?;
|
|
137
|
+
|
|
138
|
+
Ok(())
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
fn print_report(report: &MakeReport, json: bool) -> Result<()> {
|
|
142
|
+
if json {
|
|
143
|
+
return output::json(report);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
println!("electron-cli make");
|
|
147
|
+
println!();
|
|
148
|
+
println!("Project");
|
|
149
|
+
println!(" root: {}", report.package.project().root);
|
|
150
|
+
match report.package.project().package_label() {
|
|
151
|
+
Some(label) => println!(" package: {label}"),
|
|
152
|
+
None => println!(" package: not found"),
|
|
153
|
+
}
|
|
154
|
+
println!(" app name: {}", report.package.app_name());
|
|
155
|
+
println!(
|
|
156
|
+
" target: {} {} {}",
|
|
157
|
+
report.target,
|
|
158
|
+
report.package.platform(),
|
|
159
|
+
report.package.arch()
|
|
160
|
+
);
|
|
161
|
+
println!(" status: {}", report.status.as_str());
|
|
162
|
+
|
|
163
|
+
println!();
|
|
164
|
+
println!("Artifact");
|
|
165
|
+
println!(" {}", report.artifact);
|
|
166
|
+
if let Some(size) = report.artifact_size {
|
|
167
|
+
println!(" size: {size} bytes");
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
if !report.warnings.is_empty() {
|
|
171
|
+
println!();
|
|
172
|
+
println!("Warnings");
|
|
173
|
+
for warning in &report.warnings {
|
|
174
|
+
println!(" {warning}");
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
Ok(())
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
fn write_zip_archive(source: &Path, artifact: &Path) -> Result<()> {
|
|
182
|
+
if !source.exists() {
|
|
183
|
+
bail!("Package output does not exist: {}", source.display());
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
let parent = artifact
|
|
187
|
+
.parent()
|
|
188
|
+
.with_context(|| format!("Artifact path has no parent: {}", artifact.display()))?;
|
|
189
|
+
fs::create_dir_all(parent).with_context(|| format!("Could not create {}", parent.display()))?;
|
|
190
|
+
|
|
191
|
+
let file = File::create(artifact)
|
|
192
|
+
.with_context(|| format!("Could not create {}", artifact.display()))?;
|
|
193
|
+
let mut writer = ZipWriter::new(BufWriter::new(file));
|
|
194
|
+
let base = source
|
|
195
|
+
.parent()
|
|
196
|
+
.with_context(|| format!("Package output has no parent: {}", source.display()))?;
|
|
197
|
+
|
|
198
|
+
add_path_to_zip(source, base, &mut writer)?;
|
|
199
|
+
writer
|
|
200
|
+
.finish()
|
|
201
|
+
.with_context(|| format!("Could not finish {}", artifact.display()))?;
|
|
202
|
+
|
|
203
|
+
Ok(())
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
fn add_path_to_zip(
|
|
207
|
+
path: &Path,
|
|
208
|
+
base: &Path,
|
|
209
|
+
writer: &mut ZipWriter<BufWriter<File>>,
|
|
210
|
+
) -> Result<()> {
|
|
211
|
+
let metadata =
|
|
212
|
+
fs::metadata(path).with_context(|| format!("Could not stat {}", path.display()))?;
|
|
213
|
+
let relative_path = zip_relative_path(path, base)?;
|
|
214
|
+
|
|
215
|
+
if metadata.is_dir() {
|
|
216
|
+
if !relative_path.is_empty() {
|
|
217
|
+
let directory_name = format!("{relative_path}/");
|
|
218
|
+
writer
|
|
219
|
+
.add_directory(directory_name, directory_options(&metadata))
|
|
220
|
+
.with_context(|| format!("Could not add {} to archive", path.display()))?;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
let mut entries = fs::read_dir(path)
|
|
224
|
+
.with_context(|| format!("Could not read {}", path.display()))?
|
|
225
|
+
.collect::<Result<Vec<_>, io::Error>>()?;
|
|
226
|
+
entries.sort_by_key(|entry| entry.path());
|
|
227
|
+
|
|
228
|
+
for entry in entries {
|
|
229
|
+
add_path_to_zip(&entry.path(), base, writer)?;
|
|
230
|
+
}
|
|
231
|
+
} else {
|
|
232
|
+
writer
|
|
233
|
+
.start_file(relative_path, file_options(&metadata))
|
|
234
|
+
.with_context(|| format!("Could not add {} to archive", path.display()))?;
|
|
235
|
+
let mut file =
|
|
236
|
+
File::open(path).with_context(|| format!("Could not open {}", path.display()))?;
|
|
237
|
+
io::copy(&mut file, writer)
|
|
238
|
+
.with_context(|| format!("Could not write {} to archive", path.display()))?;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
Ok(())
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
fn zip_relative_path(path: &Path, base: &Path) -> Result<String> {
|
|
245
|
+
let relative = path.strip_prefix(base).with_context(|| {
|
|
246
|
+
format!(
|
|
247
|
+
"Could not make {} relative to {}",
|
|
248
|
+
path.display(),
|
|
249
|
+
base.display()
|
|
250
|
+
)
|
|
251
|
+
})?;
|
|
252
|
+
Ok(relative
|
|
253
|
+
.components()
|
|
254
|
+
.map(|component| component.as_os_str().to_string_lossy())
|
|
255
|
+
.collect::<Vec<_>>()
|
|
256
|
+
.join("/"))
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
fn file_options(metadata: &fs::Metadata) -> SimpleFileOptions {
|
|
260
|
+
SimpleFileOptions::default()
|
|
261
|
+
.compression_method(CompressionMethod::Deflated)
|
|
262
|
+
.unix_permissions(unix_mode(metadata, 0o644))
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
fn directory_options(metadata: &fs::Metadata) -> SimpleFileOptions {
|
|
266
|
+
SimpleFileOptions::default()
|
|
267
|
+
.compression_method(CompressionMethod::Stored)
|
|
268
|
+
.unix_permissions(unix_mode(metadata, 0o755))
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
#[cfg(unix)]
|
|
272
|
+
fn unix_mode(metadata: &fs::Metadata, _fallback: u32) -> u32 {
|
|
273
|
+
use std::os::unix::fs::PermissionsExt;
|
|
274
|
+
|
|
275
|
+
metadata.permissions().mode()
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
#[cfg(not(unix))]
|
|
279
|
+
fn unix_mode(_metadata: &fs::Metadata, fallback: u32) -> u32 {
|
|
280
|
+
fallback
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
fn utf8_path(path: PathBuf) -> Result<Utf8PathBuf> {
|
|
284
|
+
Utf8PathBuf::from_path_buf(path).map_err(|path| {
|
|
285
|
+
anyhow::anyhow!(
|
|
286
|
+
"Path contains invalid UTF-8 and cannot be represented in JSON: {}",
|
|
287
|
+
path.display()
|
|
288
|
+
)
|
|
289
|
+
})
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
impl MakeStatus {
|
|
293
|
+
fn as_str(&self) -> &'static str {
|
|
294
|
+
match self {
|
|
295
|
+
MakeStatus::Planned => "planned",
|
|
296
|
+
MakeStatus::Made => "made",
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
#[cfg(test)]
|
|
302
|
+
mod tests {
|
|
303
|
+
use super::*;
|
|
304
|
+
use zip::ZipArchive;
|
|
305
|
+
|
|
306
|
+
#[test]
|
|
307
|
+
fn builds_make_report_for_zip_target() {
|
|
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 = MakeArgs {
|
|
314
|
+
cwd: root.clone(),
|
|
315
|
+
out_dir: PathBuf::from("out"),
|
|
316
|
+
name: None,
|
|
317
|
+
platform: None,
|
|
318
|
+
arch: None,
|
|
319
|
+
target: crate::cli::MakeTarget::Zip,
|
|
320
|
+
skip_package: false,
|
|
321
|
+
force: false,
|
|
322
|
+
dry_run: true,
|
|
323
|
+
json: true,
|
|
324
|
+
};
|
|
325
|
+
let report = build_report(&args).expect("report should build");
|
|
326
|
+
|
|
327
|
+
assert_eq!(report.target, "zip");
|
|
328
|
+
let expected_suffix = PathBuf::from("out")
|
|
329
|
+
.join("make")
|
|
330
|
+
.join("zip")
|
|
331
|
+
.join(report.package.platform())
|
|
332
|
+
.join(report.package.arch())
|
|
333
|
+
.join(format!(
|
|
334
|
+
"starter-app-{}-{}.zip",
|
|
335
|
+
report.package.platform(),
|
|
336
|
+
report.package.arch()
|
|
337
|
+
));
|
|
338
|
+
assert!(Path::new(report.artifact.as_str()).ends_with(expected_suffix));
|
|
339
|
+
|
|
340
|
+
let _ = fs::remove_dir_all(root);
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
#[test]
|
|
344
|
+
fn makes_zip_artifact_after_packaging() {
|
|
345
|
+
let root = unique_temp_dir("execute");
|
|
346
|
+
write_package_json(&root);
|
|
347
|
+
write_app_file(&root);
|
|
348
|
+
write_fake_electron_dist(&root);
|
|
349
|
+
|
|
350
|
+
let args = MakeArgs {
|
|
351
|
+
cwd: root.clone(),
|
|
352
|
+
out_dir: PathBuf::from("out"),
|
|
353
|
+
name: None,
|
|
354
|
+
platform: None,
|
|
355
|
+
arch: None,
|
|
356
|
+
target: crate::cli::MakeTarget::Zip,
|
|
357
|
+
skip_package: false,
|
|
358
|
+
force: false,
|
|
359
|
+
dry_run: false,
|
|
360
|
+
json: false,
|
|
361
|
+
};
|
|
362
|
+
let mut report = build_report(&args).expect("report should build");
|
|
363
|
+
|
|
364
|
+
execute_make(&mut report, &args).expect("make should succeed");
|
|
365
|
+
|
|
366
|
+
let file = File::open(report.artifact.as_str()).expect("artifact should exist");
|
|
367
|
+
let mut archive = ZipArchive::new(file).expect("zip should open");
|
|
368
|
+
let app_entry = if report.package.platform() == "darwin" {
|
|
369
|
+
"starter-app.app/Contents/Resources/app/package.json".to_string()
|
|
370
|
+
} else {
|
|
371
|
+
format!(
|
|
372
|
+
"starter-app-{}-{}/resources/app/package.json",
|
|
373
|
+
report.package.platform(),
|
|
374
|
+
report.package.arch()
|
|
375
|
+
)
|
|
376
|
+
};
|
|
377
|
+
|
|
378
|
+
archive
|
|
379
|
+
.by_name(&app_entry)
|
|
380
|
+
.expect("app package.json should be archived");
|
|
381
|
+
|
|
382
|
+
let _ = fs::remove_dir_all(root);
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
fn write_package_json(root: &Path) {
|
|
386
|
+
fs::write(
|
|
387
|
+
root.join("package.json"),
|
|
388
|
+
r#"{"name":"starter-app","version":"0.1.0","main":"src/main.js","devDependencies":{"electron":"30.0.0"}}"#,
|
|
389
|
+
)
|
|
390
|
+
.expect("package.json should be written");
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
fn write_app_file(root: &Path) {
|
|
394
|
+
fs::create_dir_all(root.join("src")).expect("src should be created");
|
|
395
|
+
fs::write(root.join("src/main.js"), "console.log('hello');")
|
|
396
|
+
.expect("main file should be written");
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
fn write_fake_electron_dist(root: &Path) {
|
|
400
|
+
let dist = root.join("node_modules/electron/dist");
|
|
401
|
+
if cfg!(target_os = "macos") {
|
|
402
|
+
let app = dist.join("Electron.app/Contents/MacOS");
|
|
403
|
+
fs::create_dir_all(&app).expect("fake macOS electron app should be created");
|
|
404
|
+
fs::write(app.join("Electron"), "").expect("fake macOS binary should be written");
|
|
405
|
+
} else if cfg!(target_os = "windows") {
|
|
406
|
+
fs::create_dir_all(&dist).expect("fake electron dist should be created");
|
|
407
|
+
fs::write(dist.join("electron.exe"), "").expect("fake exe should be written");
|
|
408
|
+
} else {
|
|
409
|
+
fs::create_dir_all(&dist).expect("fake electron dist should be created");
|
|
410
|
+
fs::write(dist.join("electron"), "").expect("fake binary should be written");
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
fn unique_temp_dir(label: &str) -> PathBuf {
|
|
415
|
+
let nanos = std::time::SystemTime::now()
|
|
416
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
417
|
+
.expect("clock should be after epoch")
|
|
418
|
+
.as_nanos();
|
|
419
|
+
let path = std::env::temp_dir().join(format!(
|
|
420
|
+
"electron-cli-make-{label}-{}-{nanos}",
|
|
421
|
+
std::process::id()
|
|
422
|
+
));
|
|
423
|
+
fs::create_dir_all(&path).expect("temp dir should be created");
|
|
424
|
+
path
|
|
425
|
+
}
|
|
426
|
+
}
|
package/src/commands/mod.rs
CHANGED
package/src/commands/package.rs
CHANGED
|
@@ -10,7 +10,7 @@ use serde::Serialize;
|
|
|
10
10
|
use crate::{cli::PackageArgs, output, project::ProjectSnapshot};
|
|
11
11
|
|
|
12
12
|
#[derive(Debug, Serialize)]
|
|
13
|
-
struct PackageReport {
|
|
13
|
+
pub(crate) struct PackageReport {
|
|
14
14
|
project: ProjectSnapshot,
|
|
15
15
|
app_name: String,
|
|
16
16
|
executable_name: String,
|
|
@@ -54,7 +54,7 @@ pub fn run(args: PackageArgs) -> Result<()> {
|
|
|
54
54
|
print_report(&report, args.json)
|
|
55
55
|
}
|
|
56
56
|
|
|
57
|
-
fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Result<PackageReport> {
|
|
57
|
+
pub(crate) fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Result<PackageReport> {
|
|
58
58
|
let root = Path::new(snapshot.root.as_str());
|
|
59
59
|
let platform = args.platform.clone().unwrap_or_else(current_platform);
|
|
60
60
|
let arch = args.arch.clone().unwrap_or_else(current_arch);
|
|
@@ -150,7 +150,7 @@ fn build_report(snapshot: ProjectSnapshot, args: &PackageArgs) -> Result<Package
|
|
|
150
150
|
})
|
|
151
151
|
}
|
|
152
152
|
|
|
153
|
-
fn execute_package(report: &PackageReport, force: bool) -> Result<()> {
|
|
153
|
+
pub(crate) fn execute_package(report: &PackageReport, force: bool) -> Result<()> {
|
|
154
154
|
if report.project.package_json.is_none() {
|
|
155
155
|
bail!("No package.json found. Run electron-cli package inside an Electron project.");
|
|
156
156
|
}
|
|
@@ -508,6 +508,44 @@ impl PackageStatus {
|
|
|
508
508
|
}
|
|
509
509
|
}
|
|
510
510
|
|
|
511
|
+
impl PackageReport {
|
|
512
|
+
pub(crate) fn project(&self) -> &ProjectSnapshot {
|
|
513
|
+
&self.project
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
pub(crate) fn mark_packaged(&mut self) {
|
|
517
|
+
self.status = PackageStatus::Packaged;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
pub(crate) fn app_name(&self) -> &str {
|
|
521
|
+
&self.app_name
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
pub(crate) fn artifact_stem(&self) -> String {
|
|
525
|
+
sanitize_artifact_name(&self.app_name)
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
pub(crate) fn platform(&self) -> &str {
|
|
529
|
+
&self.platform
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
pub(crate) fn arch(&self) -> &str {
|
|
533
|
+
&self.arch
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
pub(crate) fn output_dir(&self) -> &Utf8PathBuf {
|
|
537
|
+
&self.output_dir
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
pub(crate) fn bundle_dir(&self) -> &Utf8PathBuf {
|
|
541
|
+
&self.bundle_dir
|
|
542
|
+
}
|
|
543
|
+
|
|
544
|
+
pub(crate) fn warnings(&self) -> &[String] {
|
|
545
|
+
&self.warnings
|
|
546
|
+
}
|
|
547
|
+
}
|
|
548
|
+
|
|
511
549
|
#[cfg(test)]
|
|
512
550
|
mod tests {
|
|
513
551
|
use super::*;
|
package/src/commands/plan.rs
CHANGED
|
@@ -106,7 +106,9 @@ fn build_report(snapshot: &project::ProjectSnapshot) -> PlanReport {
|
|
|
106
106
|
);
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
if
|
|
109
|
+
if matches!(project_type, ProjectType::Electron) && snapshot.main.is_some() {
|
|
110
|
+
recommended_commands.insert("make".to_string(), "electron-cli make".to_string());
|
|
111
|
+
} else if let Some(script) = first_script(snapshot, &["make", "dist"]) {
|
|
110
112
|
recommended_commands.insert("make".to_string(), run_script(snapshot, script));
|
|
111
113
|
}
|
|
112
114
|
|
|
@@ -270,5 +272,9 @@ mod tests {
|
|
|
270
272
|
.map(String::as_str),
|
|
271
273
|
Some("electron-cli package")
|
|
272
274
|
);
|
|
275
|
+
assert_eq!(
|
|
276
|
+
report.recommended_commands.get("make").map(String::as_str),
|
|
277
|
+
Some("electron-cli make")
|
|
278
|
+
);
|
|
273
279
|
}
|
|
274
280
|
}
|
package/src/main.rs
CHANGED
|
@@ -21,6 +21,7 @@ fn run() -> Result<()> {
|
|
|
21
21
|
Commands::Doctor(args) => commands::doctor::run(args),
|
|
22
22
|
Commands::Init(args) => commands::init::run(args),
|
|
23
23
|
Commands::Inspect(args) => commands::inspect::run(args),
|
|
24
|
+
Commands::Make(args) => commands::make::run(args),
|
|
24
25
|
Commands::Package(args) => commands::package::run(args),
|
|
25
26
|
Commands::Plan(args) => commands::plan::run(args),
|
|
26
27
|
Commands::Start(args) => commands::start::run(args),
|