electron-cli 0.3.0-alpha.2 → 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 +28 -9
- package/package.json +2 -1
- package/src/cli.rs +120 -3
- package/src/commands/init.rs +413 -27
- package/src/commands/make.rs +426 -0
- package/src/commands/mod.rs +3 -0
- package/src/commands/package.rs +703 -0
- package/src/commands/plan.rs +50 -5
- package/src/commands/start.rs +287 -0
- package/src/main.rs +3 -0
- 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,195 @@ 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 package = serde_json::json!({
|
|
451
|
+
"name": package_name,
|
|
452
|
+
"version": "0.1.0",
|
|
453
|
+
"private": true,
|
|
454
|
+
"description": "Minimal Electron app generated by electron-cli",
|
|
455
|
+
"main": "src/main.js",
|
|
456
|
+
"scripts": {
|
|
457
|
+
"start": "electron .",
|
|
458
|
+
"smoke": "electron --version"
|
|
459
|
+
},
|
|
460
|
+
"devDependencies": {
|
|
461
|
+
"electron": electron_version
|
|
462
|
+
}
|
|
463
|
+
});
|
|
464
|
+
|
|
465
|
+
Ok(serde_json::to_string_pretty(&package)?)
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
fn readme(package_name: &str, package_manager: PackageManager) -> String {
|
|
469
|
+
format!(
|
|
470
|
+
r#"# {package_name}
|
|
471
|
+
|
|
472
|
+
Minimal Electron app generated by electron-cli.
|
|
473
|
+
|
|
474
|
+
## Scripts
|
|
475
|
+
|
|
476
|
+
```sh
|
|
477
|
+
{install}
|
|
478
|
+
{start}
|
|
479
|
+
{smoke}
|
|
480
|
+
```
|
|
481
|
+
"#,
|
|
482
|
+
install = install_command(package_manager),
|
|
483
|
+
start = start_command(package_manager),
|
|
484
|
+
smoke = run_script_command(package_manager, "smoke"),
|
|
485
|
+
)
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
fn ci_workflow(package_manager: PackageManager) -> String {
|
|
489
|
+
let setup = match package_manager {
|
|
490
|
+
PackageManager::Npm => {
|
|
491
|
+
" - uses: actions/setup-node@v6\n with:\n node-version: 22\n"
|
|
492
|
+
.to_string()
|
|
493
|
+
}
|
|
494
|
+
PackageManager::Pnpm | PackageManager::Yarn => {
|
|
495
|
+
" - uses: actions/setup-node@v6\n with:\n node-version: 22\n - run: corepack enable\n"
|
|
496
|
+
.to_string()
|
|
497
|
+
}
|
|
498
|
+
PackageManager::Bun => " - uses: oven-sh/setup-bun@v2\n".to_string(),
|
|
499
|
+
};
|
|
500
|
+
|
|
501
|
+
format!(
|
|
502
|
+
r#"name: CI
|
|
503
|
+
|
|
504
|
+
on:
|
|
505
|
+
pull_request:
|
|
506
|
+
push:
|
|
507
|
+
branches: [main]
|
|
508
|
+
|
|
509
|
+
jobs:
|
|
510
|
+
smoke:
|
|
511
|
+
runs-on: ubuntu-latest
|
|
512
|
+
steps:
|
|
513
|
+
- uses: actions/checkout@v6
|
|
514
|
+
{setup} - run: {install}
|
|
515
|
+
- run: {smoke}
|
|
516
|
+
"#,
|
|
517
|
+
setup = setup,
|
|
518
|
+
install = install_command(package_manager),
|
|
519
|
+
smoke = run_script_command(package_manager, "smoke"),
|
|
520
|
+
)
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
fn ensure_trailing_newline(contents: &str) -> String {
|
|
524
|
+
if contents.ends_with('\n') {
|
|
525
|
+
contents.to_string()
|
|
526
|
+
} else {
|
|
527
|
+
format!("{contents}\n")
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
|
|
531
|
+
fn template_kind(template: &str) -> InitTemplateKind {
|
|
532
|
+
if template == NATIVE_TEMPLATE_NAME {
|
|
533
|
+
InitTemplateKind::Native
|
|
534
|
+
} else {
|
|
535
|
+
InitTemplateKind::Forge
|
|
536
|
+
}
|
|
537
|
+
}
|
|
538
|
+
|
|
269
539
|
fn detect_package_manager(cwd: &Path) -> PackageManager {
|
|
270
540
|
if cwd.join("pnpm-lock.yaml").exists() {
|
|
271
541
|
PackageManager::Pnpm
|
|
@@ -278,6 +548,15 @@ fn detect_package_manager(cwd: &Path) -> PackageManager {
|
|
|
278
548
|
}
|
|
279
549
|
}
|
|
280
550
|
|
|
551
|
+
fn install_command(package_manager: PackageManager) -> String {
|
|
552
|
+
match package_manager {
|
|
553
|
+
PackageManager::Npm => "npm install".to_string(),
|
|
554
|
+
PackageManager::Pnpm => "pnpm install".to_string(),
|
|
555
|
+
PackageManager::Yarn => "yarn install".to_string(),
|
|
556
|
+
PackageManager::Bun => "bun install".to_string(),
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
|
|
281
560
|
fn start_command(package_manager: PackageManager) -> String {
|
|
282
561
|
match package_manager {
|
|
283
562
|
PackageManager::Npm => "npm start".to_string(),
|
|
@@ -287,6 +566,25 @@ fn start_command(package_manager: PackageManager) -> String {
|
|
|
287
566
|
}
|
|
288
567
|
}
|
|
289
568
|
|
|
569
|
+
fn run_script_command(package_manager: PackageManager, script: &str) -> String {
|
|
570
|
+
match package_manager {
|
|
571
|
+
PackageManager::Npm => format!("npm run {script}"),
|
|
572
|
+
PackageManager::Pnpm => format!("pnpm run {script}"),
|
|
573
|
+
PackageManager::Yarn => format!("yarn {script}"),
|
|
574
|
+
PackageManager::Bun => format!("bun run {script}"),
|
|
575
|
+
}
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
fn package_manager_from_str(package_manager: &str) -> Result<PackageManager> {
|
|
579
|
+
match package_manager {
|
|
580
|
+
"npm" => Ok(PackageManager::Npm),
|
|
581
|
+
"pnpm" => Ok(PackageManager::Pnpm),
|
|
582
|
+
"yarn" => Ok(PackageManager::Yarn),
|
|
583
|
+
"bun" => Ok(PackageManager::Bun),
|
|
584
|
+
_ => bail!("Unknown package manager in init plan: {package_manager}"),
|
|
585
|
+
}
|
|
586
|
+
}
|
|
587
|
+
|
|
290
588
|
fn display_command(command: &[String]) -> String {
|
|
291
589
|
command
|
|
292
590
|
.iter()
|
|
@@ -310,6 +608,37 @@ fn path_arg(path: &Path) -> String {
|
|
|
310
608
|
path.to_string_lossy().to_string()
|
|
311
609
|
}
|
|
312
610
|
|
|
611
|
+
fn package_name(plan: &InitReport) -> String {
|
|
612
|
+
let raw_name = Path::new(plan.target_dir.as_str())
|
|
613
|
+
.file_name()
|
|
614
|
+
.and_then(|name| name.to_str())
|
|
615
|
+
.unwrap_or("electron-app");
|
|
616
|
+
|
|
617
|
+
sanitize_package_name(raw_name)
|
|
618
|
+
}
|
|
619
|
+
|
|
620
|
+
fn sanitize_package_name(raw_name: &str) -> String {
|
|
621
|
+
let name = raw_name
|
|
622
|
+
.to_ascii_lowercase()
|
|
623
|
+
.chars()
|
|
624
|
+
.map(|char| {
|
|
625
|
+
if char.is_ascii_alphanumeric() || matches!(char, '-' | '_' | '.') {
|
|
626
|
+
char
|
|
627
|
+
} else {
|
|
628
|
+
'-'
|
|
629
|
+
}
|
|
630
|
+
})
|
|
631
|
+
.collect::<String>()
|
|
632
|
+
.trim_matches(['-', '.', '_'])
|
|
633
|
+
.to_string();
|
|
634
|
+
|
|
635
|
+
if name.is_empty() {
|
|
636
|
+
"electron-app".to_string()
|
|
637
|
+
} else {
|
|
638
|
+
name
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
|
|
313
642
|
fn utf8_path(path: PathBuf) -> Result<Utf8PathBuf> {
|
|
314
643
|
Utf8PathBuf::from_path_buf(path).map_err(|path| {
|
|
315
644
|
anyhow::anyhow!(
|
|
@@ -337,7 +666,7 @@ mod tests {
|
|
|
337
666
|
let args = InitArgs {
|
|
338
667
|
dir: PathBuf::from("my-app"),
|
|
339
668
|
cwd: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
|
|
340
|
-
template: "
|
|
669
|
+
template: "minimal".to_string(),
|
|
341
670
|
package_manager: Some(PackageManager::Npm),
|
|
342
671
|
electron_version: None,
|
|
343
672
|
copy_ci_files: false,
|
|
@@ -349,27 +678,23 @@ mod tests {
|
|
|
349
678
|
|
|
350
679
|
let plan = build_plan(&args).expect("plan should build");
|
|
351
680
|
|
|
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
|
-
);
|
|
681
|
+
assert_eq!(plan.template_kind, InitTemplateKind::Native);
|
|
682
|
+
assert_eq!(plan.generator, "electron-cli");
|
|
683
|
+
assert!(plan.command.is_empty());
|
|
684
|
+
assert!(plan.create_files.contains(&"package.json".to_string()));
|
|
364
685
|
assert_eq!(plan.package_manager, "npm");
|
|
365
686
|
assert_eq!(
|
|
366
687
|
plan.next_steps.first().map(String::as_str),
|
|
367
688
|
Some("cd my-app")
|
|
368
689
|
);
|
|
690
|
+
assert_eq!(
|
|
691
|
+
plan.next_steps.get(1).map(String::as_str),
|
|
692
|
+
Some("npm install")
|
|
693
|
+
);
|
|
369
694
|
}
|
|
370
695
|
|
|
371
696
|
#[test]
|
|
372
|
-
fn
|
|
697
|
+
fn includes_optional_forge_create_flags() {
|
|
373
698
|
let args = InitArgs {
|
|
374
699
|
dir: PathBuf::from("desktop app"),
|
|
375
700
|
cwd: PathBuf::from(env!("CARGO_MANIFEST_DIR")),
|
|
@@ -385,6 +710,8 @@ mod tests {
|
|
|
385
710
|
|
|
386
711
|
let plan = build_plan(&args).expect("plan should build");
|
|
387
712
|
|
|
713
|
+
assert_eq!(plan.template_kind, InitTemplateKind::Forge);
|
|
714
|
+
assert_eq!(plan.generator, "create-electron-app@latest");
|
|
388
715
|
assert_eq!(plan.command[0], "pnpm");
|
|
389
716
|
assert!(plan.command.contains(&"--copy-ci-files".to_string()));
|
|
390
717
|
assert!(plan.command.contains(&"--force".to_string()));
|
|
@@ -395,4 +722,63 @@ mod tests {
|
|
|
395
722
|
Some("pnpm start")
|
|
396
723
|
);
|
|
397
724
|
}
|
|
725
|
+
|
|
726
|
+
#[test]
|
|
727
|
+
fn writes_native_template_files() {
|
|
728
|
+
let cwd = unique_temp_dir();
|
|
729
|
+
let args = InitArgs {
|
|
730
|
+
dir: PathBuf::from("native app"),
|
|
731
|
+
cwd: cwd.clone(),
|
|
732
|
+
template: "minimal".to_string(),
|
|
733
|
+
package_manager: Some(PackageManager::Npm),
|
|
734
|
+
electron_version: Some("30.0.0".to_string()),
|
|
735
|
+
copy_ci_files: true,
|
|
736
|
+
force: false,
|
|
737
|
+
skip_git: true,
|
|
738
|
+
dry_run: false,
|
|
739
|
+
json: false,
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
let plan = build_plan(&args).expect("plan should build");
|
|
743
|
+
ensure_target_can_be_created(&plan, args.force).expect("target should be available");
|
|
744
|
+
execute_plan(&plan).expect("template should write");
|
|
745
|
+
write_project_config(&plan).expect("config should write");
|
|
746
|
+
|
|
747
|
+
let target = cwd.join("native app");
|
|
748
|
+
let package_json =
|
|
749
|
+
fs::read_to_string(target.join("package.json")).expect("package.json should exist");
|
|
750
|
+
let config =
|
|
751
|
+
fs::read_to_string(target.join(".electron-cli.json")).expect("config should exist");
|
|
752
|
+
|
|
753
|
+
assert!(target.join("src/main.js").exists());
|
|
754
|
+
assert!(target.join("src/preload.js").exists());
|
|
755
|
+
assert!(target.join("src/index.html").exists());
|
|
756
|
+
assert!(target.join("src/renderer.js").exists());
|
|
757
|
+
assert!(target.join(".github/workflows/ci.yml").exists());
|
|
758
|
+
assert!(package_json.contains("\"name\": \"native-app\""));
|
|
759
|
+
assert!(package_json.contains("\"electron\": \"30.0.0\""));
|
|
760
|
+
assert!(config.contains("\"generator\": \"electron-cli\""));
|
|
761
|
+
|
|
762
|
+
let _ = fs::remove_dir_all(cwd);
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
#[test]
|
|
766
|
+
fn sanitizes_package_names() {
|
|
767
|
+
assert_eq!(sanitize_package_name("Native App"), "native-app");
|
|
768
|
+
assert_eq!(sanitize_package_name("..."), "electron-app");
|
|
769
|
+
assert_eq!(sanitize_package_name("@Scope/App"), "scope-app");
|
|
770
|
+
}
|
|
771
|
+
|
|
772
|
+
fn unique_temp_dir() -> PathBuf {
|
|
773
|
+
let nanos = std::time::SystemTime::now()
|
|
774
|
+
.duration_since(std::time::UNIX_EPOCH)
|
|
775
|
+
.expect("clock should be after epoch")
|
|
776
|
+
.as_nanos();
|
|
777
|
+
let path = std::env::temp_dir().join(format!(
|
|
778
|
+
"electron-cli-init-test-{}-{nanos}",
|
|
779
|
+
std::process::id()
|
|
780
|
+
));
|
|
781
|
+
fs::create_dir_all(&path).expect("temp dir should be created");
|
|
782
|
+
path
|
|
783
|
+
}
|
|
398
784
|
}
|