electron-cli 0.3.0-alpha.14 → 0.3.0-alpha.16
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 +18 -1
- package/Cargo.toml +2 -1
- package/README.md +18 -6
- package/package.json +1 -1
- package/src/commands/make.rs +44 -15
- package/src/commands/package.rs +533 -21
- package/src/commands/publish.rs +45 -14
- package/src/forge_config.rs +547 -0
- package/src/main.rs +1 -0
package/src/commands/publish.rs
CHANGED
|
@@ -449,24 +449,15 @@ fn resolved_publisher_from_args(
|
|
|
449
449
|
}
|
|
450
450
|
|
|
451
451
|
fn configured_publishers(project: &ProjectSnapshot) -> Result<Vec<ConfiguredPublisher>> {
|
|
452
|
-
let
|
|
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()))?;
|
|
452
|
+
let project_config = crate::forge_config::read(project)?;
|
|
460
453
|
|
|
461
454
|
let mut publishers = Vec::new();
|
|
462
455
|
for value in [
|
|
463
|
-
|
|
464
|
-
.
|
|
465
|
-
.and_then(|config| config.get("forge"))
|
|
456
|
+
project_config
|
|
457
|
+
.forge()
|
|
466
458
|
.and_then(|forge| forge.get("publishers")),
|
|
467
|
-
|
|
468
|
-
.
|
|
469
|
-
.or_else(|| package.get("electron-cli"))
|
|
459
|
+
project_config
|
|
460
|
+
.electron_cli()
|
|
470
461
|
.and_then(|config| config.get("publishers")),
|
|
471
462
|
]
|
|
472
463
|
.into_iter()
|
|
@@ -1394,6 +1385,46 @@ mod tests {
|
|
|
1394
1385
|
let _ = fs::remove_dir_all(root);
|
|
1395
1386
|
}
|
|
1396
1387
|
|
|
1388
|
+
#[test]
|
|
1389
|
+
fn builds_github_publish_report_from_static_forge_config_js() {
|
|
1390
|
+
let root = unique_temp_dir("configured-github-js-plan");
|
|
1391
|
+
write_package_json(&root);
|
|
1392
|
+
fs::write(
|
|
1393
|
+
root.join("forge.config.js"),
|
|
1394
|
+
r#"
|
|
1395
|
+
module.exports = {
|
|
1396
|
+
publishers: [
|
|
1397
|
+
{
|
|
1398
|
+
name: '@electron-forge/publisher-github',
|
|
1399
|
+
config: {
|
|
1400
|
+
repository: { owner: 'Ikana', name: 'electron-cli' },
|
|
1401
|
+
tagPrefix: 'release-',
|
|
1402
|
+
prerelease: true,
|
|
1403
|
+
},
|
|
1404
|
+
},
|
|
1405
|
+
],
|
|
1406
|
+
};
|
|
1407
|
+
"#,
|
|
1408
|
+
)
|
|
1409
|
+
.expect("forge config should be written");
|
|
1410
|
+
write_app_file(&root);
|
|
1411
|
+
write_fake_electron_dist(&root);
|
|
1412
|
+
|
|
1413
|
+
let mut args = publish_args(root.clone(), true);
|
|
1414
|
+
args.publisher = None;
|
|
1415
|
+
args.github_api_url = None;
|
|
1416
|
+
args.channel = None;
|
|
1417
|
+
let report = build_report(&args).expect("report should build");
|
|
1418
|
+
|
|
1419
|
+
assert_eq!(report.publisher, "github");
|
|
1420
|
+
let github = report.github.as_ref().expect("github plan should exist");
|
|
1421
|
+
assert_eq!(github.repo, "Ikana/electron-cli");
|
|
1422
|
+
assert_eq!(github.tag, "release-0.1.0");
|
|
1423
|
+
assert!(github.prerelease);
|
|
1424
|
+
|
|
1425
|
+
let _ = fs::remove_dir_all(root);
|
|
1426
|
+
}
|
|
1427
|
+
|
|
1397
1428
|
#[test]
|
|
1398
1429
|
fn builds_publish_reports_from_configured_makers_and_publishers() {
|
|
1399
1430
|
let root = unique_temp_dir("configured-publishers");
|
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
use std::{
|
|
2
|
+
fs,
|
|
3
|
+
path::{Path, PathBuf},
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
use anyhow::{anyhow, Context, Result};
|
|
7
|
+
use serde_json::Value as JsonValue;
|
|
8
|
+
|
|
9
|
+
use crate::project::ProjectSnapshot;
|
|
10
|
+
|
|
11
|
+
#[derive(Clone, Debug, Default)]
|
|
12
|
+
pub(crate) struct ProjectConfig {
|
|
13
|
+
package: Option<JsonValue>,
|
|
14
|
+
forge: Option<JsonValue>,
|
|
15
|
+
electron_cli: Option<JsonValue>,
|
|
16
|
+
warnings: Vec<String>,
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
pub(crate) fn read(snapshot: &ProjectSnapshot) -> Result<ProjectConfig> {
|
|
20
|
+
let package = read_package_json(snapshot)?;
|
|
21
|
+
let root = Path::new(snapshot.root.as_str());
|
|
22
|
+
let mut warnings = Vec::new();
|
|
23
|
+
let forge = resolve_forge_config(root, package.as_ref(), &mut warnings);
|
|
24
|
+
let electron_cli = package
|
|
25
|
+
.as_ref()
|
|
26
|
+
.and_then(|package| {
|
|
27
|
+
package
|
|
28
|
+
.get("electronCli")
|
|
29
|
+
.or_else(|| package.get("electron-cli"))
|
|
30
|
+
})
|
|
31
|
+
.cloned();
|
|
32
|
+
|
|
33
|
+
Ok(ProjectConfig {
|
|
34
|
+
package,
|
|
35
|
+
forge,
|
|
36
|
+
electron_cli,
|
|
37
|
+
warnings,
|
|
38
|
+
})
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fn read_package_json(snapshot: &ProjectSnapshot) -> Result<Option<JsonValue>> {
|
|
42
|
+
let Some(package_json_path) = &snapshot.package_json else {
|
|
43
|
+
return Ok(None);
|
|
44
|
+
};
|
|
45
|
+
let package_json_path = Path::new(package_json_path.as_str());
|
|
46
|
+
let raw = fs::read_to_string(package_json_path)
|
|
47
|
+
.with_context(|| format!("Could not read {}", package_json_path.display()))?;
|
|
48
|
+
serde_json::from_str::<JsonValue>(&raw)
|
|
49
|
+
.with_context(|| format!("Could not parse {}", package_json_path.display()))
|
|
50
|
+
.map(Some)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
fn resolve_forge_config(
|
|
54
|
+
root: &Path,
|
|
55
|
+
package: Option<&JsonValue>,
|
|
56
|
+
warnings: &mut Vec<String>,
|
|
57
|
+
) -> Option<JsonValue> {
|
|
58
|
+
match package
|
|
59
|
+
.and_then(|package| package.get("config"))
|
|
60
|
+
.and_then(|config| config.get("forge"))
|
|
61
|
+
{
|
|
62
|
+
Some(JsonValue::Object(_)) => {
|
|
63
|
+
return package
|
|
64
|
+
.and_then(|package| package.get("config"))
|
|
65
|
+
.and_then(|config| config.get("forge"))
|
|
66
|
+
.cloned()
|
|
67
|
+
}
|
|
68
|
+
Some(JsonValue::String(path)) => {
|
|
69
|
+
return read_forge_config_file(root, Path::new(path), warnings);
|
|
70
|
+
}
|
|
71
|
+
Some(_) => {
|
|
72
|
+
warnings.push(
|
|
73
|
+
"package.json config.forge must be an object or relative config file path."
|
|
74
|
+
.to_string(),
|
|
75
|
+
);
|
|
76
|
+
return None;
|
|
77
|
+
}
|
|
78
|
+
None => {}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
for candidate in [
|
|
82
|
+
"forge.config.js",
|
|
83
|
+
"forge.config.cjs",
|
|
84
|
+
"forge.config.mjs",
|
|
85
|
+
"forge.config.ts",
|
|
86
|
+
] {
|
|
87
|
+
let path = root.join(candidate);
|
|
88
|
+
if path.exists() {
|
|
89
|
+
return read_forge_config_file(root, &PathBuf::from(candidate), warnings);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
None
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
fn read_forge_config_file(
|
|
97
|
+
root: &Path,
|
|
98
|
+
configured_path: &Path,
|
|
99
|
+
warnings: &mut Vec<String>,
|
|
100
|
+
) -> Option<JsonValue> {
|
|
101
|
+
let path = if configured_path.is_absolute() {
|
|
102
|
+
configured_path.to_path_buf()
|
|
103
|
+
} else {
|
|
104
|
+
root.join(configured_path)
|
|
105
|
+
};
|
|
106
|
+
let display = path.display();
|
|
107
|
+
|
|
108
|
+
let raw = match fs::read_to_string(&path) {
|
|
109
|
+
Ok(raw) => raw,
|
|
110
|
+
Err(error) => {
|
|
111
|
+
warnings.push(format!(
|
|
112
|
+
"Could not read Forge config file {display}: {error}."
|
|
113
|
+
));
|
|
114
|
+
return None;
|
|
115
|
+
}
|
|
116
|
+
};
|
|
117
|
+
|
|
118
|
+
match parse_forge_config_file(&raw, &path) {
|
|
119
|
+
Ok(config) => Some(config),
|
|
120
|
+
Err(error) => {
|
|
121
|
+
warnings.push(format!(
|
|
122
|
+
"Could not parse Forge config file {display} without JavaScript execution: {error}."
|
|
123
|
+
));
|
|
124
|
+
None
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
fn parse_forge_config_file(raw: &str, path: &Path) -> Result<JsonValue> {
|
|
130
|
+
if path.extension().and_then(|extension| extension.to_str()) == Some("json") {
|
|
131
|
+
return serde_json::from_str(raw).with_context(|| "Forge JSON config is not valid JSON");
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
let object_literal = extract_static_config_object(raw)
|
|
135
|
+
.ok_or_else(|| anyhow!("expected a static object export"))?;
|
|
136
|
+
json5::from_str::<JsonValue>(&object_literal)
|
|
137
|
+
.with_context(|| "static Forge config object is not valid JSON5")
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
fn extract_static_config_object(source: &str) -> Option<String> {
|
|
141
|
+
for marker in ["module.exports", "exports.default"] {
|
|
142
|
+
if let Some(object) = extract_assignment_object(source, marker) {
|
|
143
|
+
return Some(object);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if let Some(object) = extract_export_default_object(source) {
|
|
148
|
+
return Some(object);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if let Some(identifier) = export_default_identifier(source)
|
|
152
|
+
.or_else(|| assignment_identifier(source, "module.exports"))
|
|
153
|
+
.or_else(|| assignment_identifier(source, "exports.default"))
|
|
154
|
+
{
|
|
155
|
+
return extract_variable_object(source, &identifier);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
None
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
fn extract_assignment_object(source: &str, marker: &str) -> Option<String> {
|
|
162
|
+
let marker_index = source.find(marker)?;
|
|
163
|
+
let after_marker = &source[marker_index + marker.len()..];
|
|
164
|
+
let equals = after_marker.find('=')?;
|
|
165
|
+
let after_equals_start = marker_index + marker.len() + equals + 1;
|
|
166
|
+
let object_start = find_next_object_start(source, after_equals_start)?;
|
|
167
|
+
extract_balanced_object(source, object_start)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
fn extract_export_default_object(source: &str) -> Option<String> {
|
|
171
|
+
let marker = "export default";
|
|
172
|
+
let marker_index = source.find(marker)?;
|
|
173
|
+
let after_marker = marker_index + marker.len();
|
|
174
|
+
let object_start = find_next_object_start(source, after_marker)?;
|
|
175
|
+
let identifier = read_identifier(source, skip_whitespace(source, after_marker)).0;
|
|
176
|
+
if identifier.is_some() {
|
|
177
|
+
return None;
|
|
178
|
+
}
|
|
179
|
+
extract_balanced_object(source, object_start)
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
fn export_default_identifier(source: &str) -> Option<String> {
|
|
183
|
+
let marker = "export default";
|
|
184
|
+
let marker_index = source.find(marker)?;
|
|
185
|
+
let start = skip_whitespace(source, marker_index + marker.len());
|
|
186
|
+
read_identifier(source, start).0
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
fn assignment_identifier(source: &str, marker: &str) -> Option<String> {
|
|
190
|
+
let marker_index = source.find(marker)?;
|
|
191
|
+
let after_marker = &source[marker_index + marker.len()..];
|
|
192
|
+
let equals = after_marker.find('=')?;
|
|
193
|
+
let start = skip_whitespace(source, marker_index + marker.len() + equals + 1);
|
|
194
|
+
read_identifier(source, start).0
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
fn extract_variable_object(source: &str, identifier: &str) -> Option<String> {
|
|
198
|
+
for keyword in ["const", "let", "var"] {
|
|
199
|
+
for (keyword_index, _) in source.match_indices(keyword) {
|
|
200
|
+
if !is_word_boundary(source, keyword_index, keyword.len()) {
|
|
201
|
+
continue;
|
|
202
|
+
}
|
|
203
|
+
let start = skip_whitespace(source, keyword_index + keyword.len());
|
|
204
|
+
let (name, after_name) = read_identifier(source, start);
|
|
205
|
+
if name.as_deref() != Some(identifier) {
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
let rest = &source[after_name..];
|
|
209
|
+
let equals = rest.find('=')?;
|
|
210
|
+
let object_start = find_next_object_start(source, after_name + equals + 1)?;
|
|
211
|
+
return extract_balanced_object(source, object_start);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
None
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
fn find_next_object_start(source: &str, start: usize) -> Option<usize> {
|
|
219
|
+
let bytes = source.as_bytes();
|
|
220
|
+
let mut index = start;
|
|
221
|
+
while index < bytes.len() {
|
|
222
|
+
match bytes[index] {
|
|
223
|
+
b'{' => return Some(index),
|
|
224
|
+
b';' | b'\n' if !source[start..index].trim().is_empty() => return None,
|
|
225
|
+
_ => index += 1,
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
None
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
fn extract_balanced_object(source: &str, object_start: usize) -> Option<String> {
|
|
232
|
+
let bytes = source.as_bytes();
|
|
233
|
+
let mut index = object_start;
|
|
234
|
+
let mut depth = 0usize;
|
|
235
|
+
let mut state = ScanState::Normal;
|
|
236
|
+
|
|
237
|
+
while index < bytes.len() {
|
|
238
|
+
match state {
|
|
239
|
+
ScanState::Normal => match bytes[index] {
|
|
240
|
+
b'{' => {
|
|
241
|
+
depth += 1;
|
|
242
|
+
index += 1;
|
|
243
|
+
}
|
|
244
|
+
b'}' => {
|
|
245
|
+
depth = depth.checked_sub(1)?;
|
|
246
|
+
index += 1;
|
|
247
|
+
if depth == 0 {
|
|
248
|
+
return Some(source[object_start..index].to_string());
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
b'\'' | b'"' | b'`' => {
|
|
252
|
+
state = ScanState::String(bytes[index]);
|
|
253
|
+
index += 1;
|
|
254
|
+
}
|
|
255
|
+
b'/' if bytes.get(index + 1) == Some(&b'/') => {
|
|
256
|
+
state = ScanState::LineComment;
|
|
257
|
+
index += 2;
|
|
258
|
+
}
|
|
259
|
+
b'/' if bytes.get(index + 1) == Some(&b'*') => {
|
|
260
|
+
state = ScanState::BlockComment;
|
|
261
|
+
index += 2;
|
|
262
|
+
}
|
|
263
|
+
_ => index += 1,
|
|
264
|
+
},
|
|
265
|
+
ScanState::String(quote) => {
|
|
266
|
+
if bytes[index] == b'\\' {
|
|
267
|
+
index = (index + 2).min(bytes.len());
|
|
268
|
+
} else if bytes[index] == quote {
|
|
269
|
+
state = ScanState::Normal;
|
|
270
|
+
index += 1;
|
|
271
|
+
} else {
|
|
272
|
+
index += 1;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
ScanState::LineComment => {
|
|
276
|
+
if bytes[index] == b'\n' {
|
|
277
|
+
state = ScanState::Normal;
|
|
278
|
+
}
|
|
279
|
+
index += 1;
|
|
280
|
+
}
|
|
281
|
+
ScanState::BlockComment => {
|
|
282
|
+
if bytes[index] == b'*' && bytes.get(index + 1) == Some(&b'/') {
|
|
283
|
+
state = ScanState::Normal;
|
|
284
|
+
index += 2;
|
|
285
|
+
} else {
|
|
286
|
+
index += 1;
|
|
287
|
+
}
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
None
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
fn skip_whitespace(source: &str, start: usize) -> usize {
|
|
296
|
+
let bytes = source.as_bytes();
|
|
297
|
+
let mut index = start;
|
|
298
|
+
while index < bytes.len() && bytes[index].is_ascii_whitespace() {
|
|
299
|
+
index += 1;
|
|
300
|
+
}
|
|
301
|
+
index
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fn read_identifier(source: &str, start: usize) -> (Option<String>, usize) {
|
|
305
|
+
let bytes = source.as_bytes();
|
|
306
|
+
if bytes.get(start).is_none_or(|byte| !identifier_start(*byte)) {
|
|
307
|
+
return (None, start);
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
let mut index = start + 1;
|
|
311
|
+
while index < bytes.len() && identifier_continue(bytes[index]) {
|
|
312
|
+
index += 1;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
(Some(source[start..index].to_string()), index)
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
fn is_word_boundary(source: &str, start: usize, len: usize) -> bool {
|
|
319
|
+
let bytes = source.as_bytes();
|
|
320
|
+
let before = start
|
|
321
|
+
.checked_sub(1)
|
|
322
|
+
.and_then(|index| bytes.get(index))
|
|
323
|
+
.copied();
|
|
324
|
+
let after = bytes.get(start + len).copied();
|
|
325
|
+
before.is_none_or(|byte| !identifier_continue(byte))
|
|
326
|
+
&& after.is_none_or(|byte| !identifier_continue(byte))
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
fn identifier_start(byte: u8) -> bool {
|
|
330
|
+
byte.is_ascii_alphabetic() || matches!(byte, b'_' | b'$')
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
fn identifier_continue(byte: u8) -> bool {
|
|
334
|
+
identifier_start(byte) || byte.is_ascii_digit()
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
#[derive(Clone, Copy, Debug)]
|
|
338
|
+
enum ScanState {
|
|
339
|
+
Normal,
|
|
340
|
+
String(u8),
|
|
341
|
+
LineComment,
|
|
342
|
+
BlockComment,
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
impl ProjectConfig {
|
|
346
|
+
pub(crate) fn package(&self) -> Option<&JsonValue> {
|
|
347
|
+
self.package.as_ref()
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
pub(crate) fn forge(&self) -> Option<&JsonValue> {
|
|
351
|
+
self.forge.as_ref()
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
pub(crate) fn electron_cli(&self) -> Option<&JsonValue> {
|
|
355
|
+
self.electron_cli.as_ref()
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
pub(crate) fn warnings(&self) -> &[String] {
|
|
359
|
+
&self.warnings
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
#[cfg(test)]
|
|
364
|
+
mod tests {
|
|
365
|
+
use super::*;
|
|
366
|
+
use camino::Utf8PathBuf;
|
|
367
|
+
|
|
368
|
+
#[test]
|
|
369
|
+
fn parses_commonjs_static_forge_config() {
|
|
370
|
+
let config = parse_forge_config_file(
|
|
371
|
+
r#"
|
|
372
|
+
module.exports = {
|
|
373
|
+
packagerConfig: {
|
|
374
|
+
name: 'Desk Tool',
|
|
375
|
+
},
|
|
376
|
+
makers: [
|
|
377
|
+
{ name: '@electron-forge/maker-zip' },
|
|
378
|
+
],
|
|
379
|
+
};
|
|
380
|
+
"#,
|
|
381
|
+
Path::new("forge.config.js"),
|
|
382
|
+
)
|
|
383
|
+
.expect("config should parse");
|
|
384
|
+
|
|
385
|
+
assert_eq!(
|
|
386
|
+
config
|
|
387
|
+
.get("packagerConfig")
|
|
388
|
+
.and_then(|config| config.get("name"))
|
|
389
|
+
.and_then(JsonValue::as_str),
|
|
390
|
+
Some("Desk Tool")
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
#[test]
|
|
395
|
+
fn parses_typescript_exported_config_identifier() {
|
|
396
|
+
let config = parse_forge_config_file(
|
|
397
|
+
r#"
|
|
398
|
+
import type { ForgeConfig } from '@electron-forge/shared-types';
|
|
399
|
+
|
|
400
|
+
const config: ForgeConfig = {
|
|
401
|
+
publishers: [
|
|
402
|
+
{
|
|
403
|
+
name: '@electron-forge/publisher-github',
|
|
404
|
+
platforms: ['darwin'],
|
|
405
|
+
config: { repository: { owner: 'Ikana', name: 'electron-cli' } },
|
|
406
|
+
},
|
|
407
|
+
],
|
|
408
|
+
};
|
|
409
|
+
|
|
410
|
+
export default config;
|
|
411
|
+
"#,
|
|
412
|
+
Path::new("forge.config.ts"),
|
|
413
|
+
)
|
|
414
|
+
.expect("config should parse");
|
|
415
|
+
|
|
416
|
+
assert_eq!(
|
|
417
|
+
config
|
|
418
|
+
.get("publishers")
|
|
419
|
+
.and_then(JsonValue::as_array)
|
|
420
|
+
.and_then(|publishers| publishers.first())
|
|
421
|
+
.and_then(|publisher| publisher.get("platforms"))
|
|
422
|
+
.and_then(JsonValue::as_array)
|
|
423
|
+
.and_then(|platforms| platforms.first())
|
|
424
|
+
.and_then(JsonValue::as_str),
|
|
425
|
+
Some("darwin")
|
|
426
|
+
);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
#[test]
|
|
430
|
+
fn reads_config_path_from_package_json() {
|
|
431
|
+
let root = unique_temp_dir("config-path");
|
|
432
|
+
fs::write(
|
|
433
|
+
root.join("package.json"),
|
|
434
|
+
r#"{"name":"app","config":{"forge":"./build/forge.config.js"}}"#,
|
|
435
|
+
)
|
|
436
|
+
.expect("package.json should be written");
|
|
437
|
+
fs::create_dir_all(root.join("build")).expect("build dir should be created");
|
|
438
|
+
fs::write(
|
|
439
|
+
root.join("build/forge.config.js"),
|
|
440
|
+
"module.exports = { makers: [{ name: '@electron-forge/maker-deb' }] };",
|
|
441
|
+
)
|
|
442
|
+
.expect("forge config should be written");
|
|
443
|
+
|
|
444
|
+
let snapshot = snapshot(&root);
|
|
445
|
+
let config = read(&snapshot).expect("config should read");
|
|
446
|
+
|
|
447
|
+
assert_eq!(
|
|
448
|
+
config
|
|
449
|
+
.forge()
|
|
450
|
+
.and_then(|forge| forge.get("makers"))
|
|
451
|
+
.and_then(JsonValue::as_array)
|
|
452
|
+
.and_then(|makers| makers.first())
|
|
453
|
+
.and_then(|maker| maker.get("name"))
|
|
454
|
+
.and_then(JsonValue::as_str),
|
|
455
|
+
Some("@electron-forge/maker-deb")
|
|
456
|
+
);
|
|
457
|
+
assert!(config.warnings().is_empty());
|
|
458
|
+
|
|
459
|
+
let _ = fs::remove_dir_all(root);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
#[test]
|
|
463
|
+
fn discovers_default_forge_config_js() {
|
|
464
|
+
let root = unique_temp_dir("default-config");
|
|
465
|
+
fs::write(root.join("package.json"), r#"{"name":"app"}"#)
|
|
466
|
+
.expect("package.json should be written");
|
|
467
|
+
fs::write(
|
|
468
|
+
root.join("forge.config.js"),
|
|
469
|
+
"module.exports = { packagerConfig: { executableName: 'desk-tool' } };",
|
|
470
|
+
)
|
|
471
|
+
.expect("forge config should be written");
|
|
472
|
+
|
|
473
|
+
let snapshot = snapshot(&root);
|
|
474
|
+
let config = read(&snapshot).expect("config should read");
|
|
475
|
+
|
|
476
|
+
assert_eq!(
|
|
477
|
+
config
|
|
478
|
+
.forge()
|
|
479
|
+
.and_then(|forge| forge.get("packagerConfig"))
|
|
480
|
+
.and_then(|packager| packager.get("executableName"))
|
|
481
|
+
.and_then(JsonValue::as_str),
|
|
482
|
+
Some("desk-tool")
|
|
483
|
+
);
|
|
484
|
+
|
|
485
|
+
let _ = fs::remove_dir_all(root);
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
#[test]
|
|
489
|
+
fn warns_when_config_requires_javascript_execution() {
|
|
490
|
+
let root = unique_temp_dir("dynamic-config");
|
|
491
|
+
fs::write(root.join("package.json"), r#"{"name":"app"}"#)
|
|
492
|
+
.expect("package.json should be written");
|
|
493
|
+
fs::write(
|
|
494
|
+
root.join("forge.config.js"),
|
|
495
|
+
"module.exports = buildConfig(process.env.NODE_ENV);",
|
|
496
|
+
)
|
|
497
|
+
.expect("forge config should be written");
|
|
498
|
+
|
|
499
|
+
let snapshot = snapshot(&root);
|
|
500
|
+
let config = read(&snapshot).expect("config should read");
|
|
501
|
+
|
|
502
|
+
assert!(config.forge().is_none());
|
|
503
|
+
assert!(config
|
|
504
|
+
.warnings()
|
|
505
|
+
.iter()
|
|
506
|
+
.any(|warning| warning.contains("without JavaScript execution")));
|
|
507
|
+
|
|
508
|
+
let _ = fs::remove_dir_all(root);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
fn snapshot(root: &Path) -> ProjectSnapshot {
|
|
512
|
+
ProjectSnapshot {
|
|
513
|
+
root: Utf8PathBuf::from_path_buf(root.to_path_buf()).expect("root should be utf-8"),
|
|
514
|
+
package_json: Some(
|
|
515
|
+
Utf8PathBuf::from_path_buf(root.join("package.json"))
|
|
516
|
+
.expect("package path should be utf-8"),
|
|
517
|
+
),
|
|
518
|
+
name: Some("app".to_string()),
|
|
519
|
+
version: None,
|
|
520
|
+
repository: None,
|
|
521
|
+
license: None,
|
|
522
|
+
main: Some("src/main.js".to_string()),
|
|
523
|
+
package_manager: None,
|
|
524
|
+
scripts: Default::default(),
|
|
525
|
+
dependencies: Default::default(),
|
|
526
|
+
dev_dependencies: Default::default(),
|
|
527
|
+
optional_dependencies: Default::default(),
|
|
528
|
+
peer_dependencies: Default::default(),
|
|
529
|
+
electron_dependency: Some("30.0.0".to_string()),
|
|
530
|
+
forge_dependencies: Default::default(),
|
|
531
|
+
signals: Vec::new(),
|
|
532
|
+
}
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fn unique_temp_dir(label: &str) -> PathBuf {
|
|
536
|
+
let nanos = std::time::SystemTime::now()
|
|
537
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
538
|
+
.expect("clock should be after epoch")
|
|
539
|
+
.as_nanos();
|
|
540
|
+
let path = std::env::temp_dir().join(format!(
|
|
541
|
+
"electron-cli-forge-config-{label}-{}-{nanos}",
|
|
542
|
+
std::process::id()
|
|
543
|
+
));
|
|
544
|
+
fs::create_dir_all(&path).expect("temp dir should be created");
|
|
545
|
+
path
|
|
546
|
+
}
|
|
547
|
+
}
|