Skip to content

Commit b747152

Browse files
committed
feat(cli): ✨ added eject command
Closes #14.
1 parent 0dcdd93 commit b747152

File tree

5 files changed

+97
-8
lines changed

5 files changed

+97
-8
lines changed

packages/perseus-cli/src/bin/main.rs

+24-5
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
use perseus_cli::errors::*;
22
use perseus_cli::{
3-
build, check_env, delete_artifacts, delete_bad_dir, help, prepare, serve, PERSEUS_VERSION,
3+
build, check_env, delete_artifacts, delete_bad_dir, eject, has_ejected, help, prepare, serve,
4+
PERSEUS_VERSION,
45
};
56
use std::env;
67
use std::io::Write;
@@ -79,25 +80,43 @@ fn core(dir: PathBuf) -> Result<i32> {
7980
// Set up the '.perseus/' directory if needed
8081
prepare(dir.clone())?;
8182
// Delete old build artifacts
82-
delete_artifacts(dir.clone())?;
83+
delete_artifacts(dir.clone(), "static")?;
8384
let exit_code = build(dir, &prog_args)?;
8485
Ok(exit_code)
8586
} else if prog_args[0] == "serve" {
8687
// Set up the '.perseus/' directory if needed
8788
prepare(dir.clone())?;
8889
// Delete old build artifacts if `--no-build` wasn't specified
8990
if !prog_args.contains(&"--no-build".to_string()) {
90-
delete_artifacts(dir.clone())?;
91+
delete_artifacts(dir.clone(), "static")?;
9192
}
9293
let exit_code = serve(dir, &prog_args)?;
9394
Ok(exit_code)
9495
} else if prog_args[0] == "prep" {
96+
// This command is deliberately undocumented, it's only used for testing
9597
// Set up the '.perseus/' directory if needed
9698
prepare(dir.clone())?;
9799
Ok(0)
100+
} else if prog_args[0] == "eject" {
101+
// Set up the '.perseus/' directory if needed
102+
prepare(dir.clone())?;
103+
eject(dir)?;
104+
Ok(0)
98105
} else if prog_args[0] == "clean" {
99-
// Just delete the '.perseus/' directory directly, as we'd do in a corruption
100-
delete_bad_dir(dir)?;
106+
if prog_args[1] == "--dist" {
107+
// The user only wants to remove distribution artifacts
108+
// We don't delete `render_conf.json` because it's literally impossible for that to be the source of a problem right now
109+
delete_artifacts(dir.clone(), "static")?;
110+
delete_artifacts(dir.clone(), "pkg")?;
111+
} else {
112+
// This command deletes the `.perseus/` directory completely, which musn't happen if the user has ejected
113+
if has_ejected(dir.clone()) && prog_args[1] != "--force" {
114+
bail!(ErrorKind::CleanAfterEjection)
115+
}
116+
// Just delete the '.perseus/' directory directly, as we'd do in a corruption
117+
delete_bad_dir(dir)?;
118+
}
119+
101120
Ok(0)
102121
} else {
103122
writeln!(

packages/perseus-cli/src/eject.rs

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
use crate::errors::*;
2+
use std::fs;
3+
use std::path::PathBuf;
4+
5+
/// Ejects the user from the Perseus CLi harness by exposing the internal subcrates to them. All this does is remove `.perseus/` from
6+
/// the user's `.gitignore` and add a file `.ejected` to `.perseus/`.
7+
pub fn eject(dir: PathBuf) -> Result<()> {
8+
// Create a file declaring ejection so `clean` throws errors (we don't want the user to accidentally delete everything)
9+
let ejected = dir.join(".perseus/.ejected");
10+
fs::write(
11+
&ejected,
12+
"This file signals to Perseus that you've ejected. Do NOT delete it!",
13+
)
14+
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;
15+
// Now remove `.perseus/` from the user's `.gitignore`
16+
let gitignore = dir.join(".gitignore");
17+
if gitignore.exists() {
18+
let content = fs::read_to_string(&gitignore)
19+
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;
20+
let mut new_content_vec = Vec::new();
21+
// Remove the line pertaining to Perseus
22+
// We only target the one that's exactly the same as what's automatically injected, anything else can be done manually
23+
for line in content.lines() {
24+
if line != ".perseus/" {
25+
new_content_vec.push(line);
26+
}
27+
}
28+
let new_content = new_content_vec.join("\n");
29+
// Make sure we've actually changed something
30+
if content == new_content {
31+
bail!(ErrorKind::GitignoreEjectUpdateFailed(
32+
"line `.perseus/` to remove not found".to_string()
33+
))
34+
}
35+
fs::write(&gitignore, new_content)
36+
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;
37+
38+
Ok(())
39+
} else {
40+
bail!(ErrorKind::GitignoreEjectUpdateFailed(
41+
"file not found".to_string()
42+
))
43+
}
44+
}
45+
46+
/// Checks if the user has ejected or not. If they have, commands like `clean` should fail unless `--force` is provided.
47+
pub fn has_ejected(dir: PathBuf) -> bool {
48+
let ejected = dir.join(".perseus/.ejected");
49+
ejected.exists()
50+
}

packages/perseus-cli/src/errors.rs

+16
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,22 @@ error_chain! {
8383
description("error occurred while trying to wait for thread")
8484
display("Waiting on thread failed.")
8585
}
86+
/// For when updating the user's gitignore for ejection fails.
87+
GitignoreEjectUpdateFailed(err: String) {
88+
description("couldn't remove perseus subcrates from gitignore for ejection")
89+
display("Couldn't remove `.perseus/` (Perseus subcrates) from your `.gitignore`. Please remove them manually, then ejection is complete (that's all this command does). Error was: '{}'.", err)
90+
}
91+
/// For when writing the file that signals that we've ejected fails.
92+
EjectionWriteFailed(err: String) {
93+
description("couldn't write ejection declaration file")
94+
display("Couldn't create `.perseus/.ejected` file to signal that you've ejected. Please make sure you have permission to write to the `.perseus/` directory, and then try again. Error was: '{}'.", err)
95+
}
96+
/// For when the user tries to run `clean` after they've ejected. That command deletes the subcrates, which shouldn't happen
97+
/// after an ejection (they'll likely have customized things).
98+
CleanAfterEjection {
99+
description("can't clean after ejection unless `--force` is provided")
100+
display("The `clean` command removes the entire `.perseus/` directory, and you've already ejected, meaning that you can make modifications to that directory. If you proceed with this command, any modifications you've made to `.perseus/` will be PERMANENTLY lost! If you're sure you want to proceed, run `perseus clean --force`.")
101+
}
86102
}
87103
}
88104

packages/perseus-cli/src/help.rs

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@ This is the CLI for Perseus, a super-fast WebAssembly frontend development frame
1414
1515
build builds your app
1616
serve serves your app (accepts $PORT and $HOST env vars, --no-build to serve pre-built files)
17+
clean removes `.perseus/` entirely (use `--dist` to only remove build artifacts)
18+
eject ejects your app from the CLI harness, see documentation at https://arctic-hen7.github.io/perseus/cli/ejection.html
1719
1820
Please note that watching for file changes is not yet inbuilt, but can be achieved with a tool like 'entr' in the meantime.
1921
Further information can be found at https://arctic-hen7.github.io/perseus.

packages/perseus-cli/src/lib.rs

+5-3
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030
mod build;
3131
mod cmd;
32+
mod eject;
3233
pub mod errors;
3334
mod help;
3435
mod prepare;
@@ -44,6 +45,7 @@ use std::path::PathBuf;
4445
/// The current version of the CLI, extracted from the crate version.
4546
pub const PERSEUS_VERSION: &str = env!("CARGO_PKG_VERSION");
4647
pub use build::build;
48+
pub use eject::{eject, has_ejected};
4749
pub use help::help;
4850
pub use prepare::{check_env, prepare};
4951
pub use serve::serve;
@@ -65,10 +67,10 @@ pub fn delete_bad_dir(dir: PathBuf) -> Result<()> {
6567
Ok(())
6668
}
6769

68-
/// Deletes build artifacts in `.perseus/dist/static` and replaces the directory.
69-
pub fn delete_artifacts(dir: PathBuf) -> Result<()> {
70+
/// Deletes build artifacts in `.perseus/dist/static` or `.perseus/dist/pkg` and replaces the directory.
71+
pub fn delete_artifacts(dir: PathBuf, dir_to_remove: &str) -> Result<()> {
7072
let mut target = dir;
71-
target.extend([".perseus", "dist", "static"]);
73+
target.extend([".perseus", "dist", dir_to_remove]);
7274
// We'll only delete the directory if it exists, otherwise we're fine
7375
if target.exists() {
7476
if let Err(err) = fs::remove_dir_all(&target) {

0 commit comments

Comments
 (0)