Skip to content

Commit

Permalink
feat(cli): ✨ added eject command
Browse files Browse the repository at this point in the history
Closes #14.
  • Loading branch information
arctic-hen7 committed Sep 19, 2021
1 parent 0dcdd93 commit b747152
Show file tree
Hide file tree
Showing 5 changed files with 97 additions and 8 deletions.
29 changes: 24 additions & 5 deletions packages/perseus-cli/src/bin/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use perseus_cli::errors::*;
use perseus_cli::{
build, check_env, delete_artifacts, delete_bad_dir, help, prepare, serve, PERSEUS_VERSION,
build, check_env, delete_artifacts, delete_bad_dir, eject, has_ejected, help, prepare, serve,
PERSEUS_VERSION,
};
use std::env;
use std::io::Write;
Expand Down Expand Up @@ -79,25 +80,43 @@ fn core(dir: PathBuf) -> Result<i32> {
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
// Delete old build artifacts
delete_artifacts(dir.clone())?;
delete_artifacts(dir.clone(), "static")?;
let exit_code = build(dir, &prog_args)?;
Ok(exit_code)
} else if prog_args[0] == "serve" {
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
// Delete old build artifacts if `--no-build` wasn't specified
if !prog_args.contains(&"--no-build".to_string()) {
delete_artifacts(dir.clone())?;
delete_artifacts(dir.clone(), "static")?;
}
let exit_code = serve(dir, &prog_args)?;
Ok(exit_code)
} else if prog_args[0] == "prep" {
// This command is deliberately undocumented, it's only used for testing
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
Ok(0)
} else if prog_args[0] == "eject" {
// Set up the '.perseus/' directory if needed
prepare(dir.clone())?;
eject(dir)?;
Ok(0)
} else if prog_args[0] == "clean" {
// Just delete the '.perseus/' directory directly, as we'd do in a corruption
delete_bad_dir(dir)?;
if prog_args[1] == "--dist" {
// The user only wants to remove distribution artifacts
// We don't delete `render_conf.json` because it's literally impossible for that to be the source of a problem right now
delete_artifacts(dir.clone(), "static")?;
delete_artifacts(dir.clone(), "pkg")?;
} else {
// This command deletes the `.perseus/` directory completely, which musn't happen if the user has ejected
if has_ejected(dir.clone()) && prog_args[1] != "--force" {
bail!(ErrorKind::CleanAfterEjection)
}
// Just delete the '.perseus/' directory directly, as we'd do in a corruption
delete_bad_dir(dir)?;
}

Ok(0)
} else {
writeln!(
Expand Down
50 changes: 50 additions & 0 deletions packages/perseus-cli/src/eject.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
use crate::errors::*;
use std::fs;
use std::path::PathBuf;

/// Ejects the user from the Perseus CLi harness by exposing the internal subcrates to them. All this does is remove `.perseus/` from
/// the user's `.gitignore` and add a file `.ejected` to `.perseus/`.
pub fn eject(dir: PathBuf) -> Result<()> {
// Create a file declaring ejection so `clean` throws errors (we don't want the user to accidentally delete everything)
let ejected = dir.join(".perseus/.ejected");
fs::write(
&ejected,
"This file signals to Perseus that you've ejected. Do NOT delete it!",
)
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;
// Now remove `.perseus/` from the user's `.gitignore`
let gitignore = dir.join(".gitignore");
if gitignore.exists() {
let content = fs::read_to_string(&gitignore)
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;
let mut new_content_vec = Vec::new();
// Remove the line pertaining to Perseus
// We only target the one that's exactly the same as what's automatically injected, anything else can be done manually
for line in content.lines() {
if line != ".perseus/" {
new_content_vec.push(line);
}
}
let new_content = new_content_vec.join("\n");
// Make sure we've actually changed something
if content == new_content {
bail!(ErrorKind::GitignoreEjectUpdateFailed(
"line `.perseus/` to remove not found".to_string()
))
}
fs::write(&gitignore, new_content)
.map_err(|err| ErrorKind::GitignoreEjectUpdateFailed(err.to_string()))?;

Ok(())
} else {
bail!(ErrorKind::GitignoreEjectUpdateFailed(
"file not found".to_string()
))
}
}

/// Checks if the user has ejected or not. If they have, commands like `clean` should fail unless `--force` is provided.
pub fn has_ejected(dir: PathBuf) -> bool {
let ejected = dir.join(".perseus/.ejected");
ejected.exists()
}
16 changes: 16 additions & 0 deletions packages/perseus-cli/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ error_chain! {
description("error occurred while trying to wait for thread")
display("Waiting on thread failed.")
}
/// For when updating the user's gitignore for ejection fails.
GitignoreEjectUpdateFailed(err: String) {
description("couldn't remove perseus subcrates from gitignore for ejection")
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)
}
/// For when writing the file that signals that we've ejected fails.
EjectionWriteFailed(err: String) {
description("couldn't write ejection declaration file")
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)
}
/// For when the user tries to run `clean` after they've ejected. That command deletes the subcrates, which shouldn't happen
/// after an ejection (they'll likely have customized things).
CleanAfterEjection {
description("can't clean after ejection unless `--force` is provided")
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`.")
}
}
}

Expand Down
2 changes: 2 additions & 0 deletions packages/perseus-cli/src/help.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ This is the CLI for Perseus, a super-fast WebAssembly frontend development frame
build builds your app
serve serves your app (accepts $PORT and $HOST env vars, --no-build to serve pre-built files)
clean removes `.perseus/` entirely (use `--dist` to only remove build artifacts)
eject ejects your app from the CLI harness, see documentation at https://arctic-hen7.github.io/perseus/cli/ejection.html
Please note that watching for file changes is not yet inbuilt, but can be achieved with a tool like 'entr' in the meantime.
Further information can be found at https://arctic-hen7.github.io/perseus.
Expand Down
8 changes: 5 additions & 3 deletions packages/perseus-cli/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

mod build;
mod cmd;
mod eject;
pub mod errors;
mod help;
mod prepare;
Expand All @@ -44,6 +45,7 @@ use std::path::PathBuf;
/// The current version of the CLI, extracted from the crate version.
pub const PERSEUS_VERSION: &str = env!("CARGO_PKG_VERSION");
pub use build::build;
pub use eject::{eject, has_ejected};
pub use help::help;
pub use prepare::{check_env, prepare};
pub use serve::serve;
Expand All @@ -65,10 +67,10 @@ pub fn delete_bad_dir(dir: PathBuf) -> Result<()> {
Ok(())
}

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

0 comments on commit b747152

Please sign in to comment.