electron-cli 0.3.0-alpha.2 → 0.3.0-alpha.20
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 +5380 -101
- package/Cargo.toml +17 -1
- package/README.md +103 -12
- package/package.json +2 -1
- package/src/cli.rs +226 -4
- package/src/commands/init.rs +443 -27
- package/src/commands/make.rs +3076 -0
- package/src/commands/mod.rs +4 -0
- package/src/commands/package.rs +3238 -0
- package/src/commands/plan.rs +65 -5
- package/src/commands/publish.rs +1832 -0
- package/src/commands/start.rs +287 -0
- package/src/forge_config.rs +547 -0
- package/src/main.rs +5 -0
- package/src/project.rs +52 -1
- package/templates/minimal/gitignore +5 -0
- package/templates/minimal/src/index.html +82 -0
- package/templates/minimal/src/main.js +33 -0
- package/templates/minimal/src/preload.js +6 -0
- package/templates/minimal/src/renderer.js +5 -0
package/src/commands/init.rs
CHANGED
|
@@ -13,22 +13,40 @@ use crate::{
|
|
|
13
13
|
output,
|
|
14
14
|
};
|
|
15
15
|
|
|
16
|
+
const NATIVE_TEMPLATE_NAME: &str = "minimal";
|
|
17
|
+
const MAIN_JS: &str = include_str!("../../templates/minimal/src/main.js");
|
|
18
|
+
const PRELOAD_JS: &str = include_str!("../../templates/minimal/src/preload.js");
|
|
19
|
+
const INDEX_HTML: &str = include_str!("../../templates/minimal/src/index.html");
|
|
20
|
+
const RENDERER_JS: &str = include_str!("../../templates/minimal/src/renderer.js");
|
|
21
|
+
const GITIGNORE: &str = include_str!("../../templates/minimal/gitignore");
|
|
22
|
+
|
|
16
23
|
#[derive(Debug, Serialize)]
|
|
17
24
|
struct InitReport {
|
|
18
25
|
cwd: Utf8PathBuf,
|
|
19
26
|
target_dir: Utf8PathBuf,
|
|
20
27
|
target_arg: String,
|
|
21
28
|
template: String,
|
|
29
|
+
template_kind: InitTemplateKind,
|
|
30
|
+
generator: String,
|
|
22
31
|
package_manager: String,
|
|
32
|
+
electron_version: Option<String>,
|
|
23
33
|
dry_run: bool,
|
|
24
34
|
command: Vec<String>,
|
|
25
35
|
command_display: String,
|
|
36
|
+
create_files: Vec<String>,
|
|
26
37
|
post_create_files: Vec<String>,
|
|
27
38
|
next_steps: Vec<String>,
|
|
28
39
|
warnings: Vec<String>,
|
|
29
40
|
status: InitStatus,
|
|
30
41
|
}
|
|
31
42
|
|
|
43
|
+
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize)]
|
|
44
|
+
#[serde(rename_all = "kebab-case")]
|
|
45
|
+
enum InitTemplateKind {
|
|
46
|
+
Native,
|
|
47
|
+
Forge,
|
|
48
|
+
}
|
|
49
|
+
|
|
32
50
|
#[derive(Debug, Serialize)]
|
|
33
51
|
#[serde(rename_all = "kebab-case")]
|
|
34
52
|
enum InitStatus {
|
|
@@ -39,9 +57,10 @@ enum InitStatus {
|
|
|
39
57
|
#[derive(Debug, Serialize)]
|
|
40
58
|
struct ElectronCliConfig {
|
|
41
59
|
version: &'static str,
|
|
42
|
-
generator:
|
|
60
|
+
generator: String,
|
|
43
61
|
template: String,
|
|
44
62
|
package_manager: String,
|
|
63
|
+
electron_version: Option<String>,
|
|
45
64
|
}
|
|
46
65
|
|
|
47
66
|
pub fn run(args: InitArgs) -> Result<()> {
|
|
@@ -79,8 +98,31 @@ fn build_plan(args: &InitArgs) -> Result<InitReport> {
|
|
|
79
98
|
let package_manager = args
|
|
80
99
|
.package_manager
|
|
81
100
|
.unwrap_or_else(|| detect_package_manager(&cwd));
|
|
82
|
-
let
|
|
83
|
-
let
|
|
101
|
+
let template_kind = template_kind(&args.template);
|
|
102
|
+
let electron_version = match template_kind {
|
|
103
|
+
InitTemplateKind::Native => Some(
|
|
104
|
+
args.electron_version
|
|
105
|
+
.clone()
|
|
106
|
+
.unwrap_or_else(|| "latest".to_string()),
|
|
107
|
+
),
|
|
108
|
+
InitTemplateKind::Forge => args.electron_version.clone(),
|
|
109
|
+
};
|
|
110
|
+
let command = match template_kind {
|
|
111
|
+
InitTemplateKind::Native => Vec::new(),
|
|
112
|
+
InitTemplateKind::Forge => create_forge_command(package_manager, &target_arg, args),
|
|
113
|
+
};
|
|
114
|
+
let command_display = match template_kind {
|
|
115
|
+
InitTemplateKind::Native => "write built-in minimal template files".to_string(),
|
|
116
|
+
InitTemplateKind::Forge => display_command(&command),
|
|
117
|
+
};
|
|
118
|
+
let generator = match template_kind {
|
|
119
|
+
InitTemplateKind::Native => "electron-cli".to_string(),
|
|
120
|
+
InitTemplateKind::Forge => "create-electron-app@latest".to_string(),
|
|
121
|
+
};
|
|
122
|
+
let create_files = match template_kind {
|
|
123
|
+
InitTemplateKind::Native => native_template_paths(args.copy_ci_files),
|
|
124
|
+
InitTemplateKind::Forge => Vec::new(),
|
|
125
|
+
};
|
|
84
126
|
let target_label = target_arg.clone();
|
|
85
127
|
|
|
86
128
|
let mut warnings = Vec::new();
|
|
@@ -92,31 +134,46 @@ fn build_plan(args: &InitArgs) -> Result<InitReport> {
|
|
|
92
134
|
}
|
|
93
135
|
|
|
94
136
|
if target_dir.exists() && !args.force {
|
|
95
|
-
|
|
96
|
-
|
|
137
|
+
let force_message = match template_kind {
|
|
138
|
+
InitTemplateKind::Native => {
|
|
139
|
+
"Use --force to overwrite files generated by the native template."
|
|
140
|
+
}
|
|
141
|
+
InitTemplateKind::Forge => {
|
|
142
|
+
"Use --force to allow create-electron-app to overwrite the target."
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
warnings.push(force_message.to_string());
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
let mut next_steps = Vec::new();
|
|
149
|
+
next_steps.push(format!("cd {target_label}"));
|
|
150
|
+
if template_kind == InitTemplateKind::Native {
|
|
151
|
+
next_steps.push(install_command(package_manager));
|
|
97
152
|
}
|
|
153
|
+
next_steps.push(start_command(package_manager));
|
|
154
|
+
next_steps.push("electron-cli doctor --json".to_string());
|
|
98
155
|
|
|
99
156
|
Ok(InitReport {
|
|
100
157
|
cwd: utf8_path(cwd)?,
|
|
101
158
|
target_dir: utf8_path(target_dir)?,
|
|
102
159
|
target_arg,
|
|
103
160
|
template: args.template.clone(),
|
|
161
|
+
template_kind,
|
|
162
|
+
generator,
|
|
104
163
|
package_manager: package_manager.as_str().to_string(),
|
|
164
|
+
electron_version,
|
|
105
165
|
dry_run: args.dry_run,
|
|
106
166
|
command,
|
|
107
167
|
command_display,
|
|
168
|
+
create_files,
|
|
108
169
|
post_create_files: vec![".electron-cli.json".to_string()],
|
|
109
|
-
next_steps
|
|
110
|
-
format!("cd {target_label}"),
|
|
111
|
-
start_command(package_manager),
|
|
112
|
-
"electron-cli doctor --json".to_string(),
|
|
113
|
-
],
|
|
170
|
+
next_steps,
|
|
114
171
|
warnings,
|
|
115
172
|
status: InitStatus::Planned,
|
|
116
173
|
})
|
|
117
174
|
}
|
|
118
175
|
|
|
119
|
-
fn
|
|
176
|
+
fn create_forge_command(
|
|
120
177
|
package_manager: PackageManager,
|
|
121
178
|
target_arg: &str,
|
|
122
179
|
args: &InitArgs,
|
|
@@ -171,6 +228,13 @@ fn create_command(
|
|
|
171
228
|
}
|
|
172
229
|
|
|
173
230
|
fn execute_plan(plan: &InitReport) -> Result<()> {
|
|
231
|
+
match plan.template_kind {
|
|
232
|
+
InitTemplateKind::Native => write_native_template(plan),
|
|
233
|
+
InitTemplateKind::Forge => execute_forge_plan(plan),
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
fn execute_forge_plan(plan: &InitReport) -> Result<()> {
|
|
174
238
|
let (program, args) = plan
|
|
175
239
|
.command
|
|
176
240
|
.split_first()
|
|
@@ -195,6 +259,10 @@ fn execute_plan(plan: &InitReport) -> Result<()> {
|
|
|
195
259
|
fn ensure_target_can_be_created(plan: &InitReport, force: bool) -> Result<()> {
|
|
196
260
|
let target = Path::new(plan.target_dir.as_str());
|
|
197
261
|
|
|
262
|
+
if target.exists() && !target.is_dir() {
|
|
263
|
+
bail!("Target exists but is not a directory: {}", plan.target_dir);
|
|
264
|
+
}
|
|
265
|
+
|
|
198
266
|
if target.exists() && !force {
|
|
199
267
|
bail!(
|
|
200
268
|
"Target directory already exists: {}. Use --force to overwrite it.",
|
|
@@ -208,9 +276,10 @@ fn ensure_target_can_be_created(plan: &InitReport, force: bool) -> Result<()> {
|
|
|
208
276
|
fn write_project_config(plan: &InitReport) -> Result<()> {
|
|
209
277
|
let config = ElectronCliConfig {
|
|
210
278
|
version: env!("CARGO_PKG_VERSION"),
|
|
211
|
-
generator:
|
|
279
|
+
generator: plan.generator.clone(),
|
|
212
280
|
template: plan.template.clone(),
|
|
213
281
|
package_manager: plan.package_manager.clone(),
|
|
282
|
+
electron_version: plan.electron_version.clone(),
|
|
214
283
|
};
|
|
215
284
|
let config_path = Path::new(plan.target_dir.as_str()).join(".electron-cli.json");
|
|
216
285
|
let json = serde_json::to_string_pretty(&config)?;
|
|
@@ -232,13 +301,25 @@ fn print_report(report: &InitReport, json: bool) -> Result<()> {
|
|
|
232
301
|
println!(" cwd: {}", report.cwd);
|
|
233
302
|
println!(" target: {}", report.target_dir);
|
|
234
303
|
println!(" template: {}", report.template);
|
|
304
|
+
println!(" generator: {}", report.generator);
|
|
235
305
|
println!(" package manager: {}", report.package_manager);
|
|
306
|
+
if let Some(electron_version) = &report.electron_version {
|
|
307
|
+
println!(" electron: {electron_version}");
|
|
308
|
+
}
|
|
236
309
|
println!(" status: {}", report.status.as_str());
|
|
237
310
|
|
|
238
311
|
println!();
|
|
239
|
-
println!("
|
|
312
|
+
println!("Action");
|
|
240
313
|
println!(" {}", report.command_display);
|
|
241
314
|
|
|
315
|
+
if !report.create_files.is_empty() {
|
|
316
|
+
println!();
|
|
317
|
+
println!("Create Files");
|
|
318
|
+
for file in &report.create_files {
|
|
319
|
+
println!(" {file}");
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
242
323
|
if !report.post_create_files.is_empty() {
|
|
243
324
|
println!();
|
|
244
325
|
println!("Post-Create Files");
|
|
@@ -266,6 +347,203 @@ fn print_report(report: &InitReport, json: bool) -> Result<()> {
|
|
|
266
347
|
Ok(())
|
|
267
348
|
}
|
|
268
349
|
|
|
350
|
+
fn write_native_template(plan: &InitReport) -> Result<()> {
|
|
351
|
+
let target = Path::new(plan.target_dir.as_str());
|
|
352
|
+
fs::create_dir_all(target).with_context(|| format!("Could not create {}", target.display()))?;
|
|
353
|
+
|
|
354
|
+
for file in native_template_files(plan)? {
|
|
355
|
+
write_template_file(target, file.path, &file.contents)?;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
Ok(())
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
struct TemplateFile {
|
|
362
|
+
path: &'static str,
|
|
363
|
+
contents: String,
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
fn native_template_files(plan: &InitReport) -> Result<Vec<TemplateFile>> {
|
|
367
|
+
let package_name = package_name(plan);
|
|
368
|
+
let electron_version = plan.electron_version.as_deref().unwrap_or("latest");
|
|
369
|
+
let package_manager = package_manager_from_str(&plan.package_manager)?;
|
|
370
|
+
let mut files = vec![
|
|
371
|
+
TemplateFile {
|
|
372
|
+
path: "package.json",
|
|
373
|
+
contents: package_json(&package_name, electron_version)?,
|
|
374
|
+
},
|
|
375
|
+
TemplateFile {
|
|
376
|
+
path: "README.md",
|
|
377
|
+
contents: readme(&package_name, package_manager),
|
|
378
|
+
},
|
|
379
|
+
TemplateFile {
|
|
380
|
+
path: ".gitignore",
|
|
381
|
+
contents: GITIGNORE.to_string(),
|
|
382
|
+
},
|
|
383
|
+
TemplateFile {
|
|
384
|
+
path: "src/main.js",
|
|
385
|
+
contents: MAIN_JS.to_string(),
|
|
386
|
+
},
|
|
387
|
+
TemplateFile {
|
|
388
|
+
path: "src/preload.js",
|
|
389
|
+
contents: PRELOAD_JS.to_string(),
|
|
390
|
+
},
|
|
391
|
+
TemplateFile {
|
|
392
|
+
path: "src/index.html",
|
|
393
|
+
contents: INDEX_HTML.to_string(),
|
|
394
|
+
},
|
|
395
|
+
TemplateFile {
|
|
396
|
+
path: "src/renderer.js",
|
|
397
|
+
contents: RENDERER_JS.to_string(),
|
|
398
|
+
},
|
|
399
|
+
];
|
|
400
|
+
|
|
401
|
+
if plan
|
|
402
|
+
.create_files
|
|
403
|
+
.iter()
|
|
404
|
+
.any(|path| path == ".github/workflows/ci.yml")
|
|
405
|
+
{
|
|
406
|
+
files.push(TemplateFile {
|
|
407
|
+
path: ".github/workflows/ci.yml",
|
|
408
|
+
contents: ci_workflow(package_manager),
|
|
409
|
+
});
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
Ok(files)
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
fn native_template_paths(copy_ci_files: bool) -> Vec<String> {
|
|
416
|
+
let mut files = vec![
|
|
417
|
+
"package.json",
|
|
418
|
+
"README.md",
|
|
419
|
+
".gitignore",
|
|
420
|
+
"src/main.js",
|
|
421
|
+
"src/preload.js",
|
|
422
|
+
"src/index.html",
|
|
423
|
+
"src/renderer.js",
|
|
424
|
+
]
|
|
425
|
+
.into_iter()
|
|
426
|
+
.map(str::to_string)
|
|
427
|
+
.collect::<Vec<_>>();
|
|
428
|
+
|
|
429
|
+
if copy_ci_files {
|
|
430
|
+
files.push(".github/workflows/ci.yml".to_string());
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
files
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
fn write_template_file(target: &Path, relative_path: &str, contents: &str) -> Result<()> {
|
|
437
|
+
let path = target.join(relative_path);
|
|
438
|
+
if let Some(parent) = path.parent() {
|
|
439
|
+
fs::create_dir_all(parent)
|
|
440
|
+
.with_context(|| format!("Could not create {}", parent.display()))?;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
fs::write(&path, ensure_trailing_newline(contents))
|
|
444
|
+
.with_context(|| format!("Could not write {}", path.display()))?;
|
|
445
|
+
|
|
446
|
+
Ok(())
|
|
447
|
+
}
|
|
448
|
+
|
|
449
|
+
fn package_json(package_name: &str, electron_version: &str) -> Result<String> {
|
|
450
|
+
let product_name = product_name(package_name);
|
|
451
|
+
let app_bundle_id = format!("com.electron.{package_name}");
|
|
452
|
+
let package = serde_json::json!({
|
|
453
|
+
"name": package_name,
|
|
454
|
+
"productName": product_name,
|
|
455
|
+
"version": "0.1.0",
|
|
456
|
+
"private": true,
|
|
457
|
+
"description": "Minimal Electron app generated by electron-cli",
|
|
458
|
+
"main": "src/main.js",
|
|
459
|
+
"scripts": {
|
|
460
|
+
"start": "electron .",
|
|
461
|
+
"smoke": "electron --version"
|
|
462
|
+
},
|
|
463
|
+
"devDependencies": {
|
|
464
|
+
"electron": electron_version
|
|
465
|
+
},
|
|
466
|
+
"electronCli": {
|
|
467
|
+
"packagerConfig": {
|
|
468
|
+
"appBundleId": app_bundle_id
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
});
|
|
472
|
+
|
|
473
|
+
Ok(serde_json::to_string_pretty(&package)?)
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
fn readme(package_name: &str, package_manager: PackageManager) -> String {
|
|
477
|
+
format!(
|
|
478
|
+
r#"# {package_name}
|
|
479
|
+
|
|
480
|
+
Minimal Electron app generated by electron-cli.
|
|
481
|
+
|
|
482
|
+
## Scripts
|
|
483
|
+
|
|
484
|
+
```sh
|
|
485
|
+
{install}
|
|
486
|
+
{start}
|
|
487
|
+
{smoke}
|
|
488
|
+
```
|
|
489
|
+
"#,
|
|
490
|
+
install = install_command(package_manager),
|
|
491
|
+
start = start_command(package_manager),
|
|
492
|
+
smoke = run_script_command(package_manager, "smoke"),
|
|
493
|
+
)
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
fn ci_workflow(package_manager: PackageManager) -> String {
|
|
497
|
+
let setup = match package_manager {
|
|
498
|
+
PackageManager::Npm => {
|
|
499
|
+
" - uses: actions/setup-node@v6\n with:\n node-version: 22\n"
|
|
500
|
+
.to_string()
|
|
501
|
+
}
|
|
502
|
+
PackageManager::Pnpm | PackageManager::Yarn => {
|
|
503
|
+
" - uses: actions/setup-node@v6\n with:\n node-version: 22\n - run: corepack enable\n"
|
|
504
|
+
.to_string()
|
|
505
|
+
}
|
|
506
|
+
PackageManager::Bun => " - uses: oven-sh/setup-bun@v2\n".to_string(),
|
|
507
|
+
};
|
|
508
|
+
|
|
509
|
+
format!(
|
|
510
|
+
r#"name: CI
|
|
511
|
+
|
|
512
|
+
on:
|
|
513
|
+
pull_request:
|
|
514
|
+
push:
|
|
515
|
+
branches: [main]
|
|
516
|
+
|
|
517
|
+
jobs:
|
|
518
|
+
smoke:
|
|
519
|
+
runs-on: ubuntu-latest
|
|
520
|
+
steps:
|
|
521
|
+
- uses: actions/checkout@v6
|
|
522
|
+
{setup} - run: {install}
|
|
523
|
+
- run: {smoke}
|
|
524
|
+
"#,
|
|
525
|
+
setup = setup,
|
|
526
|
+
install = install_command(package_manager),
|
|
527
|
+
smoke = run_script_command(package_manager, "smoke"),
|
|
528
|
+
)
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fn ensure_trailing_newline(contents: &str) -> String {
|
|
532
|
+
if contents.ends_with('\n') {
|
|
533
|
+
contents.to_string()
|
|
534
|
+
} else {
|
|
535
|
+
format!("{contents}\n")
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
fn template_kind(template: &str) -> InitTemplateKind {
|
|
540
|
+
if template == NATIVE_TEMPLATE_NAME {
|
|
541
|
+
InitTemplateKind::Native
|
|
542
|
+
} else {
|
|
543
|
+
InitTemplateKind::Forge
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
|
|
269
547
|
fn detect_package_manager(cwd: &Path) -> PackageManager {
|
|
270
548
|
if cwd.join("pnpm-lock.yaml").exists() {
|
|
271
549
|
PackageManager::Pnpm
|
|
@@ -278,6 +556,15 @@ fn detect_package_manager(cwd: &Path) -> PackageManager {
|
|
|
278
556
|
}
|
|
279
557
|
}
|
|
280
558
|
|
|
559
|
+
fn install_command(package_manager: PackageManager) -> String {
|
|
560
|
+
match package_manager {
|
|
561
|
+
PackageManager::Npm => "npm install".to_string(),
|
|
562
|
+
PackageManager::Pnpm => "pnpm install".to_string(),
|
|
563
|
+
PackageManager::Yarn => "yarn install".to_string(),
|
|
564
|
+
PackageManager::Bun => "bun install".to_string(),
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
281
568
|
fn start_command(package_manager: PackageManager) -> String {
|
|
282
569
|
match package_manager {
|
|
283
570
|
PackageManager::Npm => "npm start".to_string(),
|
|
@@ -287,6 +574,25 @@ fn start_command(package_manager: PackageManager) -> String {
|
|
|
287
574
|
}
|
|
288
575
|
}
|
|
289
576
|
|
|
577
|
+
fn run_script_command(package_manager: PackageManager, script: &str) -> String {
|
|
578
|
+
match package_manager {
|
|
579
|
+
PackageManager::Npm => format!("npm run {script}"),
|
|
580
|
+
PackageManager::Pnpm => format!("pnpm run {script}"),
|
|
581
|
+
PackageManager::Yarn => format!("yarn {script}"),
|
|
582
|
+
PackageManager::Bun => format!("bun run {script}"),
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
|
|
586
|
+
fn package_manager_from_str(package_manager: &str) -> Result<PackageManager> {
|
|
587
|
+
match package_manager {
|
|
588
|
+
"npm" => Ok(PackageManager::Npm),
|
|
589
|
+
"pnpm" => Ok(PackageManager::Pnpm),
|
|
590
|
+
"yarn" => Ok(PackageManager::Yarn),
|
|
591
|
+
"bun" => Ok(PackageManager::Bun),
|
|
592
|
+
_ => bail!("Unknown package manager in init plan: {package_manager}"),
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
290
596
|
fn display_command(command: &[String]) -> String {
|
|
291
597
|
command
|
|
292
598
|
.iter()
|
|
@@ -310,6 +616,56 @@ fn path_arg(path: &Path) -> String {
|
|
|
310
616
|
path.to_string_lossy().to_string()
|
|
311
617
|
}
|
|
312
618
|
|
|
619
|
+
fn package_name(plan: &InitReport) -> String {
|
|
620
|
+
let raw_name = Path::new(plan.target_dir.as_str())
|
|
621
|
+
.file_name()
|
|
622
|
+
.and_then(|name| name.to_str())
|
|
623
|
+
.unwrap_or("electron-app");
|
|
624
|
+
|
|
625
|
+
sanitize_package_name(raw_name)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
fn sanitize_package_name(raw_name: &str) -> String {
|
|
629
|
+
let name = raw_name
|
|
630
|
+
.to_ascii_lowercase()
|
|
631
|
+
.chars()
|
|
632
|
+
.map(|char| {
|
|
633
|
+
if char.is_ascii_alphanumeric() || matches!(char, '-' | '_' | '.') {
|
|
634
|
+
char
|
|
635
|
+
} else {
|
|
636
|
+
'-'
|
|
637
|
+
}
|
|
638
|
+
})
|
|
639
|
+
.collect::<String>()
|
|
640
|
+
.trim_matches(['-', '.', '_'])
|
|
641
|
+
.to_string();
|
|
642
|
+
|
|
643
|
+
if name.is_empty() {
|
|
644
|
+
"electron-app".to_string()
|
|
645
|
+
} else {
|
|
646
|
+
name
|
|
647
|
+
}
|
|
648
|
+
}
|
|
649
|
+
|
|
650
|
+
fn product_name(package_name: &str) -> String {
|
|
651
|
+
package_name
|
|
652
|
+
.split(['-', '_', '.'])
|
|
653
|
+
.filter(|part| !part.is_empty())
|
|
654
|
+
.map(|part| {
|
|
655
|
+
let mut chars = part.chars();
|
|
656
|
+
match chars.next() {
|
|
657
|
+
Some(first) => {
|
|
658
|
+
let mut word = first.to_uppercase().collect::<String>();
|
|
659
|
+
word.push_str(chars.as_str());
|
|
660
|
+
word
|
|
661
|
+
}
|
|
662
|
+
None => String::new(),
|
|
663
|
+
}
|
|
664
|
+
})
|
|
665
|
+
.collect::<Vec<_>>()
|
|
666
|
+
.join(" ")
|
|
667
|
+
}
|
|
668
|
+
|
|
313
669
|
fn utf8_path(path: PathBuf) -> Result<Utf8PathBuf> {
|
|
314
670
|
Utf8PathBuf::from_path_buf(path).map_err(|path| {
|
|
315
671
|
anyhow::anyhow!(
|
|
@@ -337,7 +693,7 @@ mod tests {
|
|
|
337
693
|
let args = InitArgs {
|
|
338
694
|
dir: PathBuf::from("my-app"),
|
|
339
695
|
cwd: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
|
|
340
|
-
template: "
|
|
696
|
+
template: "minimal".to_string(),
|
|
341
697
|
package_manager: Some(PackageManager::Npm),
|
|
342
698
|
electron_version: None,
|
|
343
699
|
copy_ci_files: false,
|
|
@@ -349,27 +705,23 @@ mod tests {
|
|
|
349
705
|
|
|
350
706
|
let plan = build_plan(&args).expect("plan should build");
|
|
351
707
|
|
|
352
|
-
assert_eq!(
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
"-y",
|
|
357
|
-
"create-electron-app@latest",
|
|
358
|
-
"my-app",
|
|
359
|
-
"--template",
|
|
360
|
-
"vite-typescript",
|
|
361
|
-
"--skip-git",
|
|
362
|
-
]
|
|
363
|
-
);
|
|
708
|
+
assert_eq!(plan.template_kind, InitTemplateKind::Native);
|
|
709
|
+
assert_eq!(plan.generator, "electron-cli");
|
|
710
|
+
assert!(plan.command.is_empty());
|
|
711
|
+
assert!(plan.create_files.contains(&"package.json".to_string()));
|
|
364
712
|
assert_eq!(plan.package_manager, "npm");
|
|
365
713
|
assert_eq!(
|
|
366
714
|
plan.next_steps.first().map(String::as_str),
|
|
367
715
|
Some("cd my-app")
|
|
368
716
|
);
|
|
717
|
+
assert_eq!(
|
|
718
|
+
plan.next_steps.get(1).map(String::as_str),
|
|
719
|
+
Some("npm install")
|
|
720
|
+
);
|
|
369
721
|
}
|
|
370
722
|
|
|
371
723
|
#[test]
|
|
372
|
-
fn
|
|
724
|
+
fn includes_optional_forge_create_flags() {
|
|
373
725
|
let args = InitArgs {
|
|
374
726
|
dir: PathBuf::from("desktop app"),
|
|
375
727
|
cwd: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
|
|
@@ -385,6 +737,8 @@ mod tests {
|
|
|
385
737
|
|
|
386
738
|
let plan = build_plan(&args).expect("plan should build");
|
|
387
739
|
|
|
740
|
+
assert_eq!(plan.template_kind, InitTemplateKind::Forge);
|
|
741
|
+
assert_eq!(plan.generator, "create-electron-app@latest");
|
|
388
742
|
assert_eq!(plan.command[0], "pnpm");
|
|
389
743
|
assert!(plan.command.contains(&"--copy-ci-files".to_string()));
|
|
390
744
|
assert!(plan.command.contains(&"--force".to_string()));
|
|
@@ -395,4 +749,66 @@ mod tests {
|
|
|
395
749
|
Some("pnpm start")
|
|
396
750
|
);
|
|
397
751
|
}
|
|
752
|
+
|
|
753
|
+
#[test]
|
|
754
|
+
fn writes_native_template_files() {
|
|
755
|
+
let cwd = unique_temp_dir();
|
|
756
|
+
let args = InitArgs {
|
|
757
|
+
dir: PathBuf::from("native app"),
|
|
758
|
+
cwd: cwd.clone(),
|
|
759
|
+
template: "minimal".to_string(),
|
|
760
|
+
package_manager: Some(PackageManager::Npm),
|
|
761
|
+
electron_version: Some("30.0.0".to_string()),
|
|
762
|
+
copy_ci_files: true,
|
|
763
|
+
force: false,
|
|
764
|
+
skip_git: true,
|
|
765
|
+
dry_run: false,
|
|
766
|
+
json: false,
|
|
767
|
+
};
|
|
768
|
+
|
|
769
|
+
let plan = build_plan(&args).expect("plan should build");
|
|
770
|
+
ensure_target_can_be_created(&plan, args.force).expect("target should be available");
|
|
771
|
+
execute_plan(&plan).expect("template should write");
|
|
772
|
+
write_project_config(&plan).expect("config should write");
|
|
773
|
+
|
|
774
|
+
let target = cwd.join("native app");
|
|
775
|
+
let package_json =
|
|
776
|
+
fs::read_to_string(target.join("package.json")).expect("package.json should exist");
|
|
777
|
+
let config =
|
|
778
|
+
fs::read_to_string(target.join(".electron-cli.json")).expect("config should exist");
|
|
779
|
+
|
|
780
|
+
assert!(target.join("src/main.js").exists());
|
|
781
|
+
assert!(target.join("src/preload.js").exists());
|
|
782
|
+
assert!(target.join("src/index.html").exists());
|
|
783
|
+
assert!(target.join("src/renderer.js").exists());
|
|
784
|
+
assert!(target.join(".github/workflows/ci.yml").exists());
|
|
785
|
+
assert!(package_json.contains("\"name\": \"native-app\""));
|
|
786
|
+
assert!(package_json.contains("\"productName\": \"Native App\""));
|
|
787
|
+
assert!(package_json.contains("\"electron\": \"30.0.0\""));
|
|
788
|
+
assert!(package_json.contains("\"appBundleId\": \"com.electron.native-app\""));
|
|
789
|
+
assert!(config.contains("\"generator\": \"electron-cli\""));
|
|
790
|
+
|
|
791
|
+
let _ = fs::remove_dir_all(cwd);
|
|
792
|
+
}
|
|
793
|
+
|
|
794
|
+
#[test]
|
|
795
|
+
fn sanitizes_package_names() {
|
|
796
|
+
assert_eq!(sanitize_package_name("Native App"), "native-app");
|
|
797
|
+
assert_eq!(sanitize_package_name("..."), "electron-app");
|
|
798
|
+
assert_eq!(sanitize_package_name("@Scope/App"), "scope-app");
|
|
799
|
+
assert_eq!(product_name("native-app"), "Native App");
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
fn unique_temp_dir() -> PathBuf {
|
|
803
|
+
let nanos = std::time::SystemTime::now()
|
|
804
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
805
|
+
.expect("clock should be after epoch")
|
|
806
|
+
.as_nanos();
|
|
807
|
+
let path = std::env::temp_dir().join(format!(
|
|
808
|
+
"electron-cli-init-test-{}-{nanos}",
|
|
809
|
+
std::process::id()
|
|
810
|
+
));
|
|
811
|
+
fs::create_dir_all(&path).expect("temp dir should be created");
|
|
812
|
+
path
|
|
813
|
+
}
|
|
398
814
|
}
|