academic-extensions is a mono repository to develop a couple of academic related TYPO3 extensions,
which may depend on others. To keep the maintenance burden across the set of extension small while
increasing the cross-over development and testing experience.
| For | Where |
|---|---|
| Users and integrators | The Documentation/ folder of each extension, rendered to docs.typo3.org |
| Developers and maintainers | docs/ |
| Contributors, entry point | CONTRIBUTING.md |
| AI coding agents | AGENTS.md |
Each extension ships its own manual — there is no repository-wide one, because
each extension is released and published on its own. docs/ is
the counterpart for the repository itself: the harness, the rules the code
follows, and how a release is cut.
| Branch | Version | TYPO3 | PHP |
|---|---|---|---|
| main | ^3, 3.x-dev | v13 + v14 | 8.2, 8.3, 8.4, 8.5 |
| 2, 2.x | ^2, 2.x-dev | v12 + v13 | 8.1, 8.2, 8.3, 8.4, 8.5 (depending on TYPO3) |
| 1 | ^1, 1.x-dev | v11 + v12 | 8.1, 8.2, 8.3, 8.4 (depending on TYPO3) |
Testing 3.x.x extension version in projects (composer mode)
It is already possible to use and test the 2.x version in composer based instances,
which is encouraged and feedback of issues not detected by us (or pull-requests).
Your project should configure minimum-stabilty: dev and prefer-stable to allow
requiring each extension but still use stable versions over development versions:
composer config minimum-stability "dev" \
&& composer config "prefer-stable" trueand than for example:
composer require 'fgtclb/academic-persons':'3.*.*@dev'That way, current main branch will be included and updated and as soon as 3.0.0 is released switcht to the release on update.
Upgrading from 1.x to 2.x includes breaking changes, which needs to be
addressed manualy in case not automatic upgrade path is available. See the
UPGRADE.md file of each extension for details.
| Extension | v11 | v12 | v13 | v14 |
|---|---|---|---|---|
| academic_base | - | <2> | <2> <3> | <3> |
| academic_bite_jobs | <1> | <1> <2> | <2> <3> | <3> |
| academic_contacts4pages | <1> | <1> <2> | <2> <3> | <3> |
| academic_study_plan | - | <2> | <2> <3> | <3> |
| academic_jobs | <1> | <1> <2> | <2> <3> | <3> |
| academic_partners | <1> | <1> <2> | <2> <3> | <3> |
| academic_persons | <1> | <1> <2> | <2> <3> | <3> |
| academic_persons_edit | <1> | <1> <2> | <2> <3> | <3> |
| academic_persons_sync | <1> | <1> <2> | <2> <3> | <3> |
| academic_programs | <1> | <1> <2> | <2> <3> | <3> |
| academic_projects | <1> | <1> <2> | <2> <3> | <3> |
| category_types | <1> | <1> <2> | <2> <3> | <3> |
Legend:
<X> Allowed and used with X.y.z
{X} Allowed but not tested/verified with X.y.z, but may/could work
-X- Allowed but absolutely not tested and most likely not working (yet)
(X) Planned for the upcoming X.y.z line, not yet available/tested
The 3.x line (in development)
The <3> marker documents the upcoming major 3.x line, which targets TYPO3
v13 + v14 (see the branch support matrix above). Both core versions are
implemented and verified for every extension above by the ci.yml workflow,
which carries the core version as a matrix dimension rather than as a workflow
of its own. The 3.x line itself is still in development
(3.0.0-dev) and not released yet.
Every test and quality tool runs in a container through the
Build/Scripts/runTests.sh wrapper. The only
requirement on the host is a container runtime — podman (preferred) or
docker.
# Install dependencies for the core version and PHP version you will test.
Build/Scripts/runTests.sh -t 13 -p 8.2 -s composerUpdate
# Quality gates.
Build/Scripts/runTests.sh -t 13 -p 8.2 -s cgl -n
Build/Scripts/runTests.sh -t 13 -p 8.2 -s phpstan
Build/Scripts/runTests.sh -t 13 -p 8.2 -s lintPhp
# Tests.
Build/Scripts/runTests.sh -t 13 -p 8.2 -s unit
Build/Scripts/runTests.sh -t 13 -p 8.2 -s functional
# All available options.
Build/Scripts/runTests.sh -h-t selects configuration only, it does not reinstall dependencies.
Everything has to pass for both TYPO3 versions this branch supports, each
after its own composerUpdate — see
Dual core setup.
→ CONTRIBUTING.md for the contribution workflow ·
docs/ for the full developer documentation
Two ready-to-start TYPO3 instances live at the repository root, one per supported core version:
| Folder | TYPO3 | DDEV project | Theme | Content |
|---|---|---|---|---|
core-13/ |
v13 | core13-academics-v3 |
bootstrap_package |
packages-dev/dev-site seed |
core-14/ |
v14 | core14-academics-v3 |
bootstrap_package |
packages-dev/dev-site seed |
Both run on SQLite — no database container is started (omit_containers: [db]).
Each instance is seeded on first start from the committed template in
sqlite-databases/, by config/system/additional.php. So there is no setup step:
check out, start, log in.
What is in those templates is described rather than clicked together: the
seed set packages-dev/dev-site/Configuration/DataFactory/academics-instance/
holds the page tree, the content and the records, and
ddev composer instance:seed imports it into an empty instance. That is how a template is rebuilt when it goes stale — see
Rebuilding an instance from nothing.
cd core-13 && ddev start && ddev launch /typo3/The backend admin account is john-doe, and the set also creates the frontend
user needed to look at EXT:academic_persons_edit. Both, and what is on which
page, are documented in
Development instances.
The instance database is git-ignored (core-*/var/); the template next to it is
committed. Two composer scripts move state between them:
cd core-13
ddev composer sqlite:backup # instance -> sqlite-databases/core-13.sqlite (commit this)
ddev composer sqlite:apply # sqlite-databases/core-13.sqlite -> instance (discards changes)
ddev composer system:refresh # flush + warm caches, update languages, extension:setupsqlite:backup rewrites a multi-megabyte binary that git cannot delta-compress,
so commit it when the demo content genuinely changed, not on every run.
Both directions go through Build/Scripts/sqliteSnapshot.php rather than cp,
because a running instance keeps its newest writes in a SQLite write ahead log
that a plain copy leaves behind — see
Development environment.
Deleting the database does not empty an instance: config/system/additional.php
copies the committed template back on the next request. That is what makes a
fresh clone work without a setup step, and it is in the way when the point is to
build the content again from scratch.
cd core-13
ddev composer instance:fresh # drop the database and stop the automatic seedingThis writes the git-ignored marker core-13/.no-database-seed; the environment
variable ACADEMICS_NO_DATABASE_SEED does the same for scripted use. Neither is
ever committed, and ddev composer sqlite:apply clears the marker again.
Installing TYPO3 into the empty instance afterwards has two sharp edges — TYPO3's
own setup command rewrites the tracked settings.php and picks its own
database file name. The walk-through, with both fix-ups, is in
Development environment.
cd core-13 && ddev stop -ROU && git clean -xdf -e '.idea'The instance folders sit at the repository root on every branch, but each branch
names its DDEV projects after the version line it carries — core13-academics-v3
here on main, core13-academics-v2 on 2. Two different project names for the
same directory is a state DDEV refuses:
Failed to start app core13-academics-v2: this project root '…/core-13'
already contains a project named 'core13-academics-v3'.
That is not a broken checkout. DDEV remembers the project by path, and the name changed underneath it. Unregister the one belonging to the other branch and start again:
ddev stop --unlist core13-academics-v2
ddev start--unlist only removes the registration; it touches neither the containers nor
any data. The instance database lives in the git-ignored core-*/var/, so it
survives the switch and keeps whatever the other branch left there — run
ddev composer sqlite:apply to reset it to this branch's committed template.
core-*/vendor/ survives the switch the same way, and unlike the database it is
then wrong: it is git-ignored, so it is not per branch, and its autoloader
still points at the path packages of the branch it was installed from. Running
vendor/bin/typo3 after a switch fails with a Failed opening required … EXT_CONSTANTS.php. ddev start fixes it — its post-start hook runs
composer install — and ddev composer install does the same without a
restart.
The instances do not depend on DDEV. config/system/additional.php recomputes the
database path from __DIR__ and the site configurations use host-less, relative
base values, so a host stack only needs PHP with pdo_sqlite and a vhost
pointing at core-13/public. Local-only overrides — different binary paths, a
different mail transport — go into core-*/config/system/additional/*.php, which
is git-ignored and included automatically.
A release is always cut from the branch owning that version line (see the
branch support matrix) using the two scripts in
bin/. All extensions of the mono repository are released together, sharing one
version number.
| Branch | Release line | Example version | Release tooling |
|---|---|---|---|
| main | 3.x |
3.0.0 |
bin/release, bin/set-version |
| 2 | 2.x |
2.4.0 |
bin/release, bin/set-version |
| 2.2 | 2.2.x |
2.2.2 |
bin/release, bin/set-version |
| 1 | 1.x |
- | none — that branch has no bin/ scripts |
Both scripts are kept as the same implementation on every branch. Only two
things legitimately differ per branch: the --source-branch default (which
equals the branch itself) and the version examples in the help output (which
must lie inside that branch's version range).
bin/set-version resolves composer, php, tailor, pkw, jq and sed
from PATH; bin/release additionally needs git and an authenticated gh.
Both abort with an explicit error if a tool is missing, before changing
anything.
bin/set-version <version> <type> [--source-branch=<name>] [--dry-run]<type> selects how the version is written:
| Type | Result |
|---|---|
release |
tag/release version — X.Y.Z, academic deps X.Y.Z@dev |
post-release |
next dev version — X.Y.W-dev, deps ~X.Y.W@dev, branch-alias X.Y.x-dev; the version passed is already the next one, no +1 happens here |
dev |
force a plain dev version everywhere (X.Y.Z-dev); thin variant of post-release, used for branching and forced minor/major bumps |
It rewrites, in one pass:
Build/Scripts/runTests.sh→COMPOSER_ROOT_VERSION- split extensions → academic composer deps,
extra.typo3/cms.version, branch-alias,tailor set-version,VERSIONfile - functional-test fixture extensions → composer deps only
ext_emconf.php→versionplusdepends/suggestsconstraintspackages-dev/*→ academic deps,extra.typo3/cms.version,VERSIONfilecore-*/development instances →academics-monorepo-sharedrequire- root
composer.json→monorepo-sharedrequire, branch alias
There are no path-repository version maps to keep in sync any more. The composer
plugin sbuerk/extended-path-repository derives a path package's version from
the package itself, so steps 2 and 5 — which write extra.typo3/cms.version and
the VERSION file — are what sets the version everywhere it is consumed.
The script only edits working-tree files — it performs no git and no network
operations. --dry-run prints every single change without touching a file and
is the safe way to rehearse a bump.
bin/release <release-version> [--source-branch=<name>] [--dry-run|--execute]It runs two phases, delegating all version rewriting to bin/set-version:
- Phase 1 (release) — branch
release-X.Y.Z,set-version X.Y.Z release, commit[RELEASE] X.Y.Z, push, open a PR, wait for the checks, admin rebase-merge, then tagX.Y.Zon the refreshed source branch and push the tag. - Phase 2 (post-release) — branch
set-version-X.Y.W(W = Z+1),set-version X.Y.W post-release, commit[TASK] Set version X.Y.W, push, PR, checks, admin rebase-merge.
Two independent safety gates control how far a run goes:
| Invocation | Local steps | Remote/irreversible steps (push, PR, merge, tag) |
|---|---|---|
| (bare) | executed | only printed — a bare run can never mutate the remote or create a tag |
--dry-run |
printed | printed |
--execute |
executed | executed |
--dry-run and --execute are mutually exclusive. Pre-flight checks refuse to
run outside a git work tree or when the target tag already exists; a dirty
working tree is fatal for --execute and only a warning otherwise, so the flow
stays rehearsable.
Pushing the tag starts the publish workflow
(.github/workflows/publish.yml), which:
- verifies the tag matches
MAJOR.MINOR.PATCH, - builds one TER upload artifact per extension via
tailor create-artefact— this step fails when the tag does not match an extension'sext_emconf.phpversion, which is exactly whatbin/set-versionkeeps in sync, and - creates the GitHub release
[RELEASE] <version>with generated release notes and attaches the artifacts plusLICENSE.
The mono repository itself is not published to the TER — it is a composer
project, not an extension. TER publishing happens per extension, one step
later in the chain:
- the tag is pushed here and the workflow above creates the GitHub release,
- the (external) splitter mirrors the tagged state into the read-only split repositories listed above,
- each split repository carries its own
publishworkflow, which reacts to the tag arriving there and runstailor ter:publishfor that single extension.
That per-extension workflow is maintained in this repository as
packages/fgtclb/<package>/.github/workflows/publish.yml (all 12 packages ship
one) and is split out together with the package — so TER publishing is changed
here, never in a split repository.
Note that the documentation-rendering step of the root workflow is currently commented out.