Quickly run Lucee CFML applications headless (without a HTTP server) via the command line
Please report any issues, etc in the Lucee Issue Tracker
build.xml- Main build file (always use this)build-run-cfml.xml- Internal file (do not run directly)action.yml- GitHub Action configurationsample/- Example CFML files for testing
The script-runner can be used from any directory by specifying the build file location:
# From the script-runner directory (simple)
ant -Dwebroot="/path/to/your/project" -Dexecute="yourscript.cfm"
# From your project directory (recommended for external projects)
ant -buildfile="/path/to/script-runner/build.xml" -Dwebroot="." -Dexecute="yourscript.cfm"
# From any directory with absolute paths
ant -buildfile "C:\tools\script-runner\build.xml" -Dwebroot="C:\work\myproject" -Dexecute="test.cfm"
# execute a script below the webroot
ant -buildfile "C:\tools\script-runner\build.xml" -Dwebroot="C:\work\myproject" -Dexecute="extended/index.cfm"Key Points:
-buildfilespecifies where script-runner is installed-Dwebrootis the directory containing your CFML code (can be relative to current directory)-Dexecuteis the CFML script to run (relative to webroot, no leading slash required)- Note: The script-runner always normalizes the webroot to an absolute path internally, regardless of whether you pass a relative or absolute path. All output and script execution will use this normalized absolute path.
Default ant will run the sample/index.cfm file
Version Query Format: (version)/(stable/rc/snapshot/lpha)/(jar/light/zero).
Examples:
-
7.0/stable/light -
0/stable/jar -
7.0.2/snapshot/zero -
-DluceeJar=- Path to custom Lucee JAR (optional, overrides both luceeVersion and luceeVersionQuery). Example:/full-path/to/lucee.jar, but make sure you use the exact filename for the jar. -
-DluceeVersion=- Lucee version (default:6.2.2.91). Examples:6.2.2.91,light-6.2.2.91,zero-6.2.2.91or7.0/stable/light
New LuceeVersion now also handles Version Query format, luceeVersionQuery is still supported for backwards compat
-DluceeVersionQuery=- Query-based version (optional, overrides luceeVersion).
-Dwebroot=- Directory containing CFML code (default:tests/). On Windows, trailing backslashes (\) will be rejected with an error-Dexecute=- CFML script to run (default:index.cfm). Relative to webroot, no leading/needed-DexecuteScriptByInclude=- Use include instead of _internalRequest (default: false). Set totrueto skip Application.cfc
-Dextensions=- List of extension GUIDs to install (default: empty)-DextensionDir=- Directory containing manual extension files (*.lex) to install (default: empty)-Dcompile=- Compile all CFML under webroot (default: false). Set totrueto enable-DluceeCFConfig=- Path to full .CFConfig.json file for additional Lucee configuration
Lucee deploys to a working directory, default is temp/. By default it clears the directory when it starts and finishes (unless it crashes and exits!).
-DpreCleanup=- Clear Lucee working directory before starting (default: true). Set tofalseto preserve-DpostCleanup=- Clear Lucee working directory after finishing (default: true). Set tofalseto preserve-DuniqueWorkingDir=- Working directory mode:false(default): Usestemp/lucee. One run at a time. Fast, but not for parallel jobstrue: Usestemp-unique/{VERSION}-{TIMESTAMP}-{RANDOM}(timestamp:yyMMdd-HHmmss). Enables concurrent execution/custom/path: Uses your specified directory. You're on your own for cleanup and collisions
These options are good for inspecting after a run, or setting up the /lucee-server/context dir with password.txt or .CFConfig.json.
-Ddebugger=- Enable Java debugger on port 5000 with suspend=y (default: false)-DFlightRecording=- Enable Java Flight Recorder profiling (default: false). Saves.jfrfiles tologs/directory-DFlightRecordingFilename=- Custom output path for JFR recording file-DFlightRecordingSettings=- JFR settings profile (default:profile). Options:default,profile, or path to custom.jfcfile-DjfrExports=- Add JFR module exports for Lucee JFR API access (default: false)
-DjavaAgent=- Path to a Java agent JAR file (e.g., profilers, debuggers like luceedebug)-DjavaAgentArgs=- Arguments passed to the agent (the part after=in-javaagent:path=args)-Djdwp=- Enable JDWP debugging agent with suspend=n (default: false). Required for agents like luceedebug-DjdwpPort=- JDWP port (default: 9999)
-DjvmProperties=- Path to Java properties file to load additional JVM properties-DjvmArgs=- Raw JVM arguments string (e.g.,"-Xmx64m -Xms32m")-DPrintGCDetails=- Enable detailed garbage collection logging (default: false)-DUseEpsilonGC=- Enable Epsilon no-op garbage collector for testing (default: false). Also enables AlwaysPreTouch-DPrintInlining=- Enable JVM compilation diagnostics (default: false). Enables PrintInlining, PrintCompilation, and UnlockDiagnosticVMOptions
Enable JFR to capture detailed performance data during script execution:
- Java Flight Recorder
-DFlightRecording="true"enables JFR profiling, saves .jfr files tologs/directory - JFR module access
-DjfrExports="true"adds--add-exportsand--add-opensforjdk.jfrmodule (for Lucee JFR API access) - Custom JFR filename
-DFlightRecordingFilename="/path/to/output.jfr"specify custom output path for JFR recording
ant -DFlightRecording=true -Dwebroot="." -Dexecute="yourscript.cfm"What it does:
- Creates JFR recording files in
logs/{timestamp}-j{java.version}.jfr - Captures CPU usage, memory allocation, garbage collection, thread activity
- Settings: disk=true, dumponexit=true, maxsize=1024m, maxage=1d, settings=profile, path-to-gc-roots=true, stackdepth=128
Custom JFR output path:
ant -DFlightRecording=true -DFlightRecordingFilename="D:/my-logs/custom.jfr" -Dwebroot="." -Dexecute="yourscript.cfm"JFR API access for Lucee:
If you need Lucee to access JFR APIs directly (not just record), add the JFR module exports:
ant -DjfrExports=true -Dwebroot="." -Dexecute="yourscript.cfm"This adds --add-exports=jdk.jfr/jdk.jfr=ALL-UNNAMED and --add-opens=jdk.jfr/jdk.jfr=ALL-UNNAMED to allow Lucee code to use the JFR API.
Analyzing JFR files:
# Print summary
jfr print logs/250101-120530-j21.jfr
# Print specific events
jfr print --events CPULoad,GarbageCollection logs/250101-120530-j21.jfr
# Convert to JSON
jfr print --json logs/250101-120530-j21.jfr > output.jsonThe jfr command-line tool is included in the JDK bin directory. For visual analysis, use JDK Mission Control (JMC) or import into profiling tools.
To run with a Java agent like luceedebug:
ant -buildfile "D:\work\script-runner" ^
-Djdwp="true" ^
-DjdwpPort="9999" ^
-DjavaAgent="D:\path\to\luceedebug-2.0.15.jar" ^
-DjavaAgentArgs="jdwpHost=localhost,jdwpPort=9999,debugHost=0.0.0.0,debugPort=10000,jarPath=D:\path\to\luceedebug-2.0.15.jar" ^
-Dwebroot="D:\my\cfml\app" ^
-Dexecute="index.cfm"Note: The agent's jarPath argument must match the -DjavaAgent path exactly.
To profile luceedebug overhead with JFR:
ant -buildfile "D:\work\script-runner" ^
-Djdwp="true" ^
-DjavaAgent="D:\path\to\luceedebug.jar" ^
-DjavaAgentArgs="jdwpHost=localhost,jdwpPort=9999,debugHost=0.0.0.0,debugPort=10000,jarPath=D:\path\to\luceedebug.jar" ^
-DFlightRecording="true" ^
-DFlightRecordingFilename="D:\output\luceedebug-profile.jfr" ^
-Dwebroot="." ^
-Dexecute="benchmark.cfm"If your path has spaces, use quotes. If not, don’t.
Quick Reference:
| Shell | Example Command |
|---|---|
| PowerShell | ant -buildfile "C:\tools\script-runner\build.xml" -Dwebroot="C:\work\my project" -Dexecute="test.cfm" -DuniqueWorkingDir=true |
| Command Prompt | ant -buildfile "C:\tools\script-runner\build.xml" -Dwebroot="C:\work\myproject" -Dexecute="test.cfm" -DuniqueWorkingDir=true |
| Bash/WSL | ant -buildfile /mnt/d/work/script-runner/build.xml -Dwebroot=/mnt/d/work/myproject -Dexecute=test.cfm -DuniqueWorkingDir=true |
PowerShell: Use double quotes for paths with spaces. Single quotes also work (especially in scripts to avoid variable expansion). Don’t mix and match.
Command Prompt: Quotes only if the path has spaces. You can quote just the value or the whole parameter (the latter is handy in batch files).
Bash/WSL: Use forward slashes. Quotes only if the path has spaces. Don’t use Windows backslashes or drive letters.
Blunt Warnings:
- Don’t use trailing backslashes on Windows. Ever.
- Don’t escape quotes unless you like pain.
- If Ant can’t find your file, your path or quotes are wrong. Period.
# Testing Lucee Spreadsheet from its directory
cd D:\work\lucee-spreadsheet
ant -buildfile "D:\work\script-runner\build.xml" -Dwebroot=. -Dexecute=/test/index.cfm
# Testing with specific Lucee version
ant -buildfile "D:\work\script-runner\build.xml" -DluceeVersionQuery=6.2/stable/jar -Dwebroot="D:\work\lucee-spreadsheet" -Dexecute=/test/index.cfm
# Testing with a locally built Lucee JAR (for Lucee developers)
ant -buildfile "D:\work\script-runner\build.xml" -DluceeJar="D:\work\lucee\loader\target\lucee.jar" -Dwebroot="D:\work\lucee-spreadsheet" -Dexecute=/test/index.cfm
# With unique working directory for concurrent runs
ant -buildfile "D:\work\script-runner\build.xml" -DuniqueWorkingDir=true -Dwebroot="D:\work\lucee-spreadsheet" -Dexecute=/test/index.cfmDefault Mode (uniqueWorkingDir=false or not specified):
- Uses a consistent local
temp/luceedirectory relative to script-runner location - Same directory is reused across runs (cleaned with
preCleanup/postCleanup) - Ideal for CI/CD: Predictable location, faster subsequent runs due to caching
- Single instance only: Cannot run multiple concurrent instances
Auto-Unique Mode (uniqueWorkingDir=true):
- Creates unique directories:
temp-unique/{VERSION}-{TIMESTAMP}-{RANDOM}(timestamp format:yyMMdd-HHmmss) - Each run gets its own isolated working directory
- Enables concurrent execution: Multiple instances can run simultaneously
- Useful for: Parallel testing, concurrent builds, isolation requirements
Custom Path Mode (uniqueWorkingDir=/custom/path):
- Uses your specified directory as the working directory
- Full control over location (e.g., RAM disk, specific drive, shared folder)
- Race protection: Still checks for existing directory to prevent conflicts
- Useful for: Custom environments, performance optimization, specific storage requirements
# Examples of the three modes:
ant -DuniqueWorkingDir=false # Uses: temp/lucee
ant -DuniqueWorkingDir=true # Uses: temp-unique/6.2.2.91-250913-142530-123
ant -DuniqueWorkingDir=C:/fast/work # Uses: C:/fast/workMultiple script-runner instances can be run simultaneously using unique working directories:
# Run multiple instances concurrently
ant -DuniqueWorkingDir="true" -Dexecute="test1.cfm" &
ant -DuniqueWorkingDir="true" -Dexecute="test2.cfm" &
ant -DuniqueWorkingDir="true" -Dexecute="test3.cfm" &
waitEach instance will use a unique working directory named temp-unique/{VERSION}-{TIMESTAMP}-{RANDOM} (timestamp format: yyMMdd-HHmmss) to prevent conflicts.
When running CFML scripts in headless mode (without a web server), you should use systemOutput() instead of writeOutput() to see output in the console:
// ✅ Correct - outputs to console in headless mode
systemOutput("Processing started...", true); // true adds newline
systemOutput("Item #i# processed", true);
// ❌ Wrong - writeOutput() won't display in console
writeOutput("This won't be visible");
// For debugging, you can also use:
systemOutput(serializeJSON(myData, "struct"), true);Key Points:
systemOutput()writes directly to the console (stdout)writeOutput()is for HTTP response output and won't show in headless mode- The second parameter
trueadds a newline after the output - Use
systemOutput()for progress updates, debugging, and results
Problem: "Could not locate build file" from other directories Solution: Use absolute path to build.xml:
# ✅ Correct - specify script-runner location
ant -buildfile "C:\tools\script-runner\build.xml" -Dwebroot="." -Dexecute="test.cfm"
# ❌ Wrong - looking for build.xml in current directory
ant -Dwebroot="." -Dexecute="/test.cfm"
Problem: "File not found" for CFML scripts Solution: Check webroot and execute paths:
# Verify your paths - execute is relative to webroot
ant -buildfile="/path/to/script-runner/build.xml" -Dwebroot="/your/project" -Dexecute="debug.cfm"Problem: "No shell found" or quote/escape errors on Windows Solution: Use proper quote formatting for Windows command line:
# ✅ Correct - Windows Command Prompt (no quotes needed for paths without spaces)
ant -buildfile "d:\work\script-runner\build.xml" -Dwebroot="D:\work\project" -Dexecute="test.cfm"
# ✅ Correct - Windows with spaces in paths
ant -buildfile "C:\Program Files\script-runner\build.xml" -Dwebroot="C:\My Projects\test" -Dexecute="test.cfm"
# ✅ Correct - PowerShell (single quotes work too, avoid variable expansion)
ant -buildfile 'd:\work\script-runner\build.xml' -Dwebroot 'D:\work\project' -Dexecute 'test.cfm'
# ❌ Wrong - excessive escaping or nested quotes
ant -buildfile=\"d:\work\script-runner\" -Dwebroot=\"D:\work\project\"Important Windows Tips:
- When using
-buildfilewith a directory, add\build.xmlexplicitly - Avoid escaped quotes (
\") - they're usually not needed - Use forward slashes (
/) or double backslashes (\\) in scripts to avoid escape issues - In batch files, use
%%instead of%for variables
If no webroot is specfied, you can run the provided debug script, to see which extensions are available and all the env / sys properties
ant -buildfile "C:\work\script-runner\build.xml" -Dexecute="debug.cfm"
# With light version (no bundled extensions) or zero version (no extension or admin)
ant -buildfile "C:\work\script-runner\build.xml" -Dexecute="debug.cfm" -DluceeVersion="light-6.2.2.91"To use as a GitHub Action, to run the PDF tests after building the PDF Extension, just add the following YAML
- name: Checkout Lucee
uses: actions/checkout@v2
with:
repository: lucee/lucee
path: lucee
- name: Cache Maven packages
uses: actions/cache@v3
with:
path: ~/.m2
key: lucee-script-runner-maven-cache
- name: Cache Lucee files
uses: actions/cache@v3
with:
path: _actions/lucee/script-runner/main/lucee-download-cache
key: lucee-downloads
- name: Run Lucee Test Suite
uses: lucee/script-runner@main
with:
webroot: ${{ github.workspace }}/lucee/test
execute: bootstrap-tests.cfm
luceeVersion: ${{ env.luceeVersion }}
luceeVersionQuery: 5.4/stable/light (optional, overrides luceeVersion )
luceeJar: /path/to/local/lucee.jar (optional, overrides both luceeVersion and luceeVersionQuery)
extensions: (optional list of extension guids to install)
extensionDir: ${{ github.workspace }}/dist (for testing building an extension with CI)
antFlags: -d or -v etc (optional, good for debugging any ant issues)
compile: true (optional, compiles all the cfml under the webroot)
luceeCFConfig: /path/to/.CFConfig.json pass in additional configuration
debugger: true (optional) runs with java debugging enabled on port 5000
preCleanup: true (purges Lucee working directory before starting)
postCleanup: true (purges Lucee working directory after finishing)
uniqueWorkingDir: true (optional) uses unique working directory for concurrent execution
FlightRecording: true (optional) enables Java Flight Recorder profiling
FlightRecordingFilename: /path/to/output.jfr (optional) custom JFR output path
FlightRecordingSettings: profile (optional) JFR settings profile (default/profile/custom.jfc)
jfrExports: true (optional) adds JFR module exports for Lucee JFR API access
javaAgent: /path/to/agent.jar (optional) Java agent JAR path
javaAgentArgs: agent=args (optional) Java agent arguments
jdwp: true (optional) enables JDWP debugging agent (suspend=n)
jdwpPort: 9999 (optional) JDWP port (default: 9999)
jvmProperties: /path/to/jvm.properties (optional) Java properties file path
jvmArgs: "-Xmx64m -Xms32m" (optional) raw JVM arguments
PrintGCDetails: true (optional) enables detailed GC logging
UseEpsilonGC: true (optional) enables Epsilon no-op garbage collector
PrintInlining: true (optional) enables JVM compilation diagnostics
env:
testLabels: pdf
testAdditional: ${{ github.workspace }}/testsGitHub Action Workflow Example
This will do the following steps
- checkout a copy of the Lucee Code base
- install any extension(s) (
*.lex) found in${{ github.workspace }}/dist - run all tests with the label of "pdf"
- run any additional tests found in the
/testsdirectory of the current repository
image: atlassian/default-image:3
pipelines:
default:
- step:
name: Build and Test
caches:
- maven
script:
- ant -noinput -verbose -buildfile build.xml
artifacts:
- dist/**
- step:
name: Checkout Lucee Script-runner, Lucee and run tests
script:
- git clone https://github.com/lucee/script-runner
- git clone https://github.com/lucee/lucee
- export testLabels="PDF"
- echo $testLabels
- ant -buildfile script-runner/build.xml -DluceeVersion="light-6.2.2.91" -Dwebroot="$BITBUCKET_CLONE_DIR/lucee/test" -DextensionDir="$BITBUCKET_CLONE_DIR/dist" -Dexecute="bootstrap-tests.cfm" -DtestAdditional="$BITBUCKET_CLONE_DIR/tests"