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
122 changes: 117 additions & 5 deletions cli/src/commands/wheels/plugins/list.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
* wheels plugins list --available
*/
component aliases="wheels plugin list" extends="../base" {

property name="forgebox" inject="ForgeBox";
property name="pluginService" inject="PluginService@wheels-cli";
property name="detailOutput" inject="DetailOutputService@wheels-cli";

Expand All @@ -30,8 +30,120 @@ component aliases="wheels plugin list" extends="../base" {
if (arguments.available) {
// Show available plugins from ForgeBox
detailOutput.header("Available Wheels Plugins on ForgeBox");
detailOutput.output("Searching, please wait...");
detailOutput.line();
command('forgebox show').params(type="cfwheels-plugins").run();

// Get list of all cfwheels plugins slugs
var forgeboxResult = command('forgebox show')
.params(type='cfwheels-plugins')
.run(returnOutput=true);

var results = [];

if (len(forgeboxResult)) {
var lines = listToArray(forgeboxResult, chr(10) & chr(13));

for (var i = 1; i <= arrayLen(lines); i++) {
var line = trim(lines[i]);

// Check if this is a slug line: Slug: "slug-name"
if (findNoCase('Slug:', line)) {
// Extract slug from quotes
var slugMatch = reFind('Slug:\s*"([^"]+)"', line, 1, true);
if (slugMatch.pos[1] > 0) {
var slug = mid(line, slugMatch.pos[2], slugMatch.len[2]);

try {
var pluginInfo = forgebox.getEntry(slug);

if (isStruct(pluginInfo) && structKeyExists(pluginInfo, "slug")) {
// Extract version from latestVersion structure
var version = "N/A";
if (structKeyExists(pluginInfo, "latestVersion") &&
isStruct(pluginInfo.latestVersion) &&
structKeyExists(pluginInfo.latestVersion, "version")) {
version = pluginInfo.latestVersion.version;
}

// Extract author from user structure
var author = "Unknown";
if (structKeyExists(pluginInfo, "user") &&
isStruct(pluginInfo.user) &&
structKeyExists(pluginInfo.user, "username")) {
author = pluginInfo.user.username;
}

arrayAppend(results, {
name: pluginInfo.title ?: slug,
slug: slug,
version: version,
description: pluginInfo.summary ?: pluginInfo.description ?: "",
author: author,
downloads: pluginInfo.hits ?: 0,
updateDate: pluginInfo.updatedDate ?: ""
});
}
} catch (any e) {
// Skip plugins that can't be retrieved
}
}
}
}
}

results.sort(function(a, b) {
return compareNoCase(a.name, b.name);
});

if (arguments.format == "json") {
var jsonOutput = {
"plugins": results,
"count": arrayLen(results)
};
print.line(jsonOutput).toConsole();
} else {
detailOutput.subHeader("Found #arrayLen(results)# plugin(s)");
detailOutput.line();

// Create table for results
var rows = [];

for (var plugin in results) {
// use ordered struct so JSON keeps key order
var row = structNew("ordered");

row["Name"] = plugin.name;
row["Slug"] = plugin.slug;
row["Version"] = plugin.version;
row["Downloads"] = numberFormat(plugin.downloads ?: 0);
row["Description"] = plugin.description ?: "No description";

// Truncate long descriptions
if (len(row["Description"]) > 50) {
row["Description"] = left(row["Description"], 47) & "...";
}

arrayAppend(rows, row);
}

// Display the table
detailOutput.getPrint().table(rows).toConsole();

detailOutput.line();
detailOutput.divider();
detailOutput.line();

// Show summary
detailOutput.metric("Total plugins found", "#arrayLen(results)#");
detailOutput.line();

// Show commands
detailOutput.subHeader("Commands");
detailOutput.output("- Install: wheels plugin install <name>", true);
detailOutput.output("- Details: wheels plugin info <name>", true);
detailOutput.output("- Add --format=json for JSON output", true);
detailOutput.line();
}
return;
}

Expand All @@ -58,17 +170,17 @@ component aliases="wheels plugin list" extends="../base" {
"plugins": plugins,
"count": arrayLen(plugins)
};
print.line(serializeJSON(jsonOutput, true));
print.line(jsonOutput).toConsole();
} else {
// Table format output
detailOutput.header("Installed Wheels Plugins (#arrayLen(plugins)#)");
detailOutput.line();

// Create table rows
var rows = [];
for (var plugin in plugins) {
var row = {
"Plugin Name": plugin.name,
"Slug" :plugin.slug,
"Version": plugin.version
};

Expand All @@ -87,7 +199,7 @@ component aliases="wheels plugin list" extends="../base" {
}

// Display the table
detailOutput.getPrint().table(rows).toConsole();
detailOutput.getPrint().table(rows);

detailOutput.line();
detailOutput.divider("-", 60);
Expand Down
2 changes: 1 addition & 1 deletion core/src/wheels/Global.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -1557,7 +1557,7 @@ component output="false" {
local.minimumMinor = "0";
local.minimumPatch = "0";
local.maximumMajor = "1";
local.maximumMinor = "9";
local.maximumMinor = "15";
local.maximumPatch = "999";

// Check minimum version
Expand Down
2 changes: 2 additions & 0 deletions core/src/wheels/databaseAdapters/Base.cfc
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ component output=false extends="wheels.Global"{
);

if (structKeyExists(wheels,"id") && isStruct(wheels.id) && !structIsEmpty(wheels.id)) {
// BoxLang-safe: ensure modifiable
wheels.result = duplicate(wheels.result);
structAppend(wheels.result, wheels.id);
}

Expand Down
2 changes: 1 addition & 1 deletion core/src/wheels/tests_testbox/runner.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@
local.tableList = ValueList(local.tables.table_name)
local.populate = StructKeyExists(url, "populate") ? url.populate : true
if (local.populate || !FindNoCase("c_o_r_e_authors", local.tableList)) {
include "populate.cfm"
include "/wheels/tests_testbox/populate.cfm"
}
}

Expand Down
2 changes: 1 addition & 1 deletion templates/base/src/tests/runner.cfm
Original file line number Diff line number Diff line change
Expand Up @@ -319,7 +319,7 @@

local.populate = StructKeyExists(url, "populate") ? url.populate : true
if (local.populate) {
include "populate.cfm"
include "/tests/populate.cfm"
}
}

Expand Down
13 changes: 12 additions & 1 deletion tools/docker/boxlang/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM ortussolutions/commandbox:boxlang
FROM ortussolutions/commandbox:latest

LABEL maintainer "Wheels Core Team"

Expand Down Expand Up @@ -30,5 +30,16 @@ COPY tools/docker/boxlang/settings.cfm ${APP_DIR}/config/settings.cfm
# Install dependencies
RUN box install

# Start once to create BoxLang engine
RUN box server start --background && sleep 10 && box server stop

# Move bx-* modules if engine modules dir exists
RUN if [ -d ".engine/boxlang/WEB-INF/boxlang/modules" ]; then \
echo "Moving bx-* modules"; \
mv bx-* .engine/boxlang/WEB-INF/boxlang/modules/ 2>/dev/null || true; \
else \
echo "Engine modules directory missing"; \
fi

# WARM UP THE SERVER
RUN ${BUILD_DIR}/util/warmup-server.sh
2 changes: 1 addition & 1 deletion tools/docker/boxlang/box.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"name": "wheels-test-suite-boxlang",
"version": "1.0.0",
"dependencies": {
"wirebox": "^7.0.0",
"wirebox": "^8.0.0",
"testbox": "^6.0.0",
"bx-compat-cfml":"^1.27.0+35",
"bx-csrf":"^1.2.0+3",
Expand Down
Loading