1use anyhow::Result;
2use clap::Parser;
3use std::path::Path;
4
5mod cli;
6
7#[cfg(test)]
8use fission_command_core::{read_project_config, Target};
9
10use cli::{Cli, Command, ServerCommand, SiteCommand};
11
12pub fn run<I, S>(args: I) -> Result<()>
13where
14 I: IntoIterator<Item = S>,
15 S: Into<std::ffi::OsString> + Clone,
16{
17 let mut argv: Vec<std::ffi::OsString> = args.into_iter().map(Into::into).collect();
18 if let Some(bin) = argv.first() {
19 if let Some(name) = Path::new(bin).file_name().and_then(|value| value.to_str()) {
20 if name == "cargo-fission" {
21 argv[0] = std::ffi::OsString::from("fission");
22 if argv.get(1).and_then(|value| value.to_str()) == Some("fission") {
23 argv.remove(1);
24 }
25 }
26 }
27 }
28 let cli = Cli::parse_from(argv);
29 match cli.command {
30 Command::Init {
31 path,
32 name,
33 app_id,
34 local_path,
35 } => fission_command_core::init_project(&path, name, app_id, local_path),
36 Command::AddTarget {
37 targets,
38 project_dir,
39 } => fission_command_core::add_targets(&project_dir, &targets),
40 Command::AddCapability {
41 capabilities,
42 project_dir,
43 } => fission_command_core::add_capabilities(&project_dir, &capabilities),
44 Command::Doctor {
45 targets,
46 project_dir,
47 strict,
48 } => fission_command_run::doctor::run_doctor(&project_dir, &targets, strict),
49 Command::Devices { project_dir, json } => {
50 fission_command_run::list_devices(&project_dir, json)
51 }
52 Command::Run {
53 target,
54 device,
55 project_dir,
56 detach,
57 release,
58 host,
59 port,
60 no_open,
61 headless,
62 } => fission_command_run::run_app(fission_command_run::RunOptions {
63 project_dir,
64 target,
65 device,
66 detach,
67 release,
68 host,
69 port,
70 no_open,
71 headless,
72 }),
73 Command::Build {
74 target,
75 project_dir,
76 release,
77 } => fission_command_run::build_app(fission_command_run::BuildOptions {
78 project_dir,
79 target,
80 release,
81 }),
82 Command::Test {
83 target,
84 project_dir,
85 headless,
86 } => fission_command_run::test_app(fission_command_run::TestOptions {
87 project_dir,
88 target,
89 headless,
90 }),
91 Command::Site { command } => match command {
92 SiteCommand::Build {
93 project_dir,
94 release,
95 } => fission_command_site::build(&project_dir, release),
96 SiteCommand::Check {
97 project_dir,
98 release,
99 } => fission_command_site::check(&project_dir, release),
100 SiteCommand::Serve {
101 project_dir,
102 host,
103 port,
104 release,
105 no_open,
106 } => fission_command_site::serve(&project_dir, release, host, port, !no_open),
107 SiteCommand::Routes { project_dir } => fission_command_site::routes(&project_dir),
108 },
109 Command::Server { command } => match command {
110 ServerCommand::Build {
111 project_dir,
112 release,
113 } => fission_command_server::build(&project_dir, release),
114 ServerCommand::Check {
115 project_dir,
116 release,
117 } => fission_command_server::check(&project_dir, release),
118 ServerCommand::Serve {
119 project_dir,
120 host,
121 port,
122 release,
123 } => fission_command_server::serve(&project_dir, release, host, port),
124 ServerCommand::Routes { project_dir } => fission_command_server::routes(&project_dir),
125 ServerCommand::Artifacts {
126 project_dir,
127 release,
128 no_compile,
129 } => fission_command_server::artifacts(&project_dir, release, !no_compile),
130 },
131 Command::Package {
132 target,
133 format,
134 project_dir,
135 release,
136 json,
137 } => fission_command_package::package(fission_command_package::PackageOptions {
138 project_dir,
139 target,
140 format,
141 release,
142 json,
143 }),
144 Command::Distribute {
145 action,
146 provider,
147 artifact,
148 site,
149 deploy,
150 track,
151 dry_run,
152 yes,
153 project_dir,
154 json,
155 } => fission_command_package::distribute(fission_command_package::DistributeOptions {
156 project_dir,
157 provider,
158 action: action.unwrap_or(fission_command_package::DistributeAction::Publish),
159 artifact,
160 site,
161 deploy,
162 track,
163 dry_run,
164 yes,
165 json,
166 }),
167 Command::Publish {
168 provider,
169 artifact,
170 site,
171 deploy,
172 track,
173 dry_run,
174 yes,
175 project_dir,
176 json,
177 } => fission_command_package::distribute(fission_command_package::DistributeOptions {
178 project_dir,
179 provider,
180 action: fission_command_package::DistributeAction::Publish,
181 artifact,
182 site,
183 deploy,
184 track,
185 dry_run,
186 yes,
187 json,
188 }),
189 Command::Readiness {
190 kind,
191 target,
192 format,
193 provider,
194 artifact,
195 site,
196 track,
197 project_dir,
198 json,
199 } => fission_command_package::readiness(fission_command_package::ReadinessOptions {
200 project_dir,
201 kind,
202 target,
203 format,
204 provider,
205 artifact,
206 site,
207 track,
208 json,
209 }),
210 Command::ReleaseConfig { command } => fission_command_release::release_config(command),
211 Command::ReleaseContent { command } => fission_command_release::release_content(command),
212 Command::Beta { command } => fission_command_release::beta(command),
213 Command::Signing { command } => fission_command_release::signing(command),
214 Command::Reviews { command } => fission_command_release::reviews(command),
215 Command::ReleaseWorkflow { command } => fission_command_release::release_workflow(command),
216 Command::Auth { command } => fission_command_release::auth(command),
217 Command::Logs {
218 target,
219 device,
220 project_dir,
221 follow,
222 } => fission_command_run::attach_logs(fission_command_run::LogOptions {
223 project_dir,
224 target,
225 device,
226 follow,
227 }),
228 Command::Ui {
229 project_dir,
230 screenshot,
231 exit_after_render,
232 width,
233 height,
234 } => fission_command_ui::run_ui(fission_command_ui::UiOptions {
235 project_dir,
236 screenshot,
237 exit_after_render,
238 width,
239 height,
240 }),
241 Command::ServeWeb {
242 project_dir,
243 host,
244 port,
245 open,
246 } => fission_command_run::serve_web(fission_command_run::ServeWebOptions {
247 project_dir,
248 host,
249 port,
250 open,
251 }),
252 }
253}
254
255#[cfg(test)]
256mod tests {
257 use super::*;
258 use std::{fs, path::PathBuf};
259
260 fn unique_dir(name: &str) -> PathBuf {
261 let dir =
262 std::env::temp_dir().join(format!("cargo-fission-{}-{}", name, std::process::id()));
263 let _ = fs::remove_dir_all(&dir);
264 dir
265 }
266
267 #[test]
268 fn init_creates_project_files() {
269 let dir = unique_dir("init");
270 run([
271 "fission",
272 "init",
273 dir.to_str().unwrap(),
274 "--name",
275 "hello-fission",
276 ])
277 .unwrap();
278
279 assert!(dir.join("Cargo.toml").exists());
280 assert!(dir.join("src/main.rs").exists());
281 assert!(dir.join("src/lib.rs").exists());
282 assert!(dir.join("src/app.rs").exists());
283 assert!(dir.join("assets/app-icon.png").exists());
284 assert!(dir.join("fission.toml").exists());
285 assert!(dir.join("platforms/windows/README.md").exists());
286 assert!(dir.join("platforms/macos/README.md").exists());
287 assert!(dir.join("platforms/linux/README.md").exists());
288 let readme = std::fs::read_to_string(dir.join("README.md")).unwrap();
289 assert!(readme.contains("fission devices --project-dir ."));
290 assert!(readme.contains("fission run --project-dir ."));
291 assert!(readme.contains("fission logs --target <target>"));
292 assert!(readme.contains("fission build --target <target>"));
293 assert!(readme.contains("fission test --target <target>"));
294 let manifest = std::fs::read_to_string(dir.join("Cargo.toml")).unwrap();
295 assert!(manifest.contains("default-features = false"));
296 assert!(manifest.contains("features = [\"desktop\"]"));
297 }
298
299 #[test]
300 fn add_target_updates_manifest_and_scaffold() {
301 let dir = unique_dir("targets");
302 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
303 run([
304 "fission",
305 "add-target",
306 "web",
307 "ios",
308 "android",
309 "--project-dir",
310 dir.to_str().unwrap(),
311 ])
312 .unwrap();
313
314 let project = read_project_config(&dir).unwrap();
315 assert!(project.targets.contains(&Target::Web));
316 assert!(project.targets.contains(&Target::Ios));
317 assert!(project.targets.contains(&Target::Android));
318 let manifest = std::fs::read_to_string(dir.join("Cargo.toml")).unwrap();
319 assert!(manifest.contains("default-features = false"));
320 assert!(manifest.contains("features = [\"desktop\", \"web\", \"android\", \"ios\"]"));
321 assert!(dir.join("platforms/web/README.md").exists());
322 assert!(dir.join("platforms/web/index.html").exists());
323 assert!(dir.join("platforms/web/bootstrap.mjs").exists());
324 assert!(dir.join("platforms/web/build-wasm.sh").exists());
325 assert!(dir.join("platforms/web/run-browser.sh").exists());
326 assert!(dir.join("platforms/web/test-browser.sh").exists());
327 assert!(dir.join("platforms/ios/README.md").exists());
328 assert!(dir.join("platforms/ios/Info.plist").exists());
329 assert!(dir.join("platforms/ios/LaunchScreen.storyboard").exists());
330 assert!(dir.join("platforms/ios/package-sim.sh").exists());
331 assert!(dir.join("platforms/ios/run-sim.sh").exists());
332 assert!(dir.join("platforms/ios/test-sim.sh").exists());
333 assert!(dir.join("platforms/android/README.md").exists());
334 assert!(dir.join("platforms/android/AndroidManifest.xml").exists());
335 assert!(dir.join("platforms/android/res/values/colors.xml").exists());
336 assert!(dir.join("platforms/android/res/values/styles.xml").exists());
337 assert!(dir
338 .join("platforms/android/res/drawable/fission_splash_background.xml")
339 .exists());
340 assert!(dir.join("platforms/android/package-apk.sh").exists());
341 assert!(dir.join("platforms/android/run-emulator.sh").exists());
342 assert!(dir.join("platforms/android/test-emulator.sh").exists());
343 let android_manifest =
344 std::fs::read_to_string(dir.join("platforms/android/AndroidManifest.xml")).unwrap();
345 assert!(android_manifest.contains("android:icon=\"@drawable/app_icon\""));
346 assert!(android_manifest.contains("android:targetSdkVersion=\"35\""));
347 assert!(android_manifest.contains("android:theme=\"@style/FissionLaunchTheme\""));
348 let android_styles =
349 std::fs::read_to_string(dir.join("platforms/android/res/values/styles.xml")).unwrap();
350 assert!(android_styles.contains("android:windowBackground"));
351 assert!(android_styles.contains("android:windowSplashScreenAnimatedIcon"));
352 let android_package_script =
353 std::fs::read_to_string(dir.join("platforms/android/package-apk.sh")).unwrap();
354 assert!(android_package_script.contains("detect_android_toolchain"));
355 assert!(android_package_script
356 .contains("darwin-aarch64 darwin-x86_64 linux-x86_64 windows-x86_64"));
357 assert!(android_package_script.contains(
358 "ANDROID_MIN_API_LEVEL=\"${ANDROID_MIN_API_LEVEL:-${ANDROID_API_LEVEL:-24}}\""
359 ));
360 assert!(android_package_script.contains("ANDROID_TARGET_API_LEVEL="));
361 assert!(
362 android_package_script.contains("aarch64-linux-android${ANDROID_MIN_API_LEVEL}-clang")
363 );
364 assert!(android_package_script.contains("BUILD_MANIFEST"));
365 assert!(android_package_script.contains("android:targetSdkVersion=\"{target_api}\""));
366 assert!(android_package_script.contains("cp -R \"$SCRIPT_DIR/res/.\""));
367 assert!(android_package_script.contains("fission_splash_image.png"));
368 assert!(android_package_script.contains("APP_ICONS"));
369 assert!(android_package_script.contains("res/drawable-nodpi/app_icon.*"));
370 let android_run_script =
371 std::fs::read_to_string(dir.join("platforms/android/run-emulator.sh")).unwrap();
372 assert!(android_run_script.contains("ANDROID_EMULATOR_API_LEVEL"));
373 assert!(android_run_script.contains("fission doctor android"));
374 assert!(android_run_script.contains("wait_for_android_boot()"));
375 assert!(android_run_script.contains("cmd package list packages"));
376 assert!(android_run_script.contains("ADB_INSTALL_FLAGS:---no-streaming -r"));
377 assert!(!android_run_script.contains("wait_for_android_boot() {\n wait_for_android_boot"));
378 assert!(!android_run_script.contains(" wait_for_android_boot\n wait_for_android_boot"));
379 assert!(
380 std::fs::read_to_string(dir.join("platforms/android/README.md"))
381 .unwrap()
382 .contains("fission run --target android")
383 );
384 let android_test_script =
385 std::fs::read_to_string(dir.join("platforms/android/test-emulator.sh")).unwrap();
386 assert!(android_test_script.contains("/health"));
387 let ios_package_script =
388 std::fs::read_to_string(dir.join("platforms/ios/package-sim.sh")).unwrap();
389 assert!(ios_package_script.contains("TARGET=\"${IOS_SIM_TARGET:-aarch64-apple-ios-sim}\""));
390 assert!(ios_package_script.contains("PROFILE=\"${IOS_SIM_PROFILE:-debug}\""));
391 assert!(ios_package_script.contains("BUNDLE_ID=\"${IOS_BUNDLE_ID:-com.example."));
392 assert!(ios_package_script.contains("DISPLAY_NAME=\"${IOS_DISPLAY_NAME:-"));
393 assert!(ios_package_script.contains("EXECUTABLE_NAME=\"${IOS_EXECUTABLE_NAME:-"));
394 assert!(ios_package_script.contains("plistlib.load"));
395 assert!(ios_package_script.contains("PkgInfo"));
396 assert!(ios_package_script.contains("PLATFORM_APP_ICONS"));
397 assert!(ios_package_script.contains("AppIcon.png"));
398 assert!(ios_package_script.contains("LaunchScreen.storyboard"));
399 assert!(ios_package_script.contains("ibtool"));
400 assert!(ios_package_script.contains("LaunchScreen.storyboardc"));
401 assert!(ios_package_script.contains("SplashImage.png"));
402 let ios_plist = std::fs::read_to_string(dir.join("platforms/ios/Info.plist")).unwrap();
403 assert!(ios_plist.contains("UILaunchStoryboardName"));
404 let ios_run_script = std::fs::read_to_string(dir.join("platforms/ios/run-sim.sh")).unwrap();
405 assert!(ios_run_script.contains("BUNDLE_ID=\"${IOS_BUNDLE_ID:-com.example."));
406 assert!(ios_run_script.contains("IOS_SIM_UNINSTALL_BEFORE_INSTALL"));
407 assert!(ios_run_script.contains(
408 "xcrun simctl launch --terminate-running-process \"$DEVICE_ID\" \"$BUNDLE_ID\""
409 ));
410 assert!(std::fs::read_to_string(dir.join("platforms/ios/README.md"))
411 .unwrap()
412 .contains("fission run --target ios"));
413 assert!(
414 std::fs::read_to_string(dir.join("platforms/ios/test-sim.sh"))
415 .unwrap()
416 .contains("/health")
417 );
418 assert!(
419 std::fs::read_to_string(dir.join("platforms/web/index.html"))
420 .unwrap()
421 .contains("../../assets/app-icon.png")
422 );
423 let web_index = std::fs::read_to_string(dir.join("platforms/web/index.html")).unwrap();
424 assert!(web_index.contains("id=\"fission-web-mount\""));
425 assert!(web_index.contains("height: 100vh"));
426 assert!(web_index.contains("outline: none"));
427 assert!(web_index.contains("touch-action: none"));
428 assert!(!web_index.contains("Generated by"));
429 let web_test_script =
430 std::fs::read_to_string(dir.join("platforms/web/test-browser.sh")).unwrap();
431 assert!(web_test_script.contains("--remote-debugging-port=\"$CDP_PORT\""));
432 assert!(web_test_script.contains("/json/list"));
433 assert!(std::fs::read_to_string(dir.join("platforms/web/README.md"))
434 .unwrap()
435 .contains("fission run --target web"));
436 }
437
438 #[test]
439 fn custom_icon_config_is_preserved_and_applied_to_mobile_scaffolds() {
440 let dir = unique_dir("custom-icons");
441 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
442 fs::copy(
443 dir.join("assets/app-icon.png"),
444 dir.join("assets/shared-icon.png"),
445 )
446 .unwrap();
447 fs::copy(
448 dir.join("assets/app-icon.png"),
449 dir.join("assets/android-icon.png"),
450 )
451 .unwrap();
452 fs::copy(
453 dir.join("assets/app-icon.png"),
454 dir.join("assets/ios-icon.png"),
455 )
456 .unwrap();
457 let mut manifest = fs::read_to_string(dir.join("fission.toml")).unwrap();
458 manifest.push_str(
459 r##"
460
461[package.icons]
462mode = "mixed"
463source = "assets/shared-icon.png"
464safe_zone = 0.72
465allow_upscale = false
466
467[package.icons.android]
468source = "assets/android-icon.png"
469
470[package.icons.ios]
471source = "assets/ios-icon.png"
472"##,
473 );
474 fs::write(dir.join("fission.toml"), manifest).unwrap();
475
476 run([
477 "fission",
478 "add-target",
479 "ios",
480 "android",
481 "--project-dir",
482 dir.to_str().unwrap(),
483 ])
484 .unwrap();
485
486 let manifest = fs::read_to_string(dir.join("fission.toml")).unwrap();
487 assert!(manifest.contains("[package.icons]"));
488 assert!(manifest.contains("source = \"assets/shared-icon.png\""));
489 assert!(manifest.contains("[package.icons.android]"));
490 assert!(manifest.contains("[package.icons.ios]"));
491 assert!(dir
492 .join("platforms/android/res/drawable-nodpi/app_icon.png")
493 .exists());
494 assert!(dir.join("platforms/ios/AppIcon.png").exists());
495 let android_manifest =
496 fs::read_to_string(dir.join("platforms/android/AndroidManifest.xml")).unwrap();
497 assert!(android_manifest.contains("android:icon=\"@drawable/app_icon\""));
498 let android_package =
499 fs::read_to_string(dir.join("platforms/android/package-apk.sh")).unwrap();
500 assert!(android_package.contains("APP_ICONS"));
501 let ios_package = fs::read_to_string(dir.join("platforms/ios/package-sim.sh")).unwrap();
502 assert!(ios_package.contains("PLATFORM_APP_ICONS"));
503 }
504
505 #[test]
506 fn custom_splash_config_updates_mobile_platform_files() {
507 let dir = unique_dir("custom-splash");
508 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
509 fs::copy(
510 dir.join("assets/app-icon.png"),
511 dir.join("assets/custom-splash.png"),
512 )
513 .unwrap();
514 let mut manifest = fs::read_to_string(dir.join("fission.toml")).unwrap();
515 manifest.push_str(
516 r##"
517
518[app.splash]
519background_color = "#123456"
520image = "assets/custom-splash.png"
521resize_mode = "cover"
522android_animation_duration_ms = 650
523"##,
524 );
525 fs::write(dir.join("fission.toml"), manifest).unwrap();
526
527 run([
528 "fission",
529 "add-target",
530 "ios",
531 "android",
532 "--project-dir",
533 dir.to_str().unwrap(),
534 ])
535 .unwrap();
536
537 let android_colors =
538 fs::read_to_string(dir.join("platforms/android/res/values/colors.xml")).unwrap();
539 assert!(android_colors.contains("#123456"));
540 let android_styles =
541 fs::read_to_string(dir.join("platforms/android/res/values/styles.xml")).unwrap();
542 assert!(android_styles.contains("650"));
543 assert!(dir
544 .join("platforms/android/res/drawable-nodpi/fission_splash_image.png")
545 .exists());
546 let storyboard =
547 fs::read_to_string(dir.join("platforms/ios/LaunchScreen.storyboard")).unwrap();
548 assert!(storyboard.contains("scaleAspectFill"));
549 assert!(storyboard.contains("red=\"0.070588\""));
550 assert!(dir.join("platforms/ios/SplashImage.png").exists());
551 }
552
553 #[test]
554 fn add_capability_updates_project_and_platform_config() {
555 let dir = unique_dir("capability");
556 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
557 run([
558 "fission",
559 "add-target",
560 "ios",
561 "android",
562 "--project-dir",
563 dir.to_str().unwrap(),
564 ])
565 .unwrap();
566 run([
567 "fission",
568 "add-capability",
569 "nfc",
570 "biometric",
571 "bluetooth",
572 "barcode-scanner",
573 "camera",
574 "geolocation",
575 "haptics",
576 "microphone",
577 "volume-control",
578 "wifi",
579 "--project-dir",
580 dir.to_str().unwrap(),
581 ])
582 .unwrap();
583
584 let project = read_project_config(&dir).unwrap();
585 assert!(project
586 .capabilities
587 .contains(&fission_command_core::PlatformCapability::Nfc));
588 assert!(project
589 .capabilities
590 .contains(&fission_command_core::PlatformCapability::Biometric));
591 assert!(project
592 .capabilities
593 .contains(&fission_command_core::PlatformCapability::Bluetooth));
594 assert!(project
595 .capabilities
596 .contains(&fission_command_core::PlatformCapability::BarcodeScanner));
597 assert!(project
598 .capabilities
599 .contains(&fission_command_core::PlatformCapability::Camera));
600 assert!(project
601 .capabilities
602 .contains(&fission_command_core::PlatformCapability::Geolocation));
603 assert!(project
604 .capabilities
605 .contains(&fission_command_core::PlatformCapability::Haptics));
606 assert!(project
607 .capabilities
608 .contains(&fission_command_core::PlatformCapability::Microphone));
609 assert!(project
610 .capabilities
611 .contains(&fission_command_core::PlatformCapability::VolumeControl));
612 assert!(project
613 .capabilities
614 .contains(&fission_command_core::PlatformCapability::Wifi));
615
616 let android_manifest =
617 std::fs::read_to_string(dir.join("platforms/android/AndroidManifest.xml")).unwrap();
618 assert!(android_manifest.contains("android.permission.NFC"));
619 assert!(android_manifest.contains("android.hardware.nfc"));
620 assert!(android_manifest.contains("android.permission.USE_BIOMETRIC"));
621 assert!(android_manifest.contains("android.permission.BLUETOOTH_SCAN"));
622 assert!(android_manifest.contains("android.permission.BLUETOOTH_CONNECT"));
623 assert!(android_manifest.contains("android.hardware.bluetooth_le"));
624 assert!(android_manifest.contains("android.permission.CAMERA"));
625 assert!(android_manifest.contains("android.hardware.camera.flash"));
626 assert!(android_manifest.contains("android.permission.ACCESS_FINE_LOCATION"));
627 assert!(android_manifest.contains("android.permission.VIBRATE"));
628 assert!(android_manifest.contains("android.permission.RECORD_AUDIO"));
629 assert!(android_manifest.contains("android.permission.MODIFY_AUDIO_SETTINGS"));
630 assert!(android_manifest.contains("android.permission.NEARBY_WIFI_DEVICES"));
631 assert!(android_manifest.contains("android.permission.ACCESS_WIFI_STATE"));
632 assert!(dir
633 .join("platforms/android/java/rs/fission/runtime/FissionAndroidCapabilities.java")
634 .exists());
635
636 let ios_info = std::fs::read_to_string(dir.join("platforms/ios/Info.plist")).unwrap();
637 assert!(ios_info.contains("NFCReaderUsageDescription"));
638 assert!(ios_info.contains("NSFaceIDUsageDescription"));
639 assert!(ios_info.contains("NSBluetoothAlwaysUsageDescription"));
640 assert!(ios_info.contains("NSCameraUsageDescription"));
641 assert!(ios_info.contains("NSLocationWhenInUseUsageDescription"));
642 assert!(ios_info.contains("NSMicrophoneUsageDescription"));
643 let ios_entitlements =
644 std::fs::read_to_string(dir.join("platforms/ios/Entitlements.plist")).unwrap();
645 assert!(ios_entitlements.contains("com.apple.developer.nfc.readersession.formats"));
646 assert!(ios_entitlements.contains("com.apple.developer.networking.wifi-info"));
647 }
648
649 #[test]
650 fn init_existing_project_preserves_user_files_and_detects_targets() {
651 let dir = unique_dir("existing");
652 fs::create_dir_all(dir.join("src")).unwrap();
653 fs::create_dir_all(dir.join("platforms/web")).unwrap();
654 fs::write(
655 dir.join("Cargo.toml"),
656 "[package]\nname = \"existing-web\"\nversion = \"0.1.0\"\nedition = \"2021\"\n",
657 )
658 .unwrap();
659 fs::write(dir.join("src/main.rs"), "fn main() {}\n").unwrap();
660 fs::write(dir.join("src/lib.rs"), "pub fn existing() {}\n").unwrap();
661 fs::write(dir.join("README.md"), "# keep me\n").unwrap();
662 fs::write(
663 dir.join("platforms/web/index.html"),
664 "<!doctype html><title>keep me</title>\n",
665 )
666 .unwrap();
667
668 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
669
670 assert_eq!(
671 fs::read_to_string(dir.join("README.md")).unwrap(),
672 "# keep me\n"
673 );
674 assert_eq!(
675 fs::read_to_string(dir.join("src/main.rs")).unwrap(),
676 "fn main() {}\n"
677 );
678 assert_eq!(
679 fs::read_to_string(dir.join("src/lib.rs")).unwrap(),
680 "pub fn existing() {}\n"
681 );
682 assert_eq!(
683 fs::read_to_string(dir.join("platforms/web/index.html")).unwrap(),
684 "<!doctype html><title>keep me</title>\n"
685 );
686
687 let project = read_project_config(&dir).unwrap();
688 assert_eq!(project.app.name, "existing-web");
689 assert!(project.targets.contains(&Target::Web));
690 assert!(project.targets.contains(&Target::Macos));
691 assert!(project.targets.contains(&Target::Linux));
692 assert!(project.targets.contains(&Target::Windows));
693 assert!(dir.join("platforms/web/README.md").exists());
694 assert!(dir.join("platforms/web/bootstrap.mjs").exists());
695 assert!(dir.join("assets/app-icon.png").exists());
696 }
697
698 #[test]
699 fn init_existing_project_is_idempotent() {
700 let dir = unique_dir("idempotent");
701 run(["fission", "init", dir.to_str().unwrap(), "--name", "idem"]).unwrap();
702 let manifest = fs::read_to_string(dir.join("fission.toml")).unwrap();
703 let main = fs::read_to_string(dir.join("src/main.rs")).unwrap();
704
705 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
706
707 assert_eq!(
708 fs::read_to_string(dir.join("fission.toml")).unwrap(),
709 manifest
710 );
711 assert_eq!(fs::read_to_string(dir.join("src/main.rs")).unwrap(), main);
712 }
713
714 #[test]
715 fn add_target_preserves_existing_target_files() {
716 let dir = unique_dir("preserve-target");
717 run([
718 "fission",
719 "init",
720 dir.to_str().unwrap(),
721 "--name",
722 "preserve-target",
723 ])
724 .unwrap();
725 fs::create_dir_all(dir.join("platforms/web")).unwrap();
726 fs::write(
727 dir.join("platforms/web/index.html"),
728 "<!doctype html><title>custom</title>\n",
729 )
730 .unwrap();
731 fs::write(dir.join("README.md"), "# custom readme\n").unwrap();
732
733 run([
734 "fission",
735 "add-target",
736 "web",
737 "--project-dir",
738 dir.to_str().unwrap(),
739 ])
740 .unwrap();
741
742 assert_eq!(
743 fs::read_to_string(dir.join("platforms/web/index.html")).unwrap(),
744 "<!doctype html><title>custom</title>\n"
745 );
746 assert_eq!(
747 fs::read_to_string(dir.join("README.md")).unwrap(),
748 "# custom readme\n"
749 );
750 assert!(dir.join("platforms/web/README.md").exists());
751 assert!(dir.join("platforms/web/bootstrap.mjs").exists());
752 let project = read_project_config(&dir).unwrap();
753 assert!(project.targets.contains(&Target::Web));
754 }
755
756 #[test]
757 fn cargo_fission_alias_accepts_prefixed_subcommand() {
758 let dir = unique_dir("cargo-fission");
759 run([
760 "cargo-fission",
761 "fission",
762 "init",
763 dir.to_str().unwrap(),
764 "--name",
765 "cargo-fission-demo",
766 ])
767 .unwrap();
768
769 assert!(dir.join("Cargo.toml").exists());
770 assert!(dir.join("fission.toml").exists());
771 }
772
773 #[test]
774 fn doctor_command_runs_in_non_strict_mode() {
775 let dir = unique_dir("doctor");
776 run(["fission", "init", dir.to_str().unwrap()]).unwrap();
777 run([
778 "fission",
779 "doctor",
780 "web",
781 "--project-dir",
782 dir.to_str().unwrap(),
783 ])
784 .unwrap();
785 }
786}