Skip to content

Commit 180c8bb

Browse files
authored
Merge pull request #2015 from wheels-dev/grove/W-003-wheels-4-0-legacy-compatibility-adapter-
feat: (W-003) Legacy compatibility adapter for 3.x to 4.0 migration
2 parents 0e13679 + c1bc783 commit 180c8bb

File tree

8 files changed

+1135
-0
lines changed

8 files changed

+1135
-0
lines changed
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
/**
2+
* Centralized deprecation logging for the Wheels legacy adapter.
3+
*
4+
* Tracks deprecated API usage with configurable severity levels.
5+
* Deduplicates warnings per-request to avoid log spam.
6+
*
7+
* Modes:
8+
* silent — no output (adapter installed but quiet)
9+
* log — WriteLog only (default)
10+
* warn — WriteLog + stores in request scope for debug panel
11+
* error — throws an exception (use during Stage 3 to find stragglers)
12+
*/
13+
component output="false" {
14+
15+
/**
16+
* Initialize the deprecation logger.
17+
*
18+
* @mode Logging mode: silent, log, warn, or error
19+
*/
20+
public any function init(string mode = "log") {
21+
variables.mode = arguments.mode;
22+
return this;
23+
}
24+
25+
/**
26+
* Returns the current logging mode.
27+
*/
28+
public string function getMode() {
29+
return variables.mode;
30+
}
31+
32+
/**
33+
* Sets the logging mode at runtime.
34+
*
35+
* @mode Logging mode: silent, log, warn, or error
36+
*/
37+
public void function setMode(required string mode) {
38+
if (!ListFindNoCase("silent,log,warn,error", arguments.mode)) {
39+
Throw(
40+
type = "Wheels.LegacyAdapter.InvalidMode",
41+
message = "Invalid deprecation logger mode: '#arguments.mode#'. Valid modes: silent, log, warn, error."
42+
);
43+
}
44+
variables.mode = arguments.mode;
45+
}
46+
47+
/**
48+
* Log a deprecation warning.
49+
*
50+
* @oldMethod The deprecated method or pattern name
51+
* @newMethod The replacement method or pattern
52+
* @message Additional migration guidance
53+
*/
54+
public void function logDeprecation(
55+
required string oldMethod,
56+
required string newMethod,
57+
string message = ""
58+
) {
59+
if (variables.mode == "silent") {
60+
return;
61+
}
62+
63+
var key = arguments.oldMethod & "->" & arguments.newMethod;
64+
65+
/* deduplicate within the current request */
66+
$ensureRequestScope();
67+
if (StructKeyExists(request.wheels.deprecations.seen, key)) {
68+
return;
69+
}
70+
request.wheels.deprecations.seen[key] = true;
71+
72+
var logText = "[Wheels Legacy Adapter] '#arguments.oldMethod#' is deprecated. Use '#arguments.newMethod#' instead.";
73+
if (Len(arguments.message)) {
74+
logText = logText & " " & arguments.message;
75+
}
76+
77+
/* record for debug panel */
78+
var entry = {
79+
oldMethod: arguments.oldMethod,
80+
newMethod: arguments.newMethod,
81+
message: arguments.message,
82+
timestamp: Now()
83+
};
84+
ArrayAppend(request.wheels.deprecations.entries, entry);
85+
86+
if (variables.mode == "error") {
87+
Throw(
88+
type = "Wheels.LegacyAdapter.DeprecatedAPI",
89+
message = logText
90+
);
91+
}
92+
93+
WriteLog(type = "warning", text = logText);
94+
}
95+
96+
/**
97+
* Returns all deprecation entries logged in the current request.
98+
*/
99+
public array function getRequestDeprecations() {
100+
$ensureRequestScope();
101+
return request.wheels.deprecations.entries;
102+
}
103+
104+
/**
105+
* Returns the count of unique deprecations logged in the current request.
106+
*/
107+
public numeric function getRequestDeprecationCount() {
108+
$ensureRequestScope();
109+
return ArrayLen(request.wheels.deprecations.entries);
110+
}
111+
112+
/**
113+
* Resets the per-request deprecation tracking.
114+
*/
115+
public void function resetRequestDeprecations() {
116+
request.wheels.deprecations = {seen: {}, entries: []};
117+
}
118+
119+
/**
120+
* Ensures the request-scope struct exists for deprecation tracking.
121+
*/
122+
public void function $ensureRequestScope() {
123+
if (!StructKeyExists(request, "wheels")) {
124+
request.wheels = {};
125+
}
126+
if (!StructKeyExists(request.wheels, "deprecations")) {
127+
request.wheels.deprecations = {seen: {}, entries: []};
128+
}
129+
}
130+
131+
}
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/**
2+
* wheels-legacy-adapter — Backward compatibility for Wheels 3.x applications.
3+
*
4+
* Provides deprecated API shims that delegate to current 4.0 implementations
5+
* while logging deprecation warnings. Install this package to ease migration
6+
* from 3.x to 4.0.
7+
*
8+
* Migration stages:
9+
* Stage 1: Install adapter — existing code works unchanged
10+
* Stage 2: Use migration scanner, update code incrementally
11+
* Stage 3: Remove adapter when all legacy patterns eliminated
12+
*
13+
* Configuration (in config/settings.cfm):
14+
* set(legacyAdapterMode = "log") — silent, log, warn, or error
15+
*/
16+
component mixin="controller" output="false" {
17+
18+
function init() {
19+
$initLegacyAdapter();
20+
return this;
21+
}
22+
23+
/**
24+
* Initialize the deprecation logger instance.
25+
* Reads mode from Wheels settings if available, falls back to "log".
26+
* Reads version from package.json.
27+
*/
28+
public void function $initLegacyAdapter() {
29+
var mode = "log";
30+
try {
31+
mode = get("legacyAdapterMode");
32+
} catch (any e) {
33+
/* setting not configured — use default */
34+
}
35+
variables.$legacyAdapterLogger = new DeprecationLogger(mode = mode);
36+
37+
/* read version from package.json */
38+
variables.$legacyAdapterVersionString = "0.0.0";
39+
try {
40+
var packageDir = GetDirectoryFromPath(GetCurrentTemplatePath());
41+
var packageJsonPath = packageDir & "package.json";
42+
if (FileExists(packageJsonPath)) {
43+
var manifest = DeserializeJSON(FileRead(packageJsonPath));
44+
if (StructKeyExists(manifest, "version")) {
45+
variables.$legacyAdapterVersionString = manifest.version;
46+
}
47+
}
48+
} catch (any e) {
49+
/* fallback to default if package.json is unreadable */
50+
}
51+
}
52+
53+
/**
54+
* Returns the adapter version string (sourced from package.json).
55+
*/
56+
public string function $legacyAdapterVersion() {
57+
if (!StructKeyExists(variables, "$legacyAdapterVersionString")) {
58+
$initLegacyAdapter();
59+
}
60+
return variables.$legacyAdapterVersionString;
61+
}
62+
63+
/**
64+
* Returns a summary of the adapter status and any deprecations in this request.
65+
*/
66+
public struct function $legacyAdapterStatus() {
67+
var logger = $getLegacyLogger();
68+
return {
69+
version: $legacyAdapterVersion(),
70+
mode: logger.getMode(),
71+
deprecationsThisRequest: logger.getRequestDeprecationCount(),
72+
entries: logger.getRequestDeprecations()
73+
};
74+
}
75+
76+
/* ------------------------------------------------------------------ */
77+
/* Controller Shims */
78+
/* ------------------------------------------------------------------ */
79+
80+
/**
81+
* DEPRECATED: Use renderView() instead.
82+
*
83+
* Legacy shim for Wheels 1.x/2.x renderPage() method.
84+
* Delegates to renderView() with all arguments passed through.
85+
*/
86+
public any function renderPage() {
87+
$getLegacyLogger().logDeprecation(
88+
oldMethod = "renderPage()",
89+
newMethod = "renderView()",
90+
message = "renderPage() was renamed in Wheels 3.0. Update your controller actions."
91+
);
92+
return renderView(argumentCollection = arguments);
93+
}
94+
95+
/**
96+
* DEPRECATED: Use renderView(returnAs="string") instead.
97+
*
98+
* Legacy shim for Wheels 1.x/2.x renderPageToString() method.
99+
*/
100+
public string function renderPageToString() {
101+
$getLegacyLogger().logDeprecation(
102+
oldMethod = "renderPageToString()",
103+
newMethod = "renderView(returnAs=""string"")",
104+
message = "renderPageToString() was removed in Wheels 3.0. Use renderView(returnAs=""string"") instead."
105+
);
106+
arguments.returnAs = "string";
107+
return renderView(argumentCollection = arguments);
108+
}
109+
110+
/**
111+
* DEPRECATED: Use sendEmail() with updated argument names.
112+
*
113+
* Legacy shim that maps old sendEmail argument names to current ones.
114+
* In Wheels 2.x, the layout argument defaulted differently.
115+
*/
116+
public any function $legacySendEmail() {
117+
$getLegacyLogger().logDeprecation(
118+
oldMethod = "$legacySendEmail()",
119+
newMethod = "sendEmail()",
120+
message = "Use the standard sendEmail() function directly."
121+
);
122+
return sendEmail(argumentCollection = arguments);
123+
}
124+
125+
/* ------------------------------------------------------------------ */
126+
/* Configuration Shims */
127+
/* ------------------------------------------------------------------ */
128+
129+
/**
130+
* DEPRECATED: Use the DI container via service() and injector() instead.
131+
*
132+
* Returns a value from the application.wheels struct, which was the
133+
* pre-4.0 way to access framework internals. Logs deprecation.
134+
*
135+
* @key The application.wheels key to read
136+
*/
137+
public any function $legacyAppScopeGet(required string key) {
138+
$getLegacyLogger().logDeprecation(
139+
oldMethod = "application.wheels.#arguments.key#",
140+
newMethod = "service() or injector()",
141+
message = "Direct application.wheels access is discouraged. Use the DI container for service resolution."
142+
);
143+
var appKey = "$wheels";
144+
if (StructKeyExists(application, "wheels")) {
145+
appKey = "wheels";
146+
}
147+
if (StructKeyExists(application[appKey], arguments.key)) {
148+
return application[appKey][arguments.key];
149+
}
150+
Throw(
151+
type = "Wheels.LegacyAdapter.KeyNotFound",
152+
message = "Key '#arguments.key#' not found in application scope."
153+
);
154+
}
155+
156+
/* ------------------------------------------------------------------ */
157+
/* Plugin Diagnostics */
158+
/* ------------------------------------------------------------------ */
159+
160+
/**
161+
* Checks whether legacy plugins are loaded and returns info about them.
162+
* Useful during migration to identify plugins that need conversion to packages.
163+
*
164+
* This is a diagnostic function — it reports what legacy plugins exist but
165+
* does not perform automatic wrapping or bridging. The actual migration
166+
* from plugin to package is a manual process guided by the scanner report.
167+
*/
168+
public struct function $legacyPluginInfo() {
169+
var info = {plugins: [], hasLegacyPlugins: false};
170+
var appKey = "$wheels";
171+
if (StructKeyExists(application, "wheels")) {
172+
appKey = "wheels";
173+
}
174+
if (StructKeyExists(application[appKey], "plugins")) {
175+
var pluginStruct = application[appKey].plugins;
176+
info.hasLegacyPlugins = !StructIsEmpty(pluginStruct);
177+
for (var key in pluginStruct) {
178+
ArrayAppend(info.plugins, {
179+
name: key,
180+
version: StructKeyExists(pluginStruct[key], "version") ? pluginStruct[key].version : "unknown"
181+
});
182+
}
183+
}
184+
return info;
185+
}
186+
187+
/* ------------------------------------------------------------------ */
188+
/* Migration Scanner Access */
189+
/* ------------------------------------------------------------------ */
190+
191+
/**
192+
* Runs the migration scanner against the application directory.
193+
* Returns a structured report of legacy patterns found.
194+
*
195+
* @appPath Path to scan (defaults to the app/ directory)
196+
*/
197+
public struct function $runMigrationScan(string appPath = "") {
198+
if (!Len(arguments.appPath)) {
199+
arguments.appPath = ExpandPath("/app");
200+
}
201+
var scanner = new MigrationScanner();
202+
return scanner.scan(appPath = arguments.appPath);
203+
}
204+
205+
/* ------------------------------------------------------------------ */
206+
/* Internal Helpers */
207+
/* ------------------------------------------------------------------ */
208+
209+
/**
210+
* Returns the deprecation logger, initializing if needed.
211+
*/
212+
public any function $getLegacyLogger() {
213+
if (!StructKeyExists(variables, "$legacyAdapterLogger")) {
214+
$initLegacyAdapter();
215+
}
216+
return variables.$legacyAdapterLogger;
217+
}
218+
219+
}

0 commit comments

Comments
 (0)