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
1 change: 1 addition & 0 deletions changelog.d/plugins-dir-optional.fixed.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
- The legacy `plugins/` directory is now optional. The scaffold's `Application.cfc` jar-scan and the framework plugin loader (`Plugins.cfc` `$folders()`/`$files()`) now guard their directory listing with `DirectoryExists`, so an app that has removed `plugins/` (the common case now that packages live in `vendor/<name>/`) no longer errors at startup on engines whose directory listing throws on a missing path — Lucee/Adobe tolerate a missing dir, but stricter engines (e.g. RustCFML) did not. The plugins-directory lookup is deprecated and slated for removal in the next major
13 changes: 7 additions & 6 deletions cli/lucli/templates/app/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,14 @@ component output="false" {
this.sessionManagement = true;

// If a plugin has a jar or class file, automatically add the mapping to this.javasettings.
// Legacy plugins system (DEPRECATED — superseded by vendor/<name>/ packages).
// Only scan when a plugins/ directory exists, so a removed plugins/ dir does not
// error on engines whose directoryList() throws on a missing path (e.g. RustCFML;
// Lucee tolerates it). This lookup is slated for removal in the next major.
this.wheels.pluginDir = this.appDir & "../plugins";
this.wheels.pluginFolders = DirectoryList(
this.wheels.pluginDir,
"true",
"path",
"*.class|*.jar|*.java"
);
this.wheels.pluginFolders = DirectoryExists(this.wheels.pluginDir)
? DirectoryList(this.wheels.pluginDir, "true", "path", "*.class|*.jar|*.java")
: [];

for (this.wheels.folder in this.wheels.pluginFolders) {
if (!StructKeyExists(this, "javaSettings")) {
Expand Down
13 changes: 7 additions & 6 deletions examples/starter-app/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ component output="false" {
this.sessionManagement = true;

// If a plugin has a jar or class file, automatically add the mapping to this.javasettings.
// Legacy plugins system (DEPRECATED — superseded by vendor/<name>/ packages).
// Only scan when a plugins/ directory exists, so a removed plugins/ dir does not
// error on engines whose directoryList() throws on a missing path (e.g. RustCFML;
// Lucee tolerates it). This lookup is slated for removal in the next major.
this.wheels.pluginDir = this.appDir & "../plugins";
this.wheels.pluginFolders = DirectoryList(
this.wheels.pluginDir,
"true",
"path",
"*.class|*.jar|*.java"
);
this.wheels.pluginFolders = DirectoryExists(this.wheels.pluginDir)
? DirectoryList(this.wheels.pluginDir, "true", "path", "*.class|*.jar|*.java")
: [];

for (this.wheels.folder in this.wheels.pluginFolders) {
if (!StructKeyExists(this, "javaSettings")) {
Expand Down
13 changes: 7 additions & 6 deletions examples/tweet/public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -26,13 +26,14 @@ component output="false" {
this.sessionManagement = true;

// If a plugin has a jar or class file, automatically add the mapping to this.javasettings.
// Legacy plugins system (DEPRECATED — superseded by vendor/<name>/ packages).
// Only scan when a plugins/ directory exists, so a removed plugins/ dir does not
// error on engines whose directoryList() throws on a missing path (e.g. RustCFML;
// Lucee tolerates it). This lookup is slated for removal in the next major.
this.wheels.pluginDir = this.appDir & "../plugins";
this.wheels.pluginFolders = DirectoryList(
this.wheels.pluginDir,
"true",
"path",
"*.class|*.jar|*.java"
);
this.wheels.pluginFolders = DirectoryExists(this.wheels.pluginDir)
? DirectoryList(this.wheels.pluginDir, "true", "path", "*.class|*.jar|*.java")
: [];

for (this.wheels.folder in this.wheels.pluginFolders) {
if (!StructKeyExists(this, "javaSettings")) {
Expand Down
13 changes: 7 additions & 6 deletions public/Application.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -37,13 +37,14 @@ component output="false" {
this.sessionManagement = true;

// If a plugin has a jar or class file, automatically add the mapping to this.javasettings.
// Legacy plugins system (DEPRECATED — superseded by vendor/<name>/ packages).
// Only scan when a plugins/ directory exists, so a removed plugins/ dir does not
// error on engines whose directoryList() throws on a missing path (e.g. RustCFML;
// Lucee tolerates it). This lookup is slated for removal in the next major.
this.wheels.pluginDir = this.appDir & "../plugins";
this.wheels.pluginFolders = DirectoryList(
this.wheels.pluginDir,
"true",
"path",
"*.class|*.jar|*.java"
);
this.wheels.pluginFolders = DirectoryExists(this.wheels.pluginDir)
? DirectoryList(this.wheels.pluginDir, "true", "path", "*.class|*.jar|*.java")
: [];

for (this.wheels.folder in this.wheels.pluginFolders) {
if (!StructKeyExists(this, "javaSettings")) {
Expand Down
12 changes: 12 additions & 0 deletions vendor/wheels/Plugins.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -1041,6 +1041,14 @@ component output="false" extends="wheels.Global"{
}

public query function $folders() {
// The legacy plugins/ directory is deprecated (superseded by vendor/<name>/
// packages) and may be absent. Skip the scan when it does not exist so
// engines whose directory listing throws on a missing path (e.g. RustCFML)
// don't fail at boot; Lucee/Adobe return empty for a missing dir anyway.
// Slated for removal with the plugins system in the next major.
if (!DirectoryExists(variables.$class.pluginPathFull)) {
return QueryNew("name,directory,type");
}
local.query = $directory(
action = "list",
directory = variables.$class.pluginPathFull,
Expand Down Expand Up @@ -1086,6 +1094,10 @@ component output="false" extends="wheels.Global"{
}

public query function $files() {
// See $folders(): the deprecated plugins/ directory may be absent.
if (!DirectoryExists(variables.$class.pluginPathFull)) {
return QueryNew("name,directory,type");
}
local.query = $directory(
action = "list",
directory = variables.$class.pluginPathFull,
Expand Down
67 changes: 67 additions & 0 deletions vendor/wheels/tests/specs/pluginsMissingDirSpec.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
component extends="wheels.WheelsTest" {

function run() {

g = application.wo

// The legacy plugins/ directory is deprecated (superseded by vendor/<name>/
// packages) and apps are expected to remove it. The plugin loader must not
// error when it is absent — Lucee/Adobe return empty for a missing dir, but
// stricter engines (e.g. RustCFML) throw on directory listing of a missing
// path, which previously failed onApplicationStart. $folders()/$files() now
// short-circuit to an empty query when the directory does not exist.
describe("plugin loader with an absent plugins/ directory", () => {

missingPath = "/wheels/tests/_assets/plugins/__this_directory_does_not_exist__"

it("initializes without throwing when the plugins directory is missing", () => {
var config = {
path = "wheels",
fileName = "Plugins",
method = "$init",
pluginPath = missingPath,
deletePluginDirectories = false,
overwritePlugins = false,
loadIncompatiblePlugins = true
}
var state = {thrown = false}
try {
pluginObj = $pluginObj(config)
} catch (any e) {
state.thrown = true
}
expect(state.thrown).toBeFalse()
})

it("$folders() returns an empty query for a missing directory", () => {
var config = {
path = "wheels", fileName = "Plugins", method = "$init", pluginPath = missingPath,
deletePluginDirectories = false, overwritePlugins = false, loadIncompatiblePlugins = true
}
var pluginObj = $pluginObj(config)
expect(pluginObj.$folders().recordCount).toBe(0)
})

it("$files() returns an empty query for a missing directory", () => {
var config = {
path = "wheels", fileName = "Plugins", method = "$init", pluginPath = missingPath,
deletePluginDirectories = false, overwritePlugins = false, loadIncompatiblePlugins = true
}
var pluginObj = $pluginObj(config)
expect(pluginObj.$files().recordCount).toBe(0)
})
})
}

// Mirror the sibling plugin specs (pluginsSpec.cfc:549, pluginsModernSpec,
// pluginsSemverSpec, pluginsManifestIntegrationSpec): a component-level
// helper that instantiates wheels.Plugins via $createObjectFromRoot and
// dispatches $init with the full config — INCLUDING pluginPath. Without it,
// $pluginObj(config) resolves to the parameterless Global.$pluginObj() that
// WheelsTest auto-binds, which ignores config and returns the cached PluginObj
// pointing at the real plugins/ dir — so the missing-path branch (the fix)
// never runs and these specs pass for the wrong reason.
function $pluginObj(required struct config) {
return g.$createObjectFromRoot(argumentCollection = arguments.config)
}
}
Loading