Skip to content
Open
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
131 changes: 131 additions & 0 deletions packages/legacyadapter/DeprecationLogger.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,131 @@
/**
* Centralized deprecation logging for the Wheels legacy adapter.
*
* Tracks deprecated API usage with configurable severity levels.
* Deduplicates warnings per-request to avoid log spam.
*
* Modes:
* silent — no output (adapter installed but quiet)
* log — WriteLog only (default)
* warn — WriteLog + stores in request scope for debug panel
* error — throws an exception (use during Stage 3 to find stragglers)
*/
component output="false" {

/**
* Initialize the deprecation logger.
*
* @mode Logging mode: silent, log, warn, or error
*/
public any function init(string mode = "log") {
variables.mode = arguments.mode;
return this;
}

/**
* Returns the current logging mode.
*/
public string function getMode() {
return variables.mode;
}

/**
* Sets the logging mode at runtime.
*
* @mode Logging mode: silent, log, warn, or error
*/
public void function setMode(required string mode) {
if (!ListFindNoCase("silent,log,warn,error", arguments.mode)) {
Throw(
type = "Wheels.LegacyAdapter.InvalidMode",
message = "Invalid deprecation logger mode: '#arguments.mode#'. Valid modes: silent, log, warn, error."
);
}
variables.mode = arguments.mode;
}

/**
* Log a deprecation warning.
*
* @oldMethod The deprecated method or pattern name
* @newMethod The replacement method or pattern
* @message Additional migration guidance
*/
public void function logDeprecation(
required string oldMethod,
required string newMethod,
string message = ""
) {
if (variables.mode == "silent") {
return;
}

var key = arguments.oldMethod & "->" & arguments.newMethod;

/* deduplicate within the current request */
$ensureRequestScope();
if (StructKeyExists(request.wheels.deprecations.seen, key)) {
return;
}
request.wheels.deprecations.seen[key] = true;

var logText = "[Wheels Legacy Adapter] '#arguments.oldMethod#' is deprecated. Use '#arguments.newMethod#' instead.";
if (Len(arguments.message)) {
logText = logText & " " & arguments.message;
}

/* record for debug panel */
var entry = {
oldMethod: arguments.oldMethod,
newMethod: arguments.newMethod,
message: arguments.message,
timestamp: Now()
};
ArrayAppend(request.wheels.deprecations.entries, entry);

if (variables.mode == "error") {
Throw(
type = "Wheels.LegacyAdapter.DeprecatedAPI",
message = logText
);
}

WriteLog(type = "warning", text = logText);
}

/**
* Returns all deprecation entries logged in the current request.
*/
public array function getRequestDeprecations() {
$ensureRequestScope();
return request.wheels.deprecations.entries;
}

/**
* Returns the count of unique deprecations logged in the current request.
*/
public numeric function getRequestDeprecationCount() {
$ensureRequestScope();
return ArrayLen(request.wheels.deprecations.entries);
}

/**
* Resets the per-request deprecation tracking.
*/
public void function resetRequestDeprecations() {
request.wheels.deprecations = {seen: {}, entries: []};
}

/**
* Ensures the request-scope struct exists for deprecation tracking.
*/
public void function $ensureRequestScope() {
if (!StructKeyExists(request, "wheels")) {
request.wheels = {};
}
if (!StructKeyExists(request.wheels, "deprecations")) {
request.wheels.deprecations = {seen: {}, entries: []};
}
}

}
219 changes: 219 additions & 0 deletions packages/legacyadapter/LegacyAdapter.cfc
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
/**
* wheels-legacy-adapter — Backward compatibility for Wheels 3.x applications.
*
* Provides deprecated API shims that delegate to current 4.0 implementations
* while logging deprecation warnings. Install this package to ease migration
* from 3.x to 4.0.
*
* Migration stages:
* Stage 1: Install adapter — existing code works unchanged
* Stage 2: Use migration scanner, update code incrementally
* Stage 3: Remove adapter when all legacy patterns eliminated
*
* Configuration (in config/settings.cfm):
* set(legacyAdapterMode = "log") — silent, log, warn, or error
*/
component mixin="controller" output="false" {

function init() {
$initLegacyAdapter();
return this;
}

/**
* Initialize the deprecation logger instance.
* Reads mode from Wheels settings if available, falls back to "log".
* Reads version from package.json.
*/
public void function $initLegacyAdapter() {
var mode = "log";
try {
mode = get("legacyAdapterMode");
} catch (any e) {
/* setting not configured — use default */
}
variables.$legacyAdapterLogger = new DeprecationLogger(mode = mode);

/* read version from package.json */
variables.$legacyAdapterVersionString = "0.0.0";
try {
var packageDir = GetDirectoryFromPath(GetCurrentTemplatePath());
var packageJsonPath = packageDir & "package.json";
if (FileExists(packageJsonPath)) {
var manifest = DeserializeJSON(FileRead(packageJsonPath));
if (StructKeyExists(manifest, "version")) {
variables.$legacyAdapterVersionString = manifest.version;
}
}
} catch (any e) {
/* fallback to default if package.json is unreadable */
}
}

/**
* Returns the adapter version string (sourced from package.json).
*/
public string function $legacyAdapterVersion() {
if (!StructKeyExists(variables, "$legacyAdapterVersionString")) {
$initLegacyAdapter();
}
return variables.$legacyAdapterVersionString;
}

/**
* Returns a summary of the adapter status and any deprecations in this request.
*/
public struct function $legacyAdapterStatus() {
var logger = $getLegacyLogger();
return {
version: $legacyAdapterVersion(),
mode: logger.getMode(),
deprecationsThisRequest: logger.getRequestDeprecationCount(),
entries: logger.getRequestDeprecations()
};
}

/* ------------------------------------------------------------------ */
/* Controller Shims */
/* ------------------------------------------------------------------ */

/**
* DEPRECATED: Use renderView() instead.
*
* Legacy shim for Wheels 1.x/2.x renderPage() method.
* Delegates to renderView() with all arguments passed through.
*/
public any function renderPage() {
$getLegacyLogger().logDeprecation(
oldMethod = "renderPage()",
newMethod = "renderView()",
message = "renderPage() was renamed in Wheels 3.0. Update your controller actions."
);
return renderView(argumentCollection = arguments);
}

/**
* DEPRECATED: Use renderView(returnAs="string") instead.
*
* Legacy shim for Wheels 1.x/2.x renderPageToString() method.
*/
public string function renderPageToString() {
$getLegacyLogger().logDeprecation(
oldMethod = "renderPageToString()",
newMethod = "renderView(returnAs=""string"")",
message = "renderPageToString() was removed in Wheels 3.0. Use renderView(returnAs=""string"") instead."
);
arguments.returnAs = "string";
return renderView(argumentCollection = arguments);
}

/**
* DEPRECATED: Use sendEmail() with updated argument names.
*
* Legacy shim that maps old sendEmail argument names to current ones.
* In Wheels 2.x, the layout argument defaulted differently.
*/
public any function $legacySendEmail() {
$getLegacyLogger().logDeprecation(
oldMethod = "$legacySendEmail()",
newMethod = "sendEmail()",
message = "Use the standard sendEmail() function directly."
);
return sendEmail(argumentCollection = arguments);
}

/* ------------------------------------------------------------------ */
/* Configuration Shims */
/* ------------------------------------------------------------------ */

/**
* DEPRECATED: Use the DI container via service() and injector() instead.
*
* Returns a value from the application.wheels struct, which was the
* pre-4.0 way to access framework internals. Logs deprecation.
*
* @key The application.wheels key to read
*/
public any function $legacyAppScopeGet(required string key) {
$getLegacyLogger().logDeprecation(
oldMethod = "application.wheels.#arguments.key#",
newMethod = "service() or injector()",
message = "Direct application.wheels access is discouraged. Use the DI container for service resolution."
);
var appKey = "$wheels";
if (StructKeyExists(application, "wheels")) {
appKey = "wheels";
}
if (StructKeyExists(application[appKey], arguments.key)) {
return application[appKey][arguments.key];
}
Throw(
type = "Wheels.LegacyAdapter.KeyNotFound",
message = "Key '#arguments.key#' not found in application scope."
);
}

/* ------------------------------------------------------------------ */
/* Plugin Diagnostics */
/* ------------------------------------------------------------------ */

/**
* Checks whether legacy plugins are loaded and returns info about them.
* Useful during migration to identify plugins that need conversion to packages.
*
* This is a diagnostic function — it reports what legacy plugins exist but
* does not perform automatic wrapping or bridging. The actual migration
* from plugin to package is a manual process guided by the scanner report.
*/
public struct function $legacyPluginInfo() {
var info = {plugins: [], hasLegacyPlugins: false};
var appKey = "$wheels";
if (StructKeyExists(application, "wheels")) {
appKey = "wheels";
}
if (StructKeyExists(application[appKey], "plugins")) {
var pluginStruct = application[appKey].plugins;
info.hasLegacyPlugins = !StructIsEmpty(pluginStruct);
for (var key in pluginStruct) {
ArrayAppend(info.plugins, {
name: key,
version: StructKeyExists(pluginStruct[key], "version") ? pluginStruct[key].version : "unknown"
});
}
}
return info;
}

/* ------------------------------------------------------------------ */
/* Migration Scanner Access */
/* ------------------------------------------------------------------ */

/**
* Runs the migration scanner against the application directory.
* Returns a structured report of legacy patterns found.
*
* @appPath Path to scan (defaults to the app/ directory)
*/
public struct function $runMigrationScan(string appPath = "") {
if (!Len(arguments.appPath)) {
arguments.appPath = ExpandPath("/app");
}
var scanner = new MigrationScanner();
return scanner.scan(appPath = arguments.appPath);
}

/* ------------------------------------------------------------------ */
/* Internal Helpers */
/* ------------------------------------------------------------------ */

/**
* Returns the deprecation logger, initializing if needed.
*/
public any function $getLegacyLogger() {
if (!StructKeyExists(variables, "$legacyAdapterLogger")) {
$initLegacyAdapter();
}
return variables.$legacyAdapterLogger;
}

}
Loading
Loading