diff --git a/configs/jsactions/rollup-plugin-studio-pro-format.mjs b/configs/jsactions/rollup-plugin-studio-pro-format.mjs new file mode 100644 index 000000000..de8b63865 --- /dev/null +++ b/configs/jsactions/rollup-plugin-studio-pro-format.mjs @@ -0,0 +1,126 @@ +/** + * Rollup plugin that restructures the TypeScript-compiled JS output into the + * exact format Studio Pro expects in javascriptsource/. + * + * The TypeScript compiler already handles stripping types, normalizing formatting, + * and collapsing multi-line signatures. This plugin takes that compiled output + * (chunk.code) and restructures it: + * + * 1. Moves the SP header to the top + * 2. Places all imports after the header (excluding mx-global and big.js, + * which Studio Pro injects itself from BuildData()) + * 3. Wraps EXTRA CODE in its markers + * 4. Emits `export async function Name(...) {` instead of the split + * `async function Name(...) { ... } \n export { Name };` that rollup produces + * 5. Wraps user code in BEGIN/END USER CODE markers + * + * This ensures dist/ files match what Studio Pro regenerates on first deploy, + * eliminating the phantom git diff in javascriptsource/ after a fresh module install. + */ +export function studioProFormat() { + return { + name: "studio-pro-format", + generateBundle(options, bundle) { + for (const [, chunk] of Object.entries(bundle)) { + if (chunk.type !== "chunk") continue; + chunk.code = restructureToStudioProFormat(chunk.code); + } + } + }; +} + +function restructureToStudioProFormat(code) { + const lines = code.split(/\r?\n/); + + const imports = []; + const extraCode = []; + const jsdoc = []; + const userCode = []; + let functionSignature = ""; + + let inExtraCode = false; + let inUserCode = false; + let inJsdoc = false; + let skipHeader = false; + + for (const line of lines) { + const trimmed = line.trimEnd(); + const marker = line.trim(); + + // Drop the SP header block wherever it appears — we re-emit it at the top + if (marker === "// This file was generated by Mendix Studio Pro.") { + skipHeader = true; + continue; + } + if (skipHeader) { + if (marker === "// Other code you write will be lost the next time you deploy the project.") { + skipHeader = false; + } + continue; + } + + // EXTRA CODE section + if (marker === "// BEGIN EXTRA CODE") { inExtraCode = true; continue; } + if (marker === "// END EXTRA CODE") { inExtraCode = false; continue; } + if (inExtraCode) { extraCode.push(trimmed); continue; } + + // USER CODE section + if (marker === "// BEGIN USER CODE") { inUserCode = true; continue; } + if (marker === "// END USER CODE") { inUserCode = false; continue; } + if (inUserCode) { userCode.push(trimmed); continue; } + + // JSDoc block + if (marker.startsWith("/**")) inJsdoc = true; + if (inJsdoc) { + jsdoc.push(trimmed); + if (marker.includes("*/")) inJsdoc = false; + continue; + } + + // Function signature — TypeScript already compiled it to plain JS + if (/^(export\s+)?async function \w+\(/.test(marker)) { + functionSignature = marker.replace(/^export\s+/, "").replace(/\s*\{?\s*$/, ""); + continue; + } + + // Drop trailing `export { Foo };` — SP format uses `export async function` instead + if (/^export \{[^}]+\};?\s*$/.test(marker)) continue; + + // Drop standalone closing brace that belongs to the function wrapper + if (marker === "}") continue; + + // Collect imports — skip big.js (injected explicitly below in correct position) + // and mx-global (Studio Pro adds it unconditionally from BuildData()) + if (marker.startsWith("import ")) { + if (!marker.includes("mx-global") && !marker.includes("big.js")) { + imports.push(trimmed); + } + continue; + } + } + + const out = []; + out.push("// This file was generated by Mendix Studio Pro."); + out.push("//"); + out.push("// WARNING: Only the following code will be retained when actions are regenerated:"); + out.push("// - the import list"); + out.push("// - the code between BEGIN USER CODE and END USER CODE"); + out.push("// - the code between BEGIN EXTRA CODE and END EXTRA CODE"); + out.push("// Other code you write will be lost the next time you deploy the project."); + out.push(`import { Big } from "big.js";`); + imports.forEach(i => out.push(i)); + out.push(""); + out.push("// BEGIN EXTRA CODE"); + extraCode.forEach(l => out.push(l)); + out.push("// END EXTRA CODE"); + out.push(""); + jsdoc.forEach(l => out.push(l)); + out.push(`export ${functionSignature} {`); + out.push("\t// BEGIN USER CODE"); + userCode.forEach(l => out.push(l)); + out.push("\t// END USER CODE"); + out.push("}"); + out.push(""); + + return out.join("\r\n"); +} diff --git a/configs/jsactions/rollup.config.mjs b/configs/jsactions/rollup.config.mjs index d8691fac1..f10559d1c 100644 --- a/configs/jsactions/rollup.config.mjs +++ b/configs/jsactions/rollup.config.mjs @@ -13,7 +13,7 @@ import { nodeResolve } from "@rollup/plugin-node-resolve"; import typescript from "@rollup/plugin-typescript"; import { collectDependencies, copyJsModule } from "./rollup-plugin-collect-dependencies.mjs"; import { licenseCustomTemplate, copyLicenseFile } from "./rollup-helper.mjs"; -import { bigJsImportReplacer } from "./rollup-plugin-bigjs-import-replacer.mjs"; +import { studioProFormat } from "./rollup-plugin-studio-pro-format.mjs"; const cwd = process.cwd(); @@ -75,7 +75,7 @@ export default async args => { }), nodeResolvePlugin, typescriptPlugin, - bigJsImportReplacer(), + studioProFormat(), i === files.length - 1 ? command([ async () => copyLicenseFile(cwd, outDir),