Microsoft Power Platform/Dynamics 365 CE - Generate TypeScript Overload Signatures

Automatically creates TypeScript type definitions compatible with @types/xrm by extracting form attributes and controls from Dynamics 365/Power Platform model-driven applications.

当前为 2025-03-17 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==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.6
// @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
// ==/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",
            "quickform": "Xrm.Controls.QuickFormControl"
        };

        var specificControlTypeMapping = {
            "boolean": "Xrm.Controls.BooleanControl",
            "datetime": "Xrm.Controls.DateControl",
            "decimal": "Xrm.Controls.NumberControl",
            "double": "Xrm.Controls.NumberControl",
            "integer": "Xrm.Controls.NumberControl",
            "lookup": "Xrm.Controls.LookupControl",
            "memo": "Xrm.Controls.StringControl",
            "money": "Xrm.Controls.NumberControl",
            "multiselectoptionset": "Xrm.Controls.MultiselectOptionSetControl",
            "optionset": "Xrm.Controls.OptionSetControl",
            "string": "Xrm.Controls.StringControl"
        }
 
        // Object to hold the type information.
        const typeInfo = { attributes: {}, controls: {}, possibleTypes: {} };

        const enums = {};
 
        // Loop through all controls on the form.
        if (typeof Xrm !== 'undefined' && Xrm.Page && 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;
                    typeInfo.possibleTypes[ctrl.getName()] = [];
                    typeInfo.possibleTypes[ctrl.getName()].push(mappedType);
                }
            });
        } else {
            alert("Xrm.Page is not available on this page.");
            return;
        }

        // Loop through all enums on the form.
        if (typeof Xrm.Page.getAttribute === 'function') {
            Xrm.Page.getAttribute().forEach((attr) => {
                if (attr.getAttributeType() === "optionset" && attr.controls.get().length > 0) {
                    const enumValues = attr.getOptions();
                    if (enumValues) {
                        enums[attr.controls.get(0).getLabel().replace(/\s+/g, '')] = enumValues;
                    }
                }
            });
        }

        // Loop through all Quick View controls on the form.
        if (typeof Xrm.Page.ui.quickForms.get === 'function') {
            Xrm.Page.ui.quickForms.get().forEach((ctrl) => {
                const ctrlType = ctrl.getControlType();
                const mappedType = controlTypeMapping[ctrlType];
                if (mappedType) {
                    typeInfo.possibleTypes[ctrl.getName()] = [];
                    typeInfo.possibleTypes[ctrl.getName()].push(mappedType);
                }
            });
        }

        // Ensure that the Xrm.Page API is available.
        if (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];
                const mappedControlType = specificControlTypeMapping[attrType];
                if (mappedType) {
                    typeInfo.attributes[attr.getName()] = mappedType;
                    typeInfo.controls[attr.getName()] = mappedControlType;
                    typeInfo.possibleTypes[attr.getName()] = [];
                    typeInfo.possibleTypes[attr.getName()].push(mappedType);
                    typeInfo.possibleTypes[attr.getName()].push(mappedControlType);
                }
            });
        }
 
        // 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.
`
for(const [enumName, enumValues] of Object.entries(enums)) {
    let enumTemplate = [];
    for(const enumValue of enumValues) {
        enumTemplate.push(`   ${enumValue.text.replace(/\W/g, '')} = ${enumValue.value}`);
    }
outputTS += `
enum ${enumName} {
${enumTemplate.join(",\n")}
}
`
}
outputTS += `
declare namespace Xrm {
    namespace Collection {
        interface ItemCollection<T> {
`
for (const [possibleTypeName, possibleTypesArray] of Object.entries(typeInfo.possibleTypes)) {
    let possibleTypeTemplate = "";
    for (const possibleType of possibleTypesArray) {
        possibleTypeTemplate += ` TSubType extends ${possibleType} ? ${possibleType} : `;
    }
    outputTS += `    get<TSubType extends T>(itemName: "${possibleTypeName}"):${possibleTypeTemplate}never;\n`;
}
outputTS += `
    }
}`
outputTS += `
  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('<html><head><title>TypeScript Overload Signatures</title></head><body>');
            w.document.write('<textarea readonly style="width:100%; height:90%;">' + outputTS + '</textarea>');
            w.document.write('</body></html>');
            w.document.close();
        } else {
            // Fallback to prompt if popups are blocked.
            prompt("Copy the TypeScript definition:", outputTS);
        }
    });
})();