BoxLang 🚀 A New JVM Dynamic Language Learn More...

build-template

v2.0.0 CommandBox Modules

build-template

Build Template Logo

build-template is a CommandBox module that releases CFML projects. Install it once on each machine and it gives you a release namespace that can:

  • run your TestBox tests;
  • build and check a release zip; and
  • publish the package to ForgeBox and GitHub.

Each project keeps only a small build.json. You never copy the kit into a repository, and one box update brings every project on the machine up to date.

How a release works

The normal release process has four parts:

  1. Write a short description of your changes under [Unreleased] in CHANGELOG.md.
  2. Run box release bump to update the version and date those notes.
  3. Check the project and rehearse the release.
  4. Run the real release.

The release stops if it finds a problem, such as uncommitted changes, a missing changelog entry, failed tests, or a version that has already been released.

Before you install

Every machine needs:

Depending on how a project is configured, you may also need:

  • GitHub CLI for GitHub Releases;
  • a ForgeBox account for ForgeBox publishing; and
  • a running test server when runTests is true.

Sign in to the services you use:

gh auth login
box forgebox login

Install

box install build-template

That installs the module into CommandBox itself, so the commands are available in every project. Then, in a project's root folder:

box release init

The installer:

  • writes build.json with settings detected from your project; and
  • creates CHANGELOG.md if the project does not have one.

Add --docs to copy the detailed RELEASE.md guide into the project, and --ci to copy a GitHub Actions workflow to .github/workflows/release.yml. Rerunning it is safe: existing files are kept unless you pass --force. Afterwards, review build.json and correct anything the installer could not detect, especially the test runner URL and release branch.

Update

box update build-template --system

Every project on the machine uses the updated kit at once. A project can insist on a newer kit with minimumKitVersion in build.json; a machine with an older kit is told to update instead of releasing with the wrong behaviour.

Moving from 1.x

Projects that copied the 1.x build folder keep working until you migrate them: the old build/build.json still loads, with a notice. To move a project over:

box release migrate --dryRun
box release migrate

This moves build/build.json to build.json, deletes the kit's own files from build/ (anything else in there is kept), and rewrites the 1.x box run-script entries in box.json to the new commands. Review with git diff, then commit. Add --removeScripts to delete the old scripts instead of rewriting them.

Your first release

This example assumes the current version is 1.0.0 and you are releasing 1.0.1.

1. Write the release notes

Add a useful line under ## [Unreleased] in CHANGELOG.md:

## [Unreleased]

### Fixed

- Fixed the login form validation.

These notes become the body of the GitHub Release.

2. Update the version

For a bug fix, run:

box release bump patch

This changes the version in box.json from 1.0.0 to 1.0.1 and moves the notes into a dated 1.0.1 section.

Use a different level when needed:

box release bump minor    # 1.0.0 -> 1.1.0 for a new feature
box release bump major    # 1.0.0 -> 2.0.0 for a breaking change

3. Review and commit the changes

git status
git diff
git add box.json CHANGELOG.md
git diff --staged
git commit -m "Release 1.0.1"

If your changelog has a different filename, use that filename in the git add command. The commit is only local until you send it to the remote with git push.

Using GitKraken or another Git GUI? Review the changed box.json and changelog, stage only those release files, review the staged changes, commit them as Release 1.0.1, and then push the current branch. Those are the GUI equivalents of the commands above.

4. Check that the project is ready

Start the project's test server if tests are enabled, then run:

box release check

This command changes nothing. It checks the installed kit, the settings, the Git repository, the changelog, the required tools, the service logins, and the test server. If something is wrong, it prints what to fix.

5. Rehearse the release

box release run --dryRun

The dry run executes the checks, tests, and package build, but does not publish, tag, or push anything.

6. Publish

box release run

The release:

  1. checks the project;
  2. fast-forwards the configured production branch from its Git remote;
  3. runs the tests and builds a verified zip;
  4. publishes to ForgeBox when enabled; and
  5. creates the Git tag and GitHub Release when enabled.

The finished zip and checksum are saved under .artifacts/.

Commands

Run these from anywhere inside a project. box release help prints the same list.

Command Use it to
box release check Find anything that would stop a release.
box release run --dryRun Rehearse a release without publishing.
box release run Build and publish the current version.
box release run --existingTag Publish a tag already created at the checked-out commit by Gitflow or GitKraken. Pushes the tag first if origin does not have it yet.
box release run --skipTests Publish without rerunning tests that were already completed.
box release bump patch Release a backward-compatible bug fix.
box release bump minor Release a backward-compatible feature.
box release bump major Release a breaking change.
box release bump preminor beta Start a prerelease, for example 1.1.0-beta.1.
box release package Build and check the zip without publishing it.
box release engines Run the test suite on each configured CFML engine.
box release notes Show the release notes for the current version.
box release github Finish a release that stopped after publishing.
box release init Set a project up: build.json and CHANGELOG.md.
box release migrate Move a project off the 1.x copied build folder.

Full help for any command: box help release run.

Common settings

Edit build.json in the project root to change how the commands work. The installer creates this file with values detected from your project and a complete package exclusion list.

Choose the production branch

branch is the branch that receives release tags and published versions, normally main or master. In a Gitflow repository it is the production branch, never develop or a temporary release/* branch. The installer reads Gitflow's configured production branch when available, but you should still verify the generated value.

{
    "branch": "main"
}

Require a kit version

{
    "minimumKitVersion": "2.0.0"
}

The installer writes the version it was run with. Raise it when the project starts depending on a newer kit feature.

Publish to GitHub but not ForgeBox

{
    "publish": {
        "forgebox": false,
        "github": true
    }
}

Build an application instead of a module

An application still gets a versioned zip and can still get a GitHub Release:

{
    "projectType": "app",
    "publish": {
        "forgebox": false,
        "github": true
    }
}

Do not run tests during the build

Use this when another system, such as CI, is responsible for running the tests:

{
    "runTests": false
}

Keep extra files out of the package

excludes is the complete list of regular expressions matched against top-level files and folders. A module starts with broad packaging defaults: build and test tooling, downloaded dependencies, server definitions, editor workspaces, agent notes, archives, and hidden files stay out. An application gets a narrower list: possible deployment content such as modules, resources, package manifests, .htaccess, and .well-known remains available.

Edit excludes when you need to change that baseline. Use excludesAdd for project-specific additions that should sit on top of it. For example, this keeps the top-level docs folder out of the package:

{
    "excludesAdd": [
        "^docs$"
    ]
}

Use double backslashes when a regular expression needs a backslash because the value is JSON.

Test more than one CFML engine

During release init, server.json and every server-*.json file in the project root are added here in filename order. Nested server files are deliberately ignored. A readable name comes from app.cfengine, then the server's name, then its filename; review the generated list and remove any server that is not part of your compatibility suite.

Each configFile remains a CommandBox server JSON file in the project root:

{
    "engines": [
        {
            "name": "Lucee 5",
            "configFile": "[email protected]"
        },
        {
            "name": "Adobe 2023",
            "configFile": "[email protected]"
        }
    ]
}

Run the configured list with:

box release engines

The engines run one at a time. Every engine still runs after a failure. The final report lists all results, and the command returns an error when any engine failed.

Common problems

Message What to do
Command "release" cannot be resolved The module is not installed in this CommandBox. Run box install build-template.
No box.json found Run the command from inside a CommandBox project.
This project needs build-template X or newer Run box update build-template --system.
You have uncommitted changes Commit or stash the changes, then run the command again.
No answer from the test server Start the project's test server, check testRunner, or turn off runTests if tests run elsewhere.
Could not find the GitHub CLI Install gh, open a new terminal, and run gh auth login.
has no "## [version]" section Add notes under [Unreleased], then run box release bump.
Tag v1.2.3 already exists That version has already been released. Bump the version before trying again.
Tag v1.2.3 is on origin at a different commit Your local tag and the published tag disagree. Do not move the published tag; check the release history or choose a new version.
build.json is not valid JSON Check for missing quotes, trailing commas, or backslashes that need to be doubled.

Start with box release check when you are unsure. It reports all readiness problems without changing the project.

Develop the build kit

Install the development dependencies and run the tests:

box install
box run-script test

The test runner loads this checkout as the build-template module inside its own CommandBox, so the specs always exercise the working copy, even when a released copy is installed globally. Unit tests cover the version, changelog, configuration, project-detection, and migration rules. Integration tests create throwaway projects under the ignored .test-work/ folder and run the real commands in them through tests/support/Invoke.cfc, with a local Git remote. The tests never publish to ForgeBox or GitHub.

To try the working copy as a real install, run box install <path to this checkout> and open a new shell; box release help should list the commands. The kit releases itself with its own box release run.

The commands in commands/release/ stay thin. The work happens in models/, and pure rules that do not need CommandBox live in their own components there so they are easy to test.

More information

Changelog

All notable changes to this project are written down here.

The format follows Keep a Changelog, and the version numbers follow Semantic Versioning.

[Unreleased]

[2.0.0] - 2026-09-13

Changed

  • build-template is now a CommandBox module installed once per machine with box install build-template, instead of a build folder copied into every project. The tasks became commands in the release namespace: box release run, release check, release bump <level>, release package, release engines, release init, release notes, release github, and release migrate. box release help lists them.
  • Project settings live in build.json in the project root. The 1.x build/build.json still loads, with a notice, until release migrate moves it.
  • minimumKitVersion in build.json makes every machine refuse to release with an older kit and prints the update command, so a project behaves the same wherever it is released.
  • Commands find the project from any folder inside it.
  • Updating the kit is box update build-template --system.

Removed

  • The vendored build folder, Update.cfc, build-kit.json, templateVersion, and the installer-written box.json scripts. release init no longer writes scripts; release migrate rewrites the 1.x ones to the new commands so box run-script release keeps working.

Migration from 1.x

  1. box install build-template
  2. In each project: box release migrate --dryRun, then box release migrate, review the changes, commit.
  3. In CI, install the module before the release step. See templates/github-release.yml.

[1.5.0] - 2026-09-13

Added

  • build-kit:update (build/Update.cfc) brings a project's copy of the kit up to date. It downloads the latest build-template release, or takes :source=<folder or zip>, replaces the kit files under build/ without touching build/build.json, adds new box.json scripts, records the kit version, and prints the template changelog since the version the project was on. :version= picks a release and :dryRun=true only lists the changes.
  • build/build-kit.json records the kit version and repository. The installer now stamps the real kit version into templateVersion instead of 1.0.0.
  • release:check reports whether the release tag is already on origin.

Changed

  • release:existing-tag checks origin during preflight, pushes a tag that exists only locally right before creating the GitHub Release, and refuses when origin holds the tag at a different commit. A tag left unpushed after a Gitflow finish used to fail the release at its last step, after the ForgeBox publish.
  • The box.json script list moved into build/lib/PackageScriptService.cfc, shared by the install and update tasks.

[1.4.2] - 2028-08-10

Changed

  • bump:beta, bump:alpha, and direct preminor calls now refuse to retarget an active prerelease unless :allowPrereleaseRetarget=true is supplied explicitly.

[1.4.1] - 2028-08-07

  • Updated logo

[1.4.0] - 2028-08-07

Added

  • A TestBox suite for version rules, changelog parsing, project settings, public task APIs, multi-engine control flow, installation, package builds, and safe release dry runs.

Changed

  • All build task components now use direct names, smaller workflow functions, and plain-language documentation for developers who are new to the project.
  • Version, changelog, and project-detection rules now live in small internal services under build/lib/.
  • Multi-engine documentation now states that every configured engine runs before the command reports all failures.

[1.3.0] - 2028-08-03

Added

  • release:existing-tag for publishing a release tag created while finishing a Gitflow release in tools such as GitKraken.

Changed

  • The Gitflow guide now makes the branch transitions explicit: create the release branch first, bump and commit on that branch, then finish into production and develop.
  • Git command sequences now include their equivalent review, stage, commit, and push workflow for people using GitKraken or another Git GUI.
  • GitKraken has its own finish path and safe guidance for intentional, stale, and previously published tags.

[1.2.0] - 2026-07-31

Added

  • A Gitflow release cheat sheet covering plain Git, pull-request, git-flow, and hotfix paths.
  • Existing-tag release mode for publishing tags created by Gitflow or tag-triggered CI.
  • release:skip-tests as the clear name for the existing skip-tests release behavior.

Changed

  • Release dry runs can rehearse non-production branches with a warning, while real releases remain restricted to the configured production branch.
  • Release synchronization is fast-forward-only, and normal releases now reject tags that already exist locally or on the remote.
  • Installation prefers Gitflow's configured production branch, and Git guidance now uses explicit review, staging, and commit steps for newer Git users.
  • The optional GitHub Actions workflow can publish an existing tag and requests only the repository-content permission needed to create the release.

[1.1.0] - 2026-07-30

Added

  • New installations write complete, project-specific exclusion defaults into build/build.json. Module packages get the broad ColdBox-style development exclusions; applications preserve deployment content such as modules, .htaccess, and .well-known.
  • Installation discovers both server.json and root-level server-*.json files in stable filename order, naming them from app.cfengine, the server name, or the filename.

Changed

  • The shipped build.json is now a marked starter that a fresh install replaces automatically. Existing unmarked and malformed configurations remain untouched unless installation is run with :force=true.

[1.0.0] - 2026-07-29

Added

  • First version of the build kit: Build, Release, Bump, TestEngines, Install, and Doctor, sharing one settings file at build/build.json.
  • box run-script release runs the whole release: checks, remote sync, tests, build, ForgeBox publish, git tag, and a GitHub Release with the changelog notes and the zip attached.
  • release:check reports whether a project is ready to release and prints the fix for anything that is not.
  • release:dryrun rehearses a release and publishes nothing.
  • release:hotfix and :skipTests=true skip the test suite, with a warning.
  • test:engines runs the suite on every engine in turn, stopping at the first failure and naming the engines that already passed.
  • bump:major, bump:minor, and bump:patch raise the version and move [Unreleased] notes into a dated section. Prereleases follow SemVer, so finishing 1.2.0-beta.3 with bump:patch gives 1.2.0 rather than skipping to 1.2.1.
  • bump:beta, bump:alpha, and bump:prerelease start and step prereleases, with a :preid argument for other labels.
  • Install.cfc sets a project up in one command, working out the test runner from box.json and the engine list from the server-*.json files in the project root.
  • Packages are checked before they ship: the build counts the files it staged against the files in the zip and stops on any difference.
  • Packages are stamped with the short git commit hash they were built from.
  • excludesAdd appends to the default exclude list, so keeping one extra file out of a package is a one line change.
  • An optional GitHub Actions workflow in build/templates/.

$ box install build-template

No collaborators yet.
     
  • {{ getFullDate("2026-09-13T10:19:29Z") }}
  • {{ getFullDate("2026-09-13T10:19:30Z") }}
  • 6
  • 1