A command-line tool for Adobe Experience Manager (AEM) content management and cleanup operations.
AEM CLI provides utilities for managing AEM content repositories, with a focus on cleaning and maintaining .content.xml
files and transferring JCR content between filesystem and server. The tool helps developers and content managers automate common AEM maintenance tasks.
- content-cleanup - Clean AEM metadata properties from
.content.xml
files - asset-remove-unused - Find and remove unused DAM assets with reference checking
- repo - FTP-like tool for JCR content transfer between filesystem and server
- Remove AEM metadata properties from
.content.xml
files - Flexible property selection - use default AEM properties or specify custom ones
- Recursive processing - handles entire directory trees
- Dry-run mode - preview changes before applying them
- Detailed reporting - see exactly what properties are removed
The tool removes common AEM system properties that are typically not needed in source control:
cq:isDelivered
,cq:lastModified
,cq:lastModifiedBy
cq:lastReplicated*
,cq:lastReplicatedBy*
,cq:lastReplicationAction*
jcr:isCheckedOut
,jcr:lastModified
,jcr:lastModifiedBy
,jcr:uuid
- Find unused DAM assets with common MIME types (images, videos, documents, audio)
- Reference checking - scans all
.content.xml
files forimage
andfileReference
properties - Smart thumbnail cleanup - handles
dam:folderThumbnailPaths
references properly - Confirmation prompts - asks for confirmation before deletion with detailed summary
- Dry-run mode - preview what would be deleted without making changes
- Comprehensive reporting - shows used vs unused assets with reference counts
The tool focuses on common MIME types that are typically managed in DAM:
- Images: JPEG, PNG, GIF, WebP, SVG, BMP, TIFF
- Videos: MP4, AVI, MOV, WMV, FLV, WebM, MKV
- Documents: PDF, Word, Excel, PowerPoint
- Audio: MP3, WAV, OGG, AAC, M4A
- FTP-like tool for JCR content with support for diffing
- Checkout - initial checkout of server content to filesystem
- Put - upload local filesystem content to server
- Get - download server content to local filesystem
- Status - list status of modified/added/deleted files
- Diff - show differences between local and server content
- Configuration support -
.repo
files for server/credentials - Package-based transfers - uses AEM package manager HTTP API
- Python 3.8 or higher
- pip package manager
# Clone the repository
git clone <repository-url>
cd aemcli
# Create virtual environment
python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
# Install dependencies
pip install -r requirements.txt
# Install the package in development mode
pip install -e .
# Clean using default AEM properties
aemcli content-cleanup /path/to/content
# Preview changes without modifying files
aemcli content-cleanup /path/to/content --dry-run
# Use only custom properties
aemcli content-cleanup /path/to/content cq:customProp jcr:myProp
# Combine default and custom properties
aemcli content-cleanup /path/to/content --default cq:customProp
# Explicitly use default properties
aemcli content-cleanup /path/to/content --default
--dry-run
- Show what would be changed without modifying files--default
- Include default AEM properties in removal list--help
- Show detailed help and examples
# Find and remove unused DAM assets (with confirmation)
aemcli asset-remove-unused /path/to/content/dam
# Preview what would be deleted without making changes
aemcli asset-remove-unused /path/to/content/dam --dry-run
# Start from specific directory
aemcli asset-remove-unused content/dam/projects
# Use current directory
aemcli asset-remove-unused .
The command performs the following steps:
- Asset Discovery: Recursively finds all
.content.xml
files withjcr:primaryType="dam:Asset"
and common MIME types - Reference Scanning: Checks all
.content.xml
files in the jcr_root structure for references via:image="asset-path"
propertiesfileReference="asset-path"
propertiesdam:folderThumbnailPaths="[asset-path,...]"
properties
- Smart Cleanup: For assets only referenced in
folderThumbnailPaths
:- Removes the asset path from the thumbnail array
- Marks asset for deletion
- Confirmation: Shows detailed summary and asks for confirmation before deletion
- Deletion: Removes entire parent folders containing unused asset
.content.xml
files
--dry-run
- Preview what would be deleted without making any changes--help
- Show detailed help and examples
# Preview unused assets in a project
aemcli asset-remove-unused content/dam/myproject --dry-run
# Clean up unused assets with confirmation
aemcli asset-remove-unused content/dam/myproject
# Check entire DAM folder
aemcli asset-remove-unused content/dam
# Start from scratch with a server project
aemcli repo checkout /apps/myproject
# Make local changes
cd jcr_root/apps/myproject
vim .content.xml
# Check what changed
aemcli repo status
# Upload changes
aemcli repo put
# Later, download server changes
aemcli repo get
# Show differences
aemcli repo diff
# Preview unused assets before deletion
aemcli asset-remove-unused content/dam/myproject --dry-run
# Review the output, then proceed with cleanup
aemcli asset-remove-unused content/dam/myproject
# Clean up entire DAM folder
aemcli asset-remove-unused content/dam --dry-run
aemcli asset-remove-unused content/dam
The repo
command provides FTP-like functionality for transferring JCR content between the filesystem and AEM server.
# Initial checkout from server
aemcli repo checkout /apps/myproject
# Upload changes to server
cd jcr_root/apps/myproject
aemcli repo put
# Download changes from server
aemcli repo get
# Check status
aemcli repo status
# or
aemcli repo st
# Show differences
aemcli repo diff
# Use custom server and credentials
aemcli repo status -s http://localhost:8080 -u user:password
# Force operations without confirmation
aemcli repo put -f
# Quiet mode (minimal output)
aemcli repo get -q
# Show server-side changes
aemcli repo serverdiff
# Show local changes
aemcli repo localdiff
.repo
file - Place in checkout or any parent directory:
server=http://server.com:8080
credentials=user:password
.repoignore
file - Place in jcr_root directory to exclude files:
*.tmp
.cache/
*.log
checkout <jcr-path>
- Initial checkout of server contentput [path]
- Upload local content to serverget [path]
- Download server content to localstatus [path]
- Show status of files (alias:st
)diff [path]
- Show local differences vs serverlocaldiff [path]
- Show local changesserverdiff [path]
- Show server changes
M
- ModifiedA
- Added locally / deleted remotelyD
- Deleted locally / added remotely~ fd
- Conflict: local file vs. remote directory~ df
- Conflict: local directory vs. remote file
# Clean all .content.xml files in an AEM project
aemcli content-cleanup content/sites-franklin-commerce --dry-run
# Apply the changes
aemcli content-cleanup content/sites-franklin-commerce
# Remove only specific custom properties
aemcli content-cleanup /path/to/content cq:myCustomProp jcr:tempData
# Remove default properties plus custom ones
aemcli content-cleanup /path/to/content --default cq:myCustomProp
# See what would be changed without modifying files
aemcli content-cleanup /path/to/content --dry-run
# Start from scratch with a server project
aemcli repo checkout /apps/myproject
# Make local changes
cd jcr_root/apps/myproject
vim .content.xml
# Check what changed
aemcli repo status
# Upload changes
aemcli repo put
# Later, download server changes
aemcli repo get
# Show differences
aemcli repo diff
# Preview unused assets before deletion
aemcli asset-remove-unused content/dam/myproject --dry-run
# Review the output, then proceed with cleanup
aemcli asset-remove-unused content/dam/myproject
# Clean up entire DAM folder
aemcli asset-remove-unused content/dam --dry-run
aemcli asset-remove-unused content/dam
aem-cli/
├── src/aemcli/ # Main package
│ ├── cli.py # CLI entry point
│ └── commands/ # Command modules
│ ├── content_cleanup.py
│ ├── asset_remove_unused.py
│ └── repo.py
├── tests/ # Test suite
│ ├── test_content_cleanup.py
│ ├── test_asset_remove_unused.py
│ ├── test_repo.py
│ └── test_content/ # Test data
├── requirements.txt # Dependencies
├── pyproject.toml # Project configuration
└── README.md # This file
# Activate virtual environment
source .venv/bin/activate
# Run all tests
python -m pytest
# Run with verbose output
python -m pytest -v
# Run with coverage
python -m pytest --cov=aemcli
# Format code
black src/ tests/
# Lint code
flake8 src/ tests/
# Type checking
mypy src/
Run the same checks locally that GitHub Actions will run:
# Quick checks (syntax and tests)
./scripts/run_checks.sh
# Full checks (includes linting, formatting, type checking, security)
./scripts/run_checks.sh --full
Set up pre-commit hooks to run checks automatically before each commit:
# Install pre-commit hooks
pre-commit install
# Run hooks on all files (optional)
pre-commit run --all-files
This project uses GitHub Actions for continuous integration and deployment. Two workflows are configured:
- Triggers: Every push and pull request
- Purpose: Quick feedback on test results and basic linting
- Actions:
- Runs tests with pytest
- Performs basic syntax and error checking with flake8
- Uses Python 3.11 on Ubuntu
- Triggers: Push/PR to main, master, or develop branches
- Purpose: Comprehensive testing and code quality checks
- Actions:
- Multi-version testing: Tests against Python 3.8, 3.9, 3.10, 3.11, and 3.12
- Code quality: Runs flake8, black, and mypy
- Test coverage: Generates coverage reports and uploads to Codecov
- Security scanning: Runs safety and bandit security checks
- Dependency caching: Speeds up builds by caching pip dependencies
- Parallel execution: Test and security jobs run in parallel
- Caching: Pip dependencies are cached for faster builds
- Coverage reporting: Automatic upload to Codecov (optional)
- Security checks: Automated vulnerability scanning
- Multi-Python support: Ensures compatibility across Python versions
- click>=8.0 - Command-line interface framework
- requests>=2.28 - HTTP library for AEM API calls
- pytest>=7.0 - Testing framework
- pytest-cov>=4.0 - Test coverage reporting
- black>=23.0 - Code formatting
- flake8>=6.0 - Code linting
- mypy>=1.0 - Static type checking
- colorama>=0.4.6 - Cross-platform colored terminal text
- rich>=13.0 - Rich text and beautiful formatting
- Fork the repository
- Create a feature branch (
git checkout -b feature/amazing-feature
) - Make your changes
- Add tests for new functionality
- Run the test suite (
python -m pytest
) - Commit your changes (
git commit -m 'Add amazing feature'
) - Push to the branch (
git push origin feature/amazing-feature
) - Open a Pull Request
- Follow PEP 8 style guidelines
- Add tests for new features
- Update documentation as needed
- Use type hints where appropriate
This project is licensed under the MIT License - see the LICENSE file for details.
For questions, issues, or contributions, please:
- Check existing issues in the repository
- Create a new issue with detailed information
- Include steps to reproduce any bugs
- Provide example files when relevant
See CHANGELOG.md for detailed version history and release notes.