From 1017e20bf4ed732b89d7631fdc56e99ce01de38b Mon Sep 17 00:00:00 2001 From: Mihovil Ilakovac Date: Wed, 1 Feb 2023 11:18:24 +0100 Subject: [PATCH] Adding uninstall command (#953) * Adding uninstall command * Updates docs and changelog related to the uninstall command --- waspc/ChangeLog.md | 7 +++ waspc/cli/exe/Main.hs | 4 ++ .../src/Wasp/Cli/Command/BashCompletion.hs | 2 +- waspc/cli/src/Wasp/Cli/Command/Call.hs | 1 + .../src/Wasp/Cli/Command/Telemetry/Common.hs | 27 +++++----- waspc/cli/src/Wasp/Cli/Command/Uninstall.hs | 54 +++++++++++++++++++ waspc/cli/src/Wasp/Cli/FileSystem.hs | 40 ++++++++++++++ waspc/src/Wasp/Util/IO.hs | 25 +++++++-- waspc/waspc.cabal | 2 + web/docs/cli.md | 52 ++++++++++++------ 10 files changed, 178 insertions(+), 36 deletions(-) create mode 100644 waspc/cli/src/Wasp/Cli/Command/Uninstall.hs create mode 100644 waspc/cli/src/Wasp/Cli/FileSystem.hs diff --git a/waspc/ChangeLog.md b/waspc/ChangeLog.md index 51eb96cb6..15d5c6b53 100644 --- a/waspc/ChangeLog.md +++ b/waspc/ChangeLog.md @@ -50,6 +50,13 @@ const getTasks: GetTasks = (args, context) => { } ``` +### Uninstall command +If you want to uninstall Wasp from your system, you can now do so with: +```bash +wasp uninstall +``` +It will remove all of the Wasp binaries and data from your system. + ## v0.8.0 ### BREAKING CHANGES diff --git a/waspc/cli/exe/Main.hs b/waspc/cli/exe/Main.hs index 5b896a00e..3514a49ed 100644 --- a/waspc/cli/exe/Main.hs +++ b/waspc/cli/exe/Main.hs @@ -21,6 +21,7 @@ import Wasp.Cli.Command.Dockerfile (printDockerfile) import Wasp.Cli.Command.Info (info) import Wasp.Cli.Command.Start (start) import qualified Wasp.Cli.Command.Telemetry as Telemetry +import Wasp.Cli.Command.Uninstall (uninstall) import Wasp.Cli.Command.WaspLS (runWaspLS) import Wasp.Cli.Terminal (title) import Wasp.Util (indent) @@ -36,6 +37,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do ["clean"] -> Command.Call.Clean ["compile"] -> Command.Call.Compile ("db" : dbArgs) -> Command.Call.Db dbArgs + ["uninstall"] -> Command.Call.Uninstall ["version"] -> Command.Call.Version ["build"] -> Command.Call.Build ["telemetry"] -> Command.Call.Telemetry @@ -57,6 +59,7 @@ main = withUtf8 . (`E.catch` handleInternalErrors) $ do Command.Call.Compile -> runCommand compile Command.Call.Db dbArgs -> dbCli dbArgs Command.Call.Version -> printVersion + Command.Call.Uninstall -> runCommand uninstall Command.Call.Build -> runCommand build Command.Call.Telemetry -> runCommand Telemetry.telemetry Command.Call.Deps -> runCommand deps @@ -92,6 +95,7 @@ printUsage = cmd " version Prints current version of CLI.", cmd " waspls Run Wasp Language Server. Add --help to get more info.", cmd " completion Prints help on bash completion.", + cmd " uninstall Removes Wasp from your system.", title " IN PROJECT", cmd " start Runs Wasp app in development mode, watching for file changes.", cmd " db [args] Executes a database command. Run 'wasp db' for more info.", diff --git a/waspc/cli/src/Wasp/Cli/Command/BashCompletion.hs b/waspc/cli/src/Wasp/Cli/Command/BashCompletion.hs index e36cddaeb..1e90148e5 100644 --- a/waspc/cli/src/Wasp/Cli/Command/BashCompletion.hs +++ b/waspc/cli/src/Wasp/Cli/Command/BashCompletion.hs @@ -26,7 +26,7 @@ bashCompletion = do ["db", cmdPrefix] -> listMatchingCommands cmdPrefix dbSubCommands _ -> liftIO . putStrLn $ "" where - commands = ["new", "version", "waspls", "start", "db", "clean", "build", "telemetry", "deps", "info", "completion", "completion:generate"] + commands = ["new", "version", "waspls", "start", "db", "clean", "uninstall", "build", "telemetry", "deps", "info", "completion", "completion:generate"] dbSubCommands = ["migrate-dev", "studio"] listMatchingCommands :: String -> [String] -> Command () listMatchingCommands cmdPrefix cmdList = listCommands $ filter (cmdPrefix `isPrefixOf`) cmdList diff --git a/waspc/cli/src/Wasp/Cli/Command/Call.hs b/waspc/cli/src/Wasp/Cli/Command/Call.hs index 08a3d3838..6f7ae7320 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Call.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Call.hs @@ -4,6 +4,7 @@ data Call = New String -- project name | Start | Clean + | Uninstall | Compile | Db [String] -- db args | Build diff --git a/waspc/cli/src/Wasp/Cli/Command/Telemetry/Common.hs b/waspc/cli/src/Wasp/Cli/Command/Telemetry/Common.hs index 58654979f..1804046ba 100644 --- a/waspc/cli/src/Wasp/Cli/Command/Telemetry/Common.hs +++ b/waspc/cli/src/Wasp/Cli/Command/Telemetry/Common.hs @@ -1,28 +1,27 @@ module Wasp.Cli.Command.Telemetry.Common ( TelemetryCacheDir, ensureTelemetryCacheDirExists, - getTelemetryCacheDirPath, ) where -import StrongPath (Abs, Dir, Path', reldir) +import StrongPath (Abs, Dir, Path', reldir, ()) import qualified StrongPath as SP import qualified System.Directory as SD - -data UserCacheDir - -getUserCacheDirPath :: IO (Path' Abs (Dir UserCacheDir)) -getUserCacheDirPath = SD.getXdgDirectory SD.XdgCache "" >>= SP.parseAbsDir +import Wasp.Cli.FileSystem + ( UserCacheDir, + getUserCacheDir, + getWaspCacheDir, + ) data TelemetryCacheDir +getTelemetryCacheDir :: Path' Abs (Dir UserCacheDir) -> Path' Abs (Dir TelemetryCacheDir) +getTelemetryCacheDir userCacheDirPath = getWaspCacheDir userCacheDirPath [reldir|telemetry|] + ensureTelemetryCacheDirExists :: IO (Path' Abs (Dir TelemetryCacheDir)) ensureTelemetryCacheDirExists = do - userCacheDirPath <- getUserCacheDirPath + userCacheDirPath <- getUserCacheDir SD.createDirectoryIfMissing False $ SP.fromAbsDir userCacheDirPath - let telemetryCacheDirPath = getTelemetryCacheDirPath userCacheDirPath - SD.createDirectoryIfMissing True $ SP.fromAbsDir telemetryCacheDirPath - return telemetryCacheDirPath - -getTelemetryCacheDirPath :: Path' Abs (Dir UserCacheDir) -> Path' Abs (Dir TelemetryCacheDir) -getTelemetryCacheDirPath userCacheDirPath = userCacheDirPath SP. [reldir|wasp/telemetry|] + let telemetryCacheDir = getTelemetryCacheDir userCacheDirPath + SD.createDirectoryIfMissing True $ SP.fromAbsDir telemetryCacheDir + return telemetryCacheDir diff --git a/waspc/cli/src/Wasp/Cli/Command/Uninstall.hs b/waspc/cli/src/Wasp/Cli/Command/Uninstall.hs new file mode 100644 index 000000000..0ee7f7395 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/Command/Uninstall.hs @@ -0,0 +1,54 @@ +module Wasp.Cli.Command.Uninstall + ( uninstall, + ) +where + +import Control.Monad (when) +import Control.Monad.IO.Class (liftIO) +import StrongPath (fromAbsDir, fromAbsFile, ()) +import System.Exit (die) +import Wasp.Cli.Command (Command) +import Wasp.Cli.Command.Message (cliSendMessageC) +import Wasp.Cli.FileSystem + ( getHomeDir, + getUserCacheDir, + getWaspCacheDir, + waspExecutableInHomeDir, + waspInstallationDirInHomeDir, + ) +import qualified Wasp.Message as Msg +import Wasp.Util.IO (deleteDirectoryIfExists, deleteFileIfExists) + +-- | Removes Wasp from the system. +uninstall :: Command () +uninstall = do + cliSendMessageC $ Msg.Start "Uninstalling Wasp ..." + liftIO removeWaspFiles + cliSendMessageC $ Msg.Success "Uninstalled Wasp" + +removeWaspFiles :: IO () +removeWaspFiles = do + homeDir <- getHomeDir + userCacheDir <- getUserCacheDir + + let waspInstallationDir = homeDir waspInstallationDirInHomeDir + waspExecutableFile = homeDir waspExecutableInHomeDir + waspCacheDir = getWaspCacheDir userCacheDir + + putStr $ + unlines + [ "We will remove the following directories:", + " " ++ fromAbsDir waspInstallationDir, + " " ++ fromAbsDir waspCacheDir, + "", + "We will also remove the following files:", + " " ++ fromAbsFile waspExecutableFile, + "", + "Are you sure you want to continue? [y/N]" + ] + + answer <- getLine + when (answer /= "y") $ die "Aborted." + deleteDirectoryIfExists waspInstallationDir + deleteFileIfExists waspExecutableFile + deleteDirectoryIfExists waspCacheDir diff --git a/waspc/cli/src/Wasp/Cli/FileSystem.hs b/waspc/cli/src/Wasp/Cli/FileSystem.hs new file mode 100644 index 000000000..2b5992962 --- /dev/null +++ b/waspc/cli/src/Wasp/Cli/FileSystem.hs @@ -0,0 +1,40 @@ +module Wasp.Cli.FileSystem + ( getUserCacheDir, + getWaspCacheDir, + getHomeDir, + waspInstallationDirInHomeDir, + waspExecutableInHomeDir, + UserCacheDir, + WaspCacheDir, + ) +where + +import Data.Maybe (fromJust) +import StrongPath (Abs, Dir, Dir', File', Path', Rel, reldir, relfile, ()) +import qualified StrongPath as SP +import System.Directory +import qualified System.Directory as SD + +data UserHomeDir + +data UserCacheDir + +data WaspCacheDir + +getHomeDir :: IO (Path' Abs (Dir UserHomeDir)) +getHomeDir = fromJust . SP.parseAbsDir <$> getHomeDirectory + +getWaspCacheDir :: Path' Abs (Dir UserCacheDir) -> Path' Abs (Dir WaspCacheDir) +getWaspCacheDir userCacheDirPath = userCacheDirPath [reldir|wasp|] + +getUserCacheDir :: IO (Path' Abs (Dir UserCacheDir)) +getUserCacheDir = SD.getXdgDirectory SD.XdgCache "" >>= SP.parseAbsDir + +-- NOTE: these paths are based on the installer script and if you change them there +-- you need to change them here as well (and vice versa). +-- Task to improve this: https://github.com/wasp-lang/wasp/issues/980 +waspInstallationDirInHomeDir :: Path' (Rel UserHomeDir) Dir' +waspInstallationDirInHomeDir = [reldir|.local/share/wasp-lang|] + +waspExecutableInHomeDir :: Path' (Rel UserHomeDir) File' +waspExecutableInHomeDir = [relfile|.local/bin/wasp|] diff --git a/waspc/src/Wasp/Util/IO.hs b/waspc/src/Wasp/Util/IO.hs index 71836c4aa..f36bcd359 100644 --- a/waspc/src/Wasp/Util/IO.hs +++ b/waspc/src/Wasp/Util/IO.hs @@ -3,12 +3,15 @@ module Wasp.Util.IO ( listDirectoryDeep, listDirectory, + deleteDirectoryIfExists, + deleteFileIfExists, ) where -import Control.Monad (filterM) +import Control.Monad (filterM, when) import StrongPath (Abs, Dir, Dir', File, Path', Rel, basename, parseRelDir, parseRelFile, toFilePath, ()) -import qualified System.Directory +import qualified StrongPath as SP +import qualified System.Directory as SD import qualified System.FilePath as FilePath import System.IO.Error (isDoesNotExistError) import UnliftIO.Exception (catch, throwIO) @@ -42,7 +45,7 @@ listDirectoryDeep absDirPath = do -- | Lists files and directories at top lvl of the directory. listDirectory :: forall d f. Path' Abs (Dir d) -> IO ([Path' (Rel d) (File f)], [Path' (Rel d) Dir']) listDirectory absDirPath = do - fpRelItemPaths <- System.Directory.listDirectory fpAbsDirPath + fpRelItemPaths <- SD.listDirectory fpAbsDirPath relFilePaths <- filterFiles fpAbsDirPath fpRelItemPaths relDirPaths <- filterDirs fpAbsDirPath fpRelItemPaths return (relFilePaths, relDirPaths) @@ -52,10 +55,22 @@ listDirectory absDirPath = do filterFiles :: FilePath -> [FilePath] -> IO [Path' (Rel d) (File f)] filterFiles absDir relItems = - filterM (System.Directory.doesFileExist . (absDir FilePath.)) relItems + filterM (SD.doesFileExist . (absDir FilePath.)) relItems >>= mapM parseRelFile filterDirs :: FilePath -> [FilePath] -> IO [Path' (Rel d) Dir'] filterDirs absDir relItems = - filterM (System.Directory.doesDirectoryExist . (absDir FilePath.)) relItems + filterM (SD.doesDirectoryExist . (absDir FilePath.)) relItems >>= mapM parseRelDir + +deleteDirectoryIfExists :: Path' r (Dir d) -> IO () +deleteDirectoryIfExists dirPath = do + let dirPathStr = SP.toFilePath dirPath + exists <- SD.doesDirectoryExist dirPathStr + when exists $ SD.removeDirectoryRecursive dirPathStr + +deleteFileIfExists :: Path' r (File f) -> IO () +deleteFileIfExists filePath = do + let filePathStr = SP.toFilePath filePath + exists <- SD.doesFileExist filePathStr + when exists $ SD.removeFile filePathStr diff --git a/waspc/waspc.cabal b/waspc/waspc.cabal index efda9e68f..a60d78a0a 100644 --- a/waspc/waspc.cabal +++ b/waspc/waspc.cabal @@ -332,10 +332,12 @@ library cli-lib other-modules: Paths_waspc exposed-modules: Wasp.Cli.Command + Wasp.Cli.FileSystem Wasp.Cli.Command.BashCompletion Wasp.Cli.Command.Build Wasp.Cli.Command.Call Wasp.Cli.Command.Clean + Wasp.Cli.Command.Uninstall Wasp.Cli.Command.Common Wasp.Cli.Command.Compile Wasp.Cli.Command.CreateNewProject diff --git a/web/docs/cli.md b/web/docs/cli.md index 4beb26ce4..f8e1627ed 100644 --- a/web/docs/cli.md +++ b/web/docs/cli.md @@ -5,7 +5,7 @@ This document describes the Wasp CLI commands, arguments, and options. ## Overview -The `wasp` command can be called from command line once [installed](https://wasp-lang.dev/docs/#2-installation). +The `wasp` command can be called from command line once [installed](https://wasp-lang.dev/docs/#2-installation). When called without arguments, it will display its command usage and help document: ``` @@ -18,6 +18,7 @@ COMMANDS version Prints current version of CLI. waspls Run Wasp Language Server. Add --help to get more info. completion Prints help on bash completion. + uninstall Removes Wasp from your system. IN PROJECT start Runs Wasp app in development mode, watching for file changes. db [args] Executes a database command. Run 'wasp db' for more info. @@ -41,17 +42,36 @@ Newsletter: https://wasp-lang.dev/#signup ## Commands ### General - `wasp new ` creates new Wasp project. A directory with the provided project-name will be created, containing boilerplate code. - + ``` $ wasp new MyFirstProject ``` - `wasp version` prints current version of CLI. - + ``` $ wasp version - + 0.2.0.1 - ``` + ``` + - `wasp uninstall` removes Wasp from your system. + + ``` + $ wasp uninstall + + 🐝 --- Uninstalling Wasp ... ------------------------------------------------------ + + We will remove the following directories: + {home}/.local/share/wasp-lang/ + {home}/.cache/wasp/ + + We will also remove the following files: + {home}/.local/bin/wasp + + Are you sure you want to continue? [y/N] + y + + ✅ --- Uninstalled Wasp ----------------------------------------------------------- + ``` ### Bash Completion @@ -59,23 +79,23 @@ To setup Bash completion, execute `wasp completion` and follow the instructions. ### In project - `wasp start` runs Wasp app in development mode. It opens a browser tab with your application running, and watches for any changes to .wasp or files in `src/` to automatically reflect in the browser. It also shows messages from the web app, the server and the database on stdout/stderr. - + - `wasp clean` deletes all generated code and other cached artifacts. If using SQlite, it also deletes the SQlite database. It is the Wasp equivalent to "try shutting it down and turning back on". - + ``` $ wasp clean - + Deleting .wasp/ directory... Deleted .wasp/ directory. ``` - + - `wasp build` generates full web app code, ready for deployment. Use when deploying or ejecting. Generated code goes in the .wasp/build folder. - + - `wasp telemetry` prints [telemetry](https://wasp-lang.dev/docs/telemetry) status. - + ``` - $ wasp telemetry - + $ wasp telemetry + Telemetry is currently: ENABLED Telemetry cache directory: /home/user/.cache/wasp/telemetry/ Last time telemetry data was sent for this project: 2021-05-27 09:21:16.79537226 UTC @@ -85,11 +105,11 @@ To setup Bash completion, execute `wasp completion` and follow the instructions. - `wasp deps` prints the dependencies that Wasp uses in your project. - `wasp info` prints basic information about current Wasp project. - -#### Database + +#### Database Wasp has a set of commands for working with the database. They all start with `db` and mostly call prisma commands in the background. - `wasp db migrate-dev` ensures dev database corresponds to the current state of schema (entities): it generates a new migration if there are changes in the schema and it applies any pending migration to the database. - Supports a `--name foo` option for providing a migration name, as well as `--create-only` for creating an empty migration but not applying it. - + - `wasp db studio` opens the GUI for inspecting your database.