| name | drupaltools-site-clone |
| description | Clone an entire Drupal project as a clean starter template โ copying custom code, config structure, and tooling while stripping all site-specific data, credentials, UUIDs, and history. Use this skill whenever the user wants to create a new Drupal project based on an existing one, says "clone this site", "use this project as a template", "create a new site from this codebase", or asks to scaffold a new Drupal project from an existing one. |
Drupal site clone
Create a clean starter template from an existing local Drupal project.
Step 1 โ Confirm the source project
Ask the user to confirm the source project root if not already clear from context. Verify it looks like a Drupal project:
test -f composer.json && test -f composer.lock && \
test -d web/core && echo "Valid Drupal project root"
Also detect the webroot โ it may be web/, docroot/, or html/:
for d in web docroot html; do
[ -d "$d/core" ] && echo "Webroot: $d" && break
done
Store the detected webroot as $WEBROOT for all subsequent steps. If detection fails, ask the user.
Step 2 โ Ask for destination and new project name
Ask the user:
- Full path where the clone should be created (e.g.
/home/user/projects/newsite).
- New project machine name (used for composer.json
name, directory name, and any project-specific strings).
Validate the machine name: lowercase letters, digits, hyphens only, no spaces.
Validate the destination: must not already exist.
Step 3 โ Create the destination structure
mkdir -p "$DEST"
Then create the skeleton of folders that will be populated in later steps:
mkdir -p "$DEST/$WEBROOT/modules/custom"
mkdir -p "$DEST/$WEBROOT/themes/custom"
mkdir -p "$DEST/$WEBROOT/sites/default"
mkdir -p "$DEST/config"
mkdir -p "$DEST/drush"
mkdir -p "$DEST/scripts"
mkdir -p "$DEST/patches"
Step 4 โ Copy custom code
Copy each of the following, preserving internal structure:
cp -r "$SOURCE/$WEBROOT/modules/custom/." "$DEST/$WEBROOT/modules/custom/"
cp -r "$SOURCE/$WEBROOT/themes/custom/." "$DEST/$WEBROOT/themes/custom/"
[ -d "$SOURCE/drush" ] && cp -r "$SOURCE/drush/." "$DEST/drush/"
[ -d "$SOURCE/scripts" ] && cp -r "$SOURCE/scripts/." "$DEST/scripts/"
[ -d "$SOURCE/patches" ] && cp -r "$SOURCE/patches/." "$DEST/patches/"
for f in .github .gitlab-ci.yml .gitlab .circleci; do
[ -e "$SOURCE/$f" ] && cp -r "$SOURCE/$f" "$DEST/$f"
done
Step 5 โ Copy and strip config files
Copy all config directories, then strip UUIDs from every YAML file.
find "$SOURCE" -type d -name "config" | grep -v "$WEBROOT/core" | while read d; do
rel="${d#$SOURCE/}"
mkdir -p "$DEST/$rel"
cp -r "$d/." "$DEST/$rel/"
done
Strip UUIDs from all copied YAML files:
find "$DEST/config" -name "*.yml" | xargs sed -i '/^uuid:/d'
Also remove the _core block (contains site-specific hash) from config YAML:
find "$DEST/config" -name "*.yml" | xargs sed -i '/^_core:/,/^[^ ]/{ /^_core:/d; /^ default_config_hash:/d }'
Step 6 โ Copy and rewrite composer.json
Copy the file then prompt the user for new values:
cp "$SOURCE/composer.json" "$DEST/composer.json"
Ask the user for:
- name โ format
vendor/project (e.g. mycompany/newsite)
- description โ one line describing the new project
- authors โ name and email (can be empty)
Rewrite with the provided values using jq if available, otherwise sed:
jq --arg name "$NEW_NAME" \
--arg desc "$NEW_DESC" \
'.name = $name | .description = $desc | .authors = []' \
"$DEST/composer.json" > /tmp/composer.tmp && mv /tmp/composer.tmp "$DEST/composer.json"
Do not copy composer.lock โ the clone starts without a lock file. The user must run composer install to generate a fresh one.
Step 7 โ Copy README.md as a template
cp "$SOURCE/README.md" "$DEST/README.md"
Then replace the body content with placeholder sections, preserving only the top-level heading structure:
cat > "$DEST/README.md" << 'README'
[Describe the project.]
- Drupal: [version]
- PHP: [version]
- Composer: [version]
[Describe installation steps.]
[Describe configuration steps.]
[Describe local development setup.]
[List maintainers.]
README
Step 8 โ Strip sensitive and environment-specific files
Do not copy the following. If any were accidentally copied, remove them:
rm -rf "$DEST/.git"
rm -f "$DEST/.env" "$DEST/.env.local" "$DEST/.env.*.local"
rm -rf "$DEST/.vscode" "$DEST/.idea"
rm -f "$DEST/.editorconfig" 2>/dev/null
rm -f "$DEST/$WEBROOT/sites/default/settings.php"
rm -f "$DEST/$WEBROOT/sites/default/settings.local.php"
rm -f "$DEST/$WEBROOT/sites/default/services.yml"
rm -rf "$DEST/$WEBROOT/sites/default/files"
rm -f "$DEST/composer.lock"
Step 9 โ Report
List what was created, what was stripped, and what the user must do next:
Created:
- Directory structure under
$DEST
- Custom modules, themes, drush, scripts, patches, CI config
- Config files (UUIDs and
_core hashes stripped)
composer.json (rewritten)
README.md (template)
default.settings.php
- Fresh git repository (if confirmed)
Stripped:
.git, .env, IDE config, composer.lock
settings.php, services.yml, sites/default/files/
- All config UUIDs and
_core hashes
Required next steps for the user:
composer install โ generate a fresh composer.lock.
- Create
sites/default/settings.php from default.settings.php.
- Create a new database and update
settings.php with credentials.
drush site:install or import config with drush cim.
- Fill in
README.md.
- Set a remote git origin if needed.
Step 12 โ Wait for follow-up questions
Stop after the report. Typical follow-ups:
- Init a ddev project
ddev config
- "Check the custom modules against best practices" โ hand off to
drupaltools-best-practices.
- "Show me what config files were copied" โ list
$DEST/config/**/*.yml.
- "Update the composer.json further" โ edit specific fields on request.
- "What modules are in the custom folder?" โ hand off to
drupal-module-info for each.