refactor: set_module_example_data escribe via el server Python

La tool llamaba a `action_ws=setStaticVars`, que hace un file_put_contents
del builder.json ENTERO en la web para cambiar una sola clave. Eso se
saltaba las dos garantias que protegen ese fichero: el bloqueo de escritura
de la API de ficheros (BLOCKED_GENERATED_FILENAMES en handlers/files.py) y
la allowlist de /api/modules/update-metadata.

El riesgo no era teorico. El builder.json es la unica memoria de que
variable vive en que columna de builder_custom: si una compilacion caia
entre la lectura y la escritura de setStaticVars, esta devolvia el mapeo
var->columna anterior encima del recien generado y el contenido guardado
quedaba colgado de la variable equivocada en TODAS las paginas que usan el
modulo.

Ahora delega en /api/modules/update-metadata, que ya es la via quirurgica
para la metadata y acepta staticVars en su allowlist (objeto, <=64KB,
<=200 claves).

Se aprovecha para quitar el volcado del schema y del payload completos por
consola en cada llamada.

Companion en el repo de Forge: compiler.js reenvia staticVars en cada
compilacion. Antes no lo mandaba, asi que el CMS lo tiraba al regenerar el
builder.json — solo 5 de 4870 modulos lo conservaban.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Jordan Diaz
2026-08-13 15:21:16 +00:00
parent f7e950694e
commit 76221ee1d4

View File

@@ -1,7 +1,17 @@
import { z } from "zod";
import { withAuth, getSessionCredentials, getApiClient, getCommonParams } from "../../auth/index.js";
import { handleToolError, validateRequired, handleApiResponse } from "../helpers/errorHandler.js";
import { withAuth } from "../../auth/index.js";
import { handleToolError, validateRequired } from "../helpers/errorHandler.js";
import { withAuthParams } from "../helpers/authSchema.js";
import { pythonPost } from "../helpers/pythonServerClient.js";
import { getCurrentProjectInfo } from "../files/helpers.js";
// Antes esta tool llamaba a `action_ws=setStaticVars`, que hacia un
// file_put_contents del builder.json ENTERO en la web para cambiar una sola
// clave. Eso se saltaba el bloqueo de escritura de builder.json y, si caia una
// compilacion entre su lectura y su escritura, devolvia el mapeo var->columna
// anterior encima del recien generado (contenido rotado en todas las paginas
// que usan el modulo). Ahora delega en el endpoint quirurgico de Forge, que
// escribe SOLO las claves de su allowlist.
export function registerSetModuleExampleDataTool(server) {
server.tool(
@@ -50,42 +60,36 @@ Si dudas del formato exacto, lee 'read_doc({ name: "01-builder-fields" })'.`,
}
}
const credentials = await getSessionCredentials(extra.sessionId);
const client = await getApiClient(extra.sessionId);
console.error(`[set_module_example_data] Module ID: ${moduleId}, vars: ${Object.keys(exampleData).length}`);
// Log data for debugging
console.error(`[set_module_example_data] Module ID: ${moduleId}`);
console.error(`[set_module_example_data] Module Schema:`, JSON.stringify(moduleSchema, null, 2));
console.error(`[set_module_example_data] Example Data:`, JSON.stringify(exampleData, null, 2));
// Prepare payload for setStaticVars action
const payload = await getCommonParams(extra.sessionId, {
action_ws: "setStaticVars",
moduleId: moduleId,
const { projectSlug } = getCurrentProjectInfo();
const result = await pythonPost("/api/modules/update-metadata", {
project: projectSlug,
module: moduleId,
staticVars: exampleData,
schema: moduleSchema
});
console.error(`[set_module_example_data] Full Payload:`, JSON.stringify(payload, null, 2));
// Send to viewer_functions
const response = await client.post("/cms/lib/viewer_functions.php", payload);
console.error(`[set_module_example_data] Response:`, JSON.stringify(response.data, null, 2));
// Check for API errors in response
const apiError = handleApiResponse(response.data, 'set_module_example_data');
if (apiError) return apiError;
if (!result?.success) {
return {
content: [{
type: "text",
text: JSON.stringify({
success: false,
error: result?.error || "Could not set module example data",
}),
}],
isError: true,
};
}
return {
content: [{
type: "text", text: JSON.stringify({
success: true,
message: `Example data set successfully for module '${moduleId}'`,
moduleId: moduleId,
moduleId: result.module || moduleId,
dataCount: Object.keys(exampleData).length,
schemaVarsCount: moduleSchema?.codeVars ? Object.keys(moduleSchema.codeVars).length : 0,
response: response.data
}, null, 2)
}],
};