From 2faa9a14304a2e7b8621905fc5fb69e6004f2dc8 Mon Sep 17 00:00:00 2001 From: allrude Date: Sun, 22 Feb 2026 15:57:32 +0100 Subject: [PATCH] feat: add `mage destroy ` to fully remove a project MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds a safe, interactive teardown command that removes everything created by `mage create` for a given project in one step. Usage (run from the parent directory of the project): mage destroy my-project What it destroys: - Valet TLS certificates (cert files + macOS keychain entry) - Valet nginx configs - Valet site symlinks - MySQL database (DROP DATABASE IF EXISTS) - Project folder (rm -rf) Safety: - The command is explicitly allowlisted in the Magento root guard so it can run from any directory (not just inside a Magento root). - Shows a clear WARNING listing exactly what will be removed. - Requires the user to type the full project name to confirm — any mismatch aborts immediately with no changes made. Valet cleanup is done directly on the filesystem (removing files from ~/.config/valet/Certificates/, Nginx/, and Sites/) so that nginx only restarts once at the end, regardless of how many store domains exist, instead of restarting once per domain as valet unsecure would do. Domains are read from .valet-env.php; only top-level array keys are treated as domain names (MAGE_RUN_CODE / MAGE_RUN_TYPE values are ignored). Changes: src/_destroy.sh (new) — mage_destroy_project function src/_global.sh — added destroy to the root-guard allowlist src/_info.sh — added help entry for destroy [NAME] src/_mage.sh — added dispatch cases for destroy and destroy * --- mage | 100 ++++++++++++++++++++++++++++++++++++++++++++++-- src/_destroy.sh | 82 +++++++++++++++++++++++++++++++++++++++ src/_global.sh | 2 +- src/_info.sh | 1 + src/_mage.sh | 8 ++++ 5 files changed, 188 insertions(+), 5 deletions(-) create mode 100644 src/_destroy.sh diff --git a/mage b/mage index 05962c5..b717159 100755 --- a/mage +++ b/mage @@ -8,7 +8,7 @@ MAGE_VERSION="2.5.2" # Check if this is the Magento 2 root if [[ ! -d app/etc ]]; then case "$1" in - version|help|self-update|install|setup|create) + version|help|self-update|install|setup|create|destroy) # Allow these commands to run even if not in Magento root ;; *) @@ -89,6 +89,89 @@ if [ -f .env ] && grep -q "WARDEN_ENV_NAME" .env && [[ ! "$PATH" == /var/www/htm PURGE_CLI="warden env exec -T php-fpm rm -rf" fi +# Fully remove a Magento project: folder, database, and all Valet links/certs. +# Run from the PARENT directory of the project, e.g.: +# cd ~/Developer/magento && mage destroy my-project +function mage_destroy_project() { + local name="$1" + + if [[ -z "$name" ]]; then + echo "Usage: mage destroy " + return 1 + fi + + if [[ ! -d "$name" ]]; then + echo "Error: Directory '${name}' not found in the current folder ($(pwd))." + echo "Run this command from the parent directory of the project." + return 1 + fi + + # Gather Valet domains by reading .valet-env.php — only top-level array keys + # (the ones with array values) are domain names; string-value keys are skipped. + local valet_env="${name}/.valet-env.php" + local domains=() + if [[ -f "$valet_env" ]]; then + while IFS= read -r _d; do + [[ -n "$_d" ]] && domains+=("$_d") + done < <(python3 -c " +import re, sys +content = open(sys.argv[1]).read() +for k in re.findall(r\"'([^']+)'\\s*=>\\s*\\[\", content): + print(k) +" "$valet_env") + fi + + echo "" + echo -e "${RED}WARNING: This will permanently destroy:${RESET}" + echo " • Project folder : $(pwd)/${name}" + echo " • Database : ${name}" + if [[ ${#domains[@]} -gt 0 ]]; then + echo " • Valet domains : ${domains[*]}" + fi + echo "" + read -p "Type the project name to confirm: " _confirm && echo "" + + if [[ "$_confirm" != "$name" ]]; then + echo -e "Aborted — '${_confirm}' does not match '${name}'." + return 1 + fi + + # Valet cleanup — delete cert files, nginx configs, and site symlinks directly + # so we can do a single valet restart instead of one nginx restart per domain. + if [[ $VALET == 1 && ${#domains[@]} -gt 0 ]]; then + local _valet_cfg="$HOME/.config/valet" + echo "Removing Valet certificates, nginx configs and site links..." + for d in "${domains[@]}"; do + rm -f "${_valet_cfg}/Certificates/${d}.test.crt" \ + "${_valet_cfg}/Certificates/${d}.test.csr" \ + "${_valet_cfg}/Certificates/${d}.test.key" \ + "${_valet_cfg}/Certificates/${d}.test.conf" + rm -f "${_valet_cfg}/Nginx/${d}.test" + rm -f "${_valet_cfg}/Sites/${d}" + sudo security delete-certificate -c "${d}.test" \ + /Library/Keychains/System.keychain 2>/dev/null || true + done + echo "Restarting nginx once..." + valet restart + fi + + # Drop database + echo "Dropping database '${name}'..." + if command -v mysql &>/dev/null; then + mysql -uroot -proot -e "DROP DATABASE IF EXISTS \`${name}\`;" 2>/dev/null \ + || echo " Warning: could not drop database (check credentials)." + else + echo " Warning: mysql not found — skipping database removal." + fi + + # Remove project folder + echo "Removing project folder '${name}'..." + rm -rf "${name}" + + echo "" + echo -e "${GREEN}✓ Project '${name}' has been fully removed.${RESET}" +} + # Creates a file/folder and echo the contents in one command function mage_make_file() { touch $1 @@ -347,6 +430,7 @@ function mage_help() { mage_help_cmd "create [NAME]" "Alias for 'mage install' and 'mage setup'" mage_help_cmd "install [NAME]" "Installs a new Magento 2 Project" mage_help_cmd "setup [NAME]" "Configures and sets up the new Magento 2 Project" + mage_help_cmd "destroy [NAME]" "Fully remove a project (folder, DB, Valet links & certs)" mage_help_sub_header "Development" mage_help_cmd "start" "Open store and admin with code editor and git client" @@ -871,9 +955,6 @@ function mage_add_sample() { touch README.md php -f $HOME/.magento-sampledata/$mversion/dev/tools/build-sample-data.php -- --ce-source="$PWD" - # Unset default styles from sample data - $MAGENTO_CLI config:set design/head/includes "" &> /dev/null - $MAGENTO_CLI setup:upgrade # Set theme to Hyva if present @@ -883,6 +964,9 @@ function mage_add_sample() { fi fi + # Unset default styles from sample data + $MAGENTO_CLI config:set design/head/includes "" &> /dev/null + $MAGENTO_CLI indexer:reindex $MAGENTO_CLI cache:flush } @@ -1055,6 +1139,14 @@ case "${@}" in echo "No name was given for the magento project, aborting.." ;; +"destroy") + echo "No project name given. Usage: mage destroy " + ;; + +"destroy "*) + mage_destroy_project "$2" + ;; + "create "*) mage_install $2 mage_setup diff --git a/src/_destroy.sh b/src/_destroy.sh new file mode 100644 index 0000000..5fb30c9 --- /dev/null +++ b/src/_destroy.sh @@ -0,0 +1,82 @@ +# Fully remove a Magento project: folder, database, and all Valet links/certs. +# Run from the PARENT directory of the project, e.g.: +# cd ~/Developer/magento && mage destroy my-project +function mage_destroy_project() { + local name="$1" + + if [[ -z "$name" ]]; then + echo "Usage: mage destroy " + return 1 + fi + + if [[ ! -d "$name" ]]; then + echo "Error: Directory '${name}' not found in the current folder ($(pwd))." + echo "Run this command from the parent directory of the project." + return 1 + fi + + # Gather Valet domains by reading .valet-env.php — only top-level array keys + # (the ones with array values) are domain names; string-value keys are skipped. + local valet_env="${name}/.valet-env.php" + local domains=() + if [[ -f "$valet_env" ]]; then + while IFS= read -r _d; do + [[ -n "$_d" ]] && domains+=("$_d") + done < <(python3 -c " +import re, sys +content = open(sys.argv[1]).read() +for k in re.findall(r\"'([^']+)'\\s*=>\\s*\\[\", content): + print(k) +" "$valet_env") + fi + + echo "" + echo -e "${RED}WARNING: This will permanently destroy:${RESET}" + echo " • Project folder : $(pwd)/${name}" + echo " • Database : ${name}" + if [[ ${#domains[@]} -gt 0 ]]; then + echo " • Valet domains : ${domains[*]}" + fi + echo "" + read -p "Type the project name to confirm: " _confirm && echo "" + + if [[ "$_confirm" != "$name" ]]; then + echo -e "Aborted — '${_confirm}' does not match '${name}'." + return 1 + fi + + # Valet cleanup — delete cert files, nginx configs, and site symlinks directly + # so we can do a single valet restart instead of one nginx restart per domain. + if [[ $VALET == 1 && ${#domains[@]} -gt 0 ]]; then + local _valet_cfg="$HOME/.config/valet" + echo "Removing Valet certificates, nginx configs and site links..." + for d in "${domains[@]}"; do + rm -f "${_valet_cfg}/Certificates/${d}.test.crt" \ + "${_valet_cfg}/Certificates/${d}.test.csr" \ + "${_valet_cfg}/Certificates/${d}.test.key" \ + "${_valet_cfg}/Certificates/${d}.test.conf" + rm -f "${_valet_cfg}/Nginx/${d}.test" + rm -f "${_valet_cfg}/Sites/${d}" + sudo security delete-certificate -c "${d}.test" \ + /Library/Keychains/System.keychain 2>/dev/null || true + done + echo "Restarting nginx once..." + valet restart + fi + + # Drop database + echo "Dropping database '${name}'..." + if command -v mysql &>/dev/null; then + mysql -uroot -proot -e "DROP DATABASE IF EXISTS \`${name}\`;" 2>/dev/null \ + || echo " Warning: could not drop database (check credentials)." + else + echo " Warning: mysql not found — skipping database removal." + fi + + # Remove project folder + echo "Removing project folder '${name}'..." + rm -rf "${name}" + + echo "" + echo -e "${GREEN}✓ Project '${name}' has been fully removed.${RESET}" +} diff --git a/src/_global.sh b/src/_global.sh index 11116b1..51bc7ee 100644 --- a/src/_global.sh +++ b/src/_global.sh @@ -3,7 +3,7 @@ MAGE_VERSION="2.5.2" # Check if this is the Magento 2 root if [[ ! -d app/etc ]]; then case "$1" in - version|help|self-update|install|setup|create) + version|help|self-update|install|setup|create|destroy) # Allow these commands to run even if not in Magento root ;; *) diff --git a/src/_info.sh b/src/_info.sh index 094648c..a1d210a 100644 --- a/src/_info.sh +++ b/src/_info.sh @@ -23,6 +23,7 @@ function mage_help() { mage_help_cmd "create [NAME]" "Alias for 'mage install' and 'mage setup'" mage_help_cmd "install [NAME]" "Installs a new Magento 2 Project" mage_help_cmd "setup [NAME]" "Configures and sets up the new Magento 2 Project" + mage_help_cmd "destroy [NAME]" "Fully remove a project (folder, DB, Valet links & certs)" mage_help_sub_header "Development" mage_help_cmd "start" "Open store and admin with code editor and git client" diff --git a/src/_mage.sh b/src/_mage.sh index 14d6559..c5f9639 100644 --- a/src/_mage.sh +++ b/src/_mage.sh @@ -27,6 +27,14 @@ case "${@}" in echo "No name was given for the magento project, aborting.." ;; +"destroy") + echo "No project name given. Usage: mage destroy " + ;; + +"destroy "*) + mage_destroy_project "$2" + ;; + "create "*) mage_install $2 mage_setup