Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion tools/build/base/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,8 @@
# Installed framework core + packages (pulled by `box install` / `box update`).
/vendor/*/
/db/**

# SQLite/H2 database files created at runtime. The db/ directory itself MUST
# ship (it carries db/.keep) so JDBC drivers have a parent dir to write into —
# only the generated database files are ignored.
/db/*.sqlite
/db/*.db
36 changes: 28 additions & 8 deletions tools/build/base/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,31 @@
# wheels-base-template

This is a blank application written in Wheels.dev
A blank, ready-to-boot [Wheels](https://wheels.dev) application.

> **Recommended path:** install the Wheels CLI (Homebrew / Scoop / apt / yum)
> and run `wheels new myapp`. The CLI substitutes app settings for you and is
> the canonical, fully documented workflow. This ForgeBox package exists for
> CommandBox users who prefer `box install`; it ships pre-substituted working
> defaults so it boots with no manual edits.

## As an Application

As an application, this is a starting point for a modern Wheels application with Bootstrap integration.
This is a starting point for a modern Wheels application. It uses the same
scaffold `wheels new` produces, so a `box install`-born app and a CLI-born app
are structurally identical.

## As a ForgeBox Package

As a ForgeBox package there is some interesting things going on here. Although this package doesn't contain much custom code, it does have a dependency which pulls in the core folder the framework needs to function. This folder is pulled in via this dependency:
This package contains the app skeleton only. The framework core is pulled in via
a dependency on `wheels-core`:

```
"Dependencies":{
"wheels-core":"^3.1.0"
"dependencies":{
"wheels-core":"4.0.3"
}
```

The core files are put into the `vendor/wheels/` folder according to these settings.
The core files land in `vendor/wheels/` per these settings:

```
"installPaths":{
Expand All @@ -26,16 +35,27 @@ The core files are put into the `vendor/wheels/` folder according to these setti

## To Install

To install this package you'll need to have a running CommandBox installation. Then you can install this package with the following:
You'll need a running [CommandBox](https://www.ortussolutions.com/products/commandbox)
installation. Then install this package into an empty directory:

```
box
mkdir myapp --cd
install wheels-base-template
```

This could be shortened to a single command run in an empty directory:
This can be shortened to a single command run in an empty directory:

```
box install wheels-base-template
```

Then boot the server (CommandBox reads the bundled `server.json`):

```
box server start
```

`GET /` renders the Wheels welcome page. To reload after config changes, hit
`?reload=true` (the reload password defaults to empty in development — set
`reloadPassword` in `config/settings.cfm` for other environments).
10 changes: 5 additions & 5 deletions tools/build/base/box.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"mvc",
"rails",
"wheels",
"wheels",
"cfml",
"core"
],
"homepage":"https://wheels.dev/",
Expand Down Expand Up @@ -48,9 +48,9 @@
}
],
"scripts":{
"postInstall":"pathExists .env || cp env.example .env && mv env.example .env.example",
"format":"cfformat run config/,controllers/,events/,files/,global/,miscellaneous/,models/,tests/,views/,*.cfc --overwrite",
"format:check":"cfformat check config/,controllers/,events/,files/,global/,miscellaneous/,models/,tests/,views/,*.cfc",
"format:watch":"cfformat watch path='config/,controllers/,events/,files/,global/,miscellaneous/,models/,tests/,views/,*.cfc' settingsPath='.cfformat.json'"
"postInstall":"pathExists .env || cp env.example .env",
"format":"cfformat run config/,app/,tests/,*.cfc --overwrite",
"format:check":"cfformat check config/,app/,tests/,*.cfc",
"format:watch":"cfformat watch path='config/,app/,tests/,*.cfc' settingsPath='.cfformat.json'"
}
}
12 changes: 0 additions & 12 deletions tools/build/base/config/app.cfm

This file was deleted.

29 changes: 0 additions & 29 deletions tools/build/base/config/settings.cfm

This file was deleted.

4 changes: 2 additions & 2 deletions tools/build/base/server.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name":"|appName|",
"name":"Wheels",
"web":{
"host":"localhost",
"webroot": "public",
Expand All @@ -9,7 +9,7 @@
}
},
"app":{
"cfengine":"|cfmlEngine|",
"cfengine":"lucee",
"libDirs":"app/lib"
}
}
175 changes: 144 additions & 31 deletions tools/build/scripts/prepare-base.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,25 @@ set -e
# Prepare script for Wheels Base Template (ForgeBox publishing)
# This script prepares the directory structure without creating ZIP files
# Usage: ./prepare-base.sh <version> <branch> <build_number> <is_prerelease>
#
# SINGLE SOURCE OF TRUTH (Peter, 2026-06-12): the published ForgeBox base
# template is regenerated from cli/lucli/templates/app/ — the canonical
# `wheels new` output — NOT the repo-root demo app. This keeps CommandBox-born
# apps byte-for-byte aligned with CLI-born apps and auto-drops repo-only demo
# cruft (app/jobs/ProcessOrdersJob.cfc, public/ApplicationProxy.cfc,
# public/index.bxm, the dev /cli + /modules mappings in public/Application.cfc).
#
# CommandBox-only build artifacts (server.json, box.json, README.md, the base
# .gitignore, .mcp.json, .opencode.json) are layered on top from
# tools/build/base/ — these are the CommandBox equivalents of what `wheels new`
# users get via lucee.json/rewrite.config/_env.
#
# The LuCLI template ships unsubstituted placeholders ({{appName}},
# {{datasourceName}}, {{reloadPassword}}, {{luceeAdminPassword}}) that the
# `wheels new` installer fills in. `box install` has NO substitution step, so
# this script substitutes them to sane working defaults at BUILD TIME — the
# published artifact carries zero placeholders and `box server start` boots with
# no manual edits.

VERSION=$1
BRANCH=$2
Expand All @@ -12,59 +31,153 @@ IS_PRERELEASE=$4

echo "Preparing Wheels Base Template v${VERSION} for ForgeBox publishing"

# Locate the repo root from this script's location so the build is independent
# of the current working directory.
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)"
TEMPLATE_DIR="${REPO_ROOT}/cli/lucli/templates/app"

# Build-time substitution defaults (the dev defaults `wheels new` would yield).
APP_NAME="Wheels"
CFML_ENGINE="lucee"
DATASOURCE_NAME="wheels"
RELOAD_PASSWORD=""
LUCEE_ADMIN_PASSWORD=""

# Setup directories
BUILD_DIR="build-wheels-base"

if [ ! -d "${TEMPLATE_DIR}" ]; then
echo "ERROR: LuCLI app template not found at ${TEMPLATE_DIR}"
exit 1
fi

# Cleanup and create directories
rm -rf "${BUILD_DIR}"
mkdir -p "${BUILD_DIR}"
echo "Current Working Directory"
pwd
echo "Contents of current directory"
ls -la
echo "Template source: ${TEMPLATE_DIR}"

# Create build label file
BUILD_LABEL="wheels-base-template-${VERSION}-$(date +%Y%m%d%H%M%S)"
echo "Built on $(date)" > "${BUILD_DIR}/${BUILD_LABEL}"

# Copy app scaffold directories from root
echo "Copying app scaffold files..."
cp -r app "${BUILD_DIR}/"
cp -r config "${BUILD_DIR}/"
cp -r db "${BUILD_DIR}/"
cp -r plugins "${BUILD_DIR}/"
cp -r public "${BUILD_DIR}/"
cp -r tests "${BUILD_DIR}/"
# Copy the canonical app scaffold (app/config/db/public/tests/vendor) from the
# LuCLI template — the same tree `wheels new` produces.
echo "Copying app scaffold from LuCLI template..."
cp -r "${TEMPLATE_DIR}/app" "${BUILD_DIR}/"
cp -r "${TEMPLATE_DIR}/config" "${BUILD_DIR}/"
cp -r "${TEMPLATE_DIR}/db" "${BUILD_DIR}/"
cp -r "${TEMPLATE_DIR}/public" "${BUILD_DIR}/"
cp -r "${TEMPLATE_DIR}/tests" "${BUILD_DIR}/"
cp -r "${TEMPLATE_DIR}/vendor" "${BUILD_DIR}/"

# The deprecated plugins/ system is no longer part of a fresh Wheels app; the
# LuCLI template carries app/plugins/ for modern (vendor/) packages instead.
# Keep a webroot-level plugins/ marker only if the template ever ships one.

# Generate the default Main controller + index view. `wheels new` writes these
# post-copy (cli/lucli/Module.cfc) because they are app-specific starter content,
# NOT framework structure — so they don't live in the static template. The root
# route (config/routes.cfm) points to main##index, so without these GET / throws
# Wheels.ViewNotFound. Mirror the CLI exactly so a box-install app and a
# `wheels new` app render the same welcome page.
echo "Generating default Main controller and index view..."
mkdir -p "${BUILD_DIR}/app/views/main"
printf 'component extends="Controller" {\n\n\tfunction index() {\n\t\t// Default action\n\t}\n\n}\n' \
> "${BUILD_DIR}/app/controllers/Main.cfc"
printf '<h1>Welcome to %s</h1>\n<p>Your Wheels application is running. Edit this file at app/views/main/index.cfm</p>\n' \
"${APP_NAME}" > "${BUILD_DIR}/app/views/main/index.cfm"

# Materialize directory-keeper markers so empty scaffold dirs survive packaging.
# CommandBox `package publish` keeps .keep / .gitkeep files, so no rename is
# needed — but normalize .gitkeep -> .keep is intentionally NOT done; both ship.
echo "Ensuring db/ ships with a keeper marker..."
touch "${BUILD_DIR}/db/.keep"

# Translate the LuCLI underscore-prefixed dotfile templates into the CommandBox
# equivalents. _env becomes env.example (box.json's postInstall copies it to
# .env on first install). _gitignore is superseded by the CommandBox-specific
# tools/build/base/.gitignore copied below.
if [ -f "${TEMPLATE_DIR}/_env" ]; then
cp "${TEMPLATE_DIR}/_env" "${BUILD_DIR}/env.example"
fi

mkdir -p "${BUILD_DIR}/vendor"
cp vendor/wheels/.keep "${BUILD_DIR}/vendor/.keep" 2>/dev/null || touch "${BUILD_DIR}/vendor/.keep"
touch "${BUILD_DIR}/vendor/.keep"

# Copy AI documentation files
# Copy AI documentation files from the repo root.
echo "Copying AI documentation..."
cp -r .ai "${BUILD_DIR}/"
cp CLAUDE.md "${BUILD_DIR}/"
cp -r .claude "${BUILD_DIR}/"
cp -r .opencode "${BUILD_DIR}/" 2>/dev/null || true
cp -r "${REPO_ROOT}/.ai" "${BUILD_DIR}/"
cp "${REPO_ROOT}/CLAUDE.md" "${BUILD_DIR}/"
cp -r "${REPO_ROOT}/.claude" "${BUILD_DIR}/"
cp -r "${REPO_ROOT}/.opencode" "${BUILD_DIR}/" 2>/dev/null || true

# Apache 2.0 §4(d) requires NOTICE to propagate to derivatives.
cp LICENSE "${BUILD_DIR}/"
cp NOTICE "${BUILD_DIR}/"
cp "${REPO_ROOT}/LICENSE" "${BUILD_DIR}/"
cp "${REPO_ROOT}/NOTICE" "${BUILD_DIR}/"

# Copy VS Code snippets
echo "Copying VS Code snippets..."
mkdir -p "${BUILD_DIR}/.vscode"
cp .vscode/wheels.code-snippets "${BUILD_DIR}/.vscode/"
cp .vscode/wheels-test.code-snippets "${BUILD_DIR}/.vscode/"

# Copy template files, overwriting defaults
cp tools/build/base/.gitignore "${BUILD_DIR}/.gitignore"
cp tools/build/base/box.json "${BUILD_DIR}/box.json"
cp tools/build/base/README.md "${BUILD_DIR}/README.md"
cp tools/build/base/server.json "${BUILD_DIR}/server.json"
cp tools/build/base/config/app.cfm "${BUILD_DIR}/config/app.cfm"
cp tools/build/base/config/settings.cfm "${BUILD_DIR}/config/settings.cfm"

# Copy .env file
cp .env "${BUILD_DIR}/.env"
cp "${REPO_ROOT}/.vscode/wheels.code-snippets" "${BUILD_DIR}/.vscode/"
cp "${REPO_ROOT}/.vscode/wheels-test.code-snippets" "${BUILD_DIR}/.vscode/"

# Layer the CommandBox-specific build overrides on top. These are the CommandBox
# counterparts of the LuCLI server config (lucee.json/rewrite.config): a
# CommandBox server.json (webroot/cfengine/rewrites pointing at the template's
# public/urlrewrite.xml), the ForgeBox box.json (dependency + installPaths), the
# package README, the base .gitignore (must NOT exclude db/), and editor/MCP
# config.
echo "Applying CommandBox build overrides..."
cp "${REPO_ROOT}/tools/build/base/.gitignore" "${BUILD_DIR}/.gitignore"
cp "${REPO_ROOT}/tools/build/base/box.json" "${BUILD_DIR}/box.json"
cp "${REPO_ROOT}/tools/build/base/README.md" "${BUILD_DIR}/README.md"
cp "${REPO_ROOT}/tools/build/base/server.json" "${BUILD_DIR}/server.json"
cp "${REPO_ROOT}/tools/build/base/.mcp.json" "${BUILD_DIR}/.mcp.json"
cp "${REPO_ROOT}/tools/build/base/.opencode.json" "${BUILD_DIR}/.opencode.json"

# Substitute template placeholders to working defaults so the published artifact
# has NO |tokens| / {{tokens}}. `box install` has no substitution step, so this
# is what makes `box server start` boot with zero manual edits.
#
# The app-level tokens ({{appName}}, {{datasourceName}}, {{reloadPassword}},
# {{luceeAdminPassword}}) appear only in config/app.cfm, config/settings.cfm,
# app/views/layout.cfm and env.example — NOT in app/snippets/*.txt (those carry
# code-gen tokens like {{belongsToRelationships}} that must ship verbatim), so
# substituting these four specific tokens is safe across the whole tree.
#
# The legacy |token| forms come only from the CommandBox overrides (server.json,
# and any historical config copies); substitute them too.
echo "Substituting template placeholders to build defaults..."
substitute_placeholders() {
local file="$1"
sed -i.bak \
-e "s/{{appName}}/${APP_NAME}/g" \
-e "s/{{datasourceName}}/${DATASOURCE_NAME}/g" \
-e "s/{{reloadPassword}}/${RELOAD_PASSWORD}/g" \
-e "s/{{luceeAdminPassword}}/${LUCEE_ADMIN_PASSWORD}/g" \
-e "s/|appName|/${APP_NAME}/g" \
-e "s/|cfmlEngine|/${CFML_ENGINE}/g" \
-e "s/|datasourceName|/${DATASOURCE_NAME}/g" \
-e "s/|reloadPassword|/${RELOAD_PASSWORD}/g" \
"$file" && rm "${file}.bak"
}
# Restrict substitution to config + server + env + the layout view; never touch
# app/snippets/*.txt code-gen templates.
for f in \
"${BUILD_DIR}/config/app.cfm" \
"${BUILD_DIR}/config/settings.cfm" \
"${BUILD_DIR}/config/routes.cfm" \
"${BUILD_DIR}/config/environment.cfm" \
"${BUILD_DIR}/app/views/layout.cfm" \
"${BUILD_DIR}/server.json" \
"${BUILD_DIR}/env.example"; do
if [ -f "$f" ]; then
substitute_placeholders "$f"
fi
done

# Replace version placeholders
echo "Replacing version placeholders..."
Expand Down
Loading
Loading