Welcome to the MageForge development repository. This guide covers everything you need to set up a local development environment and contribute to the module.
- Repository Structure
- Prerequisites
- Initial Setup
- Development Workflow
- Code Quality
- Common Issues
- Building a Release
/mageforge
├── /src/ # ⭐ The MageForge module code
│ ├── /Console/Command/ # CLI commands
│ ├── /Service/ # Business logic & theme builders
│ ├── /Model/ # Domain models
│ ├── /etc/ # Module configuration (di.xml, module.xml, …)
│ ├── /view/ # Frontend assets & templates
│ └── /i18n/ # Localisation files
│
├── /magento/ # Local Magento 2 installation (testing only)
│ ├── /app/design/ # Test themes
│ ├── /vendor/ # Magento & dependencies
│ └── /bin/magento # Magento CLI
│
├── /.ddev/ # DDEV configuration
│ └── /commands/web/ # Custom DDEV web-container commands
│
├── /docs/ # Documentation (you are here)
├── CONTRIBUTING.md # Contribution guidelines & commit conventions
└── README.md # End-user documentation (install, features)
Key points
- All module development happens in
/src/— this is where you write code.- Testing happens in
/magento/— a full Magento 2 installation wired up for local use.- The repository is mounted read-only by Docker into
/magento/mageforge-source/and installed into Magento as a regular Composer package (path repository with symlink). Changes in/src/are visible instantly; no Composer step needed. The mount is established onddev start/ddev restart./magento/is never included in a release. It exists solely for local development.
| Tool | Notes |
|---|---|
| DDEV | v1.23 or higher recommended |
| Git | For cloning and branching |
| Docker | Required by DDEV |
Basic familiarity with Magento 2 module development is assumed.
-
Clone the repository:
git clone git@github.com:OpenForgeProject/mageforge.git cd mageforge -
Start DDEV (pulls containers, configures the environment):
ddev start
-
Install Magento 2:
ddev install-magento
This script will:
- Install a fresh Magento 2 instance inside
/magento/ - Install MageForge via Composer (path repository pointing at the mounted module source)
- Install Magento sample data
- Enable the MageForge module (
bin/magento module:enable) - Set developer mode and disable 2FA
Note:
ddev startmust run before this command to establish the Docker mount. - Install a fresh Magento 2 instance inside
-
Verify the installation:
ddev magento mageforge:system:check
You now have a fully functional local development environment.
The repository root is bind-mounted read-only by Docker (.ddev/docker-compose.mageforge-source.yaml) into the Magento root:
.. → magento/mageforge-source/ (read-only)
ddev install-magento registers this directory as a Composer path repository and installs the module with composer require openforgeproject/mageforge:@dev. Composer creates a symlink:
magento/vendor/openforgeproject/mageforge → ../../mageforge-source
Why this construction?
- Magento's path validator rejects paths that resolve (via
realpath()) outside the Magento root. A plain Composer symlink to the repository root would fail; the mount point lives inside the Magento root, so validation passes. - Read-only protects the source: an accidental
rm -rf magentoinside the container stops at the mount instead of deleting the repository through it. - Real Composer install: third-party dependencies of the module (e.g.
laravel/prompts) are resolved by Composer like for any end-user installation — no extra sync scripts needed. - An anonymous volume shadows
mageforge-source/magento/so the mount does not recurse into itself.
The mount is (re-)established every time the containers start. Always run ddev start or ddev restart when:
- setting up for the first time
- pulling config changes from the repository
/magento/was deleted while DDEV was running (see Common Issues)
Changed module dependencies (composer.json in the repository root) are picked up with:
ddev composer update openforgeproject/mageforge-
Edit code in
/src/(commands, services, builders, etc.) -
Apply changes to Magento:
ddev magento setup:upgrade # Register module updates ddev magento cache:clean # Clear application cache
-
Test your changes:
ddev magento mageforge:theme:list # List all themes ddev magento mageforge:theme:build <theme> # Build a theme ddev magento mageforge:theme:watch <theme> # Watch mode (Ctrl+C to stop) ddev magento mageforge:system:check # System diagnostics
ddev magento <command> # Run any Magento CLI command inside the container
ddev ssh # Open a shell inside the container
ddev xdebug on/off # Toggle Xdebug (VS Code tasks also available)
ddev logs # Tail container logs
ddev restart # Restart all containers# Theme detection
ddev magento mageforge:theme:list
# Standard Magento build
ddev magento mageforge:theme:build Magento/luma
# Hyvä theme build (requires Hyvä to be installed)
ddev magento mageforge:theme:build Hyva/default
# Watch mode
ddev magento mageforge:theme:watch Magento/luma
# System diagnostics
ddev magento mageforge:system:checkCI runs the following matrix against OpenSearch — test locally before opening a PR:
| Magento | PHP |
|---|---|
| 2.4.7-p10 | 8.3 |
| 2.4.8-p5 | 8.4 |
| 2.4.9 | 8.5 |
Run all checks before submitting a pull request:
# All non-PHP linters (actionlint, checkov, hadolint, markdownlint, prettier, shellcheck, yamllint)
trunk check
# Auto-format non-PHP files (prettier, shfmt, …)
trunk fmt
# Mago – PHP linter & formatter (config: mago.toml)
ddev mago lint
ddev mago fmt # or: ddev mago fmt --dry-run
# PHP CodeSniffer – Magento Coding Standard
ddev phpcs # auto-fix: ddev phpcbf
# PHPStan – static analysis (level 9, requires the local Magento installation)
ddev phpstanPHPCS and Mago are require-dev dependencies of the module (composer install in the
repository root provides vendor/bin/phpcs and vendor/bin/mago), so local runs and CI
use the same tool versions. PHPStan runs inside the Magento installation because it needs
the Magento class definitions.
CI runs the same checks on every pull request: the PHPCS, PHPStan and Lint workflows (Mago + Trunk) must all pass.
declare(strict_types=1)in every PHP file- Constructor property promotion with
readonly - Native type hints only — no FQNs in docblocks
- Named arguments when a function takes many parameters
- Enums over constants (PHP 8.3+)
For full details see CONTRIBUTING.md.
MageForge must work with and without Hyvä installed. Never add hard type-hints on optional third-party classes in constructors. Use mixed + class_exists() for optional dependencies and inline @var annotations for PHPStan.
Module not found after changes:
ddev magento setup:upgrade && ddev magento cache:cleanDDEV not starting:
ddev poweroff
ddev startMageForge source mount not active / mageforge-source empty:
This happens when /magento/ was deleted while DDEV was still running. Docker holds the old inode; the new directory is not covered by the mount.
ddev restart # Re-establishes the mount on the new inode
ddev install-magento # Re-installs Magento with the mount now activeModule dependencies missing after updating composer.json:
ddev composer update openforgeproject/mageforge # Re-resolves the module's dependenciesNeed to reinstall Magento from scratch:
ddev restart # Ensure the source mount is fresh first
ddev install-magento # Handles cleanup automaticallydetect() returns wrong builder:
Each builder's detect() method must uniquely identify its theme type. The BuilderPool picks the first matching builder, so overlapping detection logic causes unpredictable results.
Shell commands in builders:
Always use the Shell service via dependency injection — never call exec() or shell_exec() directly.
The module in /src/ is what gets packaged and published to Packagist. End users install it via:
composer require openforgeproject/mageforgeReleases are fully automated via Release Please. The workflow works as follows:
-
PR titles drive versioning: When a PR is merged into
mainwith a valid Conventional Commits title, Release Please automatically determines the next version number:feat:→ minor bump (e.g.,0.3.0→0.4.0)fix:,docs:,perf:→ patch bump (e.g.,0.3.0→0.3.1)feat!:orfix!:→ major bump (e.g.,0.3.0→1.0.0)
-
Automatic changelog: Release Please generates a changelog from all PR titles and descriptions since the last release. Contributors should write clear PR descriptions so they appear meaningfully in the release notes.
-
Release PR: Release Please opens an automated PR that updates
CHANGELOG.md,composer.jsonversion, and any other versioned files. Merging this PR triggers the actual release to Packagist and publishes a GitHub Release with the full changelog.
Release Please groups changes into sections based on the Conventional Commits type in your PR title:
| Commit Type | Changelog Section | Visible in Notes |
|---|---|---|
feat: |
Added | ✅ Yes |
fix: |
Fixed | ✅ Yes |
refactor: |
Changed | ✅ Yes |
perf: |
Performance | ✅ Yes |
docs: |
Documentation | ✅ Yes |
style: |
Styling | ✅ Yes |
chore: |
Chore | ❌ Hidden |
test: |
Tests | ❌ Hidden |
build: |
Build | ❌ Hidden |
ci: |
CI/CD | ❌ Hidden |
PRs are automatically labelled by the Labeler workflow based on branch name, PR title, or changed files:
| Label | Trigger |
|---|---|
| Documentation | Any *.md file changed |
| Feature | Branch matches add-*, feature-*, feat-*; or PR title starts with feat:, feature: |
| Fix | Branch matches fix-*, bugfix-*; or PR title starts with fix:, bugfix: |
| Next-Release | Branch/PR title matches chore: release* or release-please* |
| Label | Trigger |
|---|---|
| Command | Files changed in src/Console/Command/** |
| Frontend | Files changed in src/view/frontend/** |
| Theme-Builder | Files changed in src/Service/ThemeBuilder/** |
Tip: Name your branch according to the conventions above (
feat/my-feature,fix/issue-123) and the correct labels are applied automatically.
Tip: Use visible section types (
feat,fix,docs, etc.) for changes you want in the release notes. Use hidden types (chore,test,ci) for internal work that doesn't affect end users.
Tip: Check the Release Please PR before merging — it shows exactly what will appear in the release notes. You can edit the PR description to improve changelog entries if needed.
The /magento/ directory is excluded from all release artefacts.