// ==UserScript== // @name Microsoft Power Platform/Dynamics 365 CE - Generate TypeScript Overload Signatures // @namespace https://github.com/gncnpk/xrm-generate-ts-overloads // @author Gavin Canon-Phratsachack (https://github.com/gncnpk) // @version 1.1 // @license GPL-3.0 // @description Automatically creates TypeScript type definitions compatible with @types/xrm by extracting form attributes and controls from Dynamics 365/Power Platform model-driven applications. // @match https://*.dynamics.com/main.aspx?appid=*&pagetype=entityrecord&etn=*&id=* // @grant none // @downloadURL none // ==/UserScript== (function() { 'use strict'; // Create a button element and style it to be fixed in the bottom-right corner. const btn = document.createElement('button'); btn.textContent = 'Generate TypeScript Signatures'; btn.style.position = 'fixed'; btn.style.bottom = '20px'; btn.style.right = '20px'; btn.style.padding = '10px'; btn.style.backgroundColor = '#007ACC'; btn.style.color = '#fff'; btn.style.border = 'none'; btn.style.borderRadius = '5px'; btn.style.cursor = 'pointer'; btn.style.zIndex = 10000; document.body.appendChild(btn); btn.addEventListener('click', () => { // Mapping objects for Xrm attribute and control types. var attributeTypeMapping = { "boolean": "Xrm.Attributes.BooleanAttribute", "datetime": "Xrm.Attributes.DateAttribute", "decimal": "Xrm.Attributes.NumberAttribute", "double": "Xrm.Attributes.NumberAttribute", "integer": "Xrm.Attributes.NumberAttribute", "lookup": "Xrm.Attributes.LookupAttribute", "memo": "Xrm.Attributes.StringAttribute", "money": "Xrm.Attributes.NumberAttribute", "multiselectoptionset": "Xrm.Attributes.MultiselectOptionSetAttribute", "optionset": "Xrm.Attributes.OptionSetAttribute", "string": "Xrm.Attributes.StringAttribute" }; var controlTypeMapping = { "standard": "Xrm.Controls.StandardControl", "iframe": "Xrm.Controls.IframeControl", "lookup": "Xrm.Controls.LookupControl", "optionset": "Xrm.Controls.OptionSetControl", "customsubgrid:MscrmControls.Grid.GridControl": "Xrm.Controls.GridControl", "subgrid": "Xrm.Controls.GridControl", "timelinewall": "Xrm.Controls.TimelineWall" }; // Object to hold the type information. const typeInfo = { attributes: {}, controls: {} }; // Ensure that the Xrm.Page API is available. if (typeof Xrm !== 'undefined' && Xrm.Page && typeof Xrm.Page.getAttribute === 'function') { // Loop through all attributes on the form. Xrm.Page.getAttribute().forEach((attr) => { const attrType = attr.getAttributeType(); const mappedType = attributeTypeMapping[attrType]; if (mappedType) { typeInfo.attributes[attr.getName()] = mappedType; } }); } else { alert("Xrm.Page is not available on this page."); return; } // Loop through all controls on the form. if (typeof Xrm.Page.getControl === 'function') { Xrm.Page.getControl().forEach((ctrl) => { const ctrlType = ctrl.getControlType(); const mappedType = controlTypeMapping[ctrlType]; if (mappedType) { typeInfo.controls[ctrl.getName()] = mappedType; } }); } // Build the TypeScript overload string. let outputTS = `// This file is generated automatically. // It extends the Xrm.FormContext interface with overloads for getAttribute and getControl. // Do not modify this file manually. declare namespace Xrm { interface FormContext { `; for (const [attributeName, attributeType] of Object.entries(typeInfo.attributes)) { outputTS += ` getAttribute(attributeName: "${attributeName}"): ${attributeType};\n`; } for (const [controlName, controlType] of Object.entries(typeInfo.controls)) { outputTS += ` getControl(controlName: "${controlName}"): ${controlType};\n`; } outputTS += ` } } `; // Create a new window with a textarea showing the output. // The textarea is set to readonly to prevent editing. const w = window.open('', '_blank', 'width=600,height=400,menubar=no,toolbar=no,location=no,resizable=yes'); if (w) { w.document.write('