Skip to main content

Version Management

FrankPHP includes a lightweight version management workflow designed for the way the framework is actually shipped: through GitHub Releases, with framework/ versioned and packaged independently from app/. This feature gives you a basic form of release management inside the framework itself. It stays true to the FrankPHP approach: no dependencies, no complex pipelines, no external release tooling. Just plain PHP scripts, a few predictable files, and a repeatable workflow you can follow every time you prepare a release.
This is not intended to be enterprise release engineering. It is a simple, durable release workflow for a lightweight PHP framework.

What it does

The version management feature gives you four practical things:
  1. A single source of truth for the current framework/ version
  2. A structured release manifest that records what changed
  3. A snapshot-based diff system so future releases can be compared properly
  4. A clean packaging workflow for building a framework-only release zip without leaking local-only files
In practice, that means you can move from “I think I changed a few files” to “I know what changed, what version this is, and what should go into the framework-only zip attached to the GitHub Release.”
This feature only versions framework/. app/ is never touched by a framework update, so it has no VERSION, CHANGELOG.md, or release tooling of its own — it’s just your own application code, tracked however you already track your project.

The files this feature adds

All of the files below live inside framework/, not at the project root.

framework/VERSION

A plain text file containing the current framework version number only. Example:
This is the single source of truth for the framework version. Both the release manifest generator and the release build process read from this file.

framework/CHANGELOG.md

A human-readable summary of what was added, changed, and fixed in each framework release. This is the file you update for yourself, your customers, and your future documentation. It explains the release in normal language.

framework/RELEASE_MANIFEST.json

A structured, AI-friendly release record. This file is generated as a draft and then reviewed by hand. It is especially useful when you come back later and want to understand what changed in a release without scanning the whole codebase. It typically includes:
  • version
  • release_date
  • summary
  • changes.added
  • changes.changed
  • changes.removed
  • route_changes
  • database_changes
  • breaking_changes
  • ai_notes

framework/tools/generate_release_manifest.php

This script creates a draft RELEASE_MANIFEST.json and stores a snapshot of the current release so the next release has something concrete to compare against. It works by:
  • reading the current version from framework/VERSION
  • scanning the framework project files
  • comparing them with the previous release snapshot
  • extracting route changes from framework/bootstrap.php
  • extracting basic schema signals from framework/sql/schema.core.sql
  • writing a new draft RELEASE_MANIFEST.json
  • storing release snapshot data under framework/releases/{version}/

framework/releases/{version}/

This folder stores the internal snapshot for each release. It exists so FrankPHP can compare the current state of the framework against the last known release. That is what makes the generated manifest more useful over time. This folder is for your internal release process. It is excluded from the framework-only release zip.

framework/tools/build_release.php

This script builds a clean, framework-only distributable folder and zip — it packages framework/ alone, not app/. It creates a dist/ build using exclusion rules so local-only files such as internal release snapshots, editor files, and tooling directories do not accidentally end up in the packaged zip.
Because the build is framework-only, it’s meant to be attached to a GitHub Release as an update asset for existing installs — not used as the artifact a new customer downloads. A new install just uses the repo’s own “Source code” zip/tarball, which GitHub generates automatically for every release and already contains both framework/ and app/.

Why this fits FrankPHP

FrankPHP is deliberately explicit. It avoids abstraction layers, hidden magic, and dependency-heavy workflows. This release system follows the same principle.
  • No Composer packages
  • No Node tooling
  • No CI/CD requirement
  • No release SaaS
  • No “pipeline” you need to understand before you can ship a release
Instead, the release workflow is just:
  • update the version
  • generate the manifest
  • review the outputs
  • build the framework-only zip
  • publish a GitHub Release and attach it
That is enough structure to make releases safer and more repeatable, without turning the framework into something heavier than it needs to be.

How to use it

This is the workflow to follow every time you prepare a framework/ release.

Step 1: Update framework/VERSION

Open the VERSION file inside framework/ and change it to the new release number. Example:
The VERSION file must be saved as plain text and contain only the version number. Do not save it as rich text.

Step 2: Generate the release manifest

From inside framework/, run:
This will:
  • create or update RELEASE_MANIFEST.json
  • store a snapshot under releases/{version}/
  • compare the current framework codebase to the previous release snapshot when one exists

First run behaviour

On the first run, there is no previous snapshot yet. That is expected. In that case, the script creates the baseline snapshot for the current version. From that point onward, future runs can compare against it.

Step 3: Review RELEASE_MANIFEST.json

Do not treat the generated manifest as final. It is a draft. You should always review and usually update:
  • summary
  • breaking_changes
  • ai_notes
You should also quickly sanity-check:
  • changes
  • route_changes
  • database_changes
The machine should record the facts. You should provide the meaning.

Step 4: Update framework/CHANGELOG.md

Add the new release entry in plain language. This is the human-readable record of the release.

Step 5: Keep framework/codebase.md in sync

If the framework conventions, workflow, or release process changed, update framework/codebase.md so future AI pair-coding sessions have an accurate framework-level reference. This is particularly useful when the change is architectural rather than just functional.

Step 6: Build the framework-only zip

From inside framework/, run:
This creates a clean release under dist/ and then builds the zip — framework/ only, app/ is never included. Typical output:
The build script excludes local-only and internal files such as:
  • releases/
  • tools/
  • editor junk files
  • temporary logs or caches

Step 7: Publish a GitHub Release and attach the zip

Create a new GitHub Release for the tag/version you’re shipping. GitHub automatically generates and attaches its own Source code (zip) and Source code (tar.gz) assets to every release — these contain the full repo, framework/ and app/ together, and are what a brand new install downloads. Manually attach the dist/frankphp-2.0.0.zip file you just built as an additional release asset. This is the framework-only artifact existing installs download to update — replace their framework/ folder with it and leave app/ untouched.

Step 8: Inspect the built zip

Before attaching it to the release, confirm the zip contains what you expect. At minimum, check that it includes the framework itself plus your release metadata files:
  • VERSION
  • CHANGELOG.md
  • codebase.md
  • RELEASE_MANIFEST.json
If the zip contains only release-safe files, no app/ content, and no local credentials, the build is ready to attach to the release.

What to update by hand

The generated manifest reduces the chance of missing something, but there are still some fields that should be reviewed manually.

Always update by hand

  • summary
  • breaking_changes
  • ai_notes

Usually just review

  • changes
  • route_changes
  • database_changes

Usually leave generated

  • version
  • release_date
  • previous_version
  • generator

If you will forget this later, follow this exact order:
  1. Change framework/VERSION
  2. Run php tools/generate_release_manifest.php
  3. Review and edit RELEASE_MANIFEST.json
  4. Update framework/CHANGELOG.md
  5. Update framework/codebase.md if framework conventions changed
  6. Run php tools/build_release.php
  7. Publish a GitHub Release — GitHub auto-attaches the Source code zip/tarball
  8. Attach dist/frankphp-{version}.zip as an additional release asset
  9. Inspect both before considering the release final
That is the whole workflow.

Final reminder

This feature is meant to make releases more reliable, not more complicated. If you stick to the workflow above, FrankPHP gives you a simple built-in release process with:
  • a real version number
  • a record of what changed
  • basic release history
  • a clean, framework-only packaging step
All without dependencies, external services, or a complex toolchain.