If anyone is still looking for a solution to this, I managed to get around with this slightly convoluted in my opinion bit of code. It makes use mainly of the reverse-engineering method JavaScriptSerializer.serializeToJs() the SDK provides which neatly contains all references of objects of a document.
import { microflows } from "mendixmodelsdk";
import { pages } from "mendixmodelsdk";
import { JavaScriptSerializer } from "mendixmodelsdk";
import { domainmodels } from "mendixmodelsdk";
import { documenttemplates } from "mendixmodelsdk";
import { MendixPlatformClient } from "mendixplatformsdk";
async function main() {
const client = new MendixPlatformClient();
const app = client.getApp("XXXXXX"); //Replace XXXXXX with your app ID that you get from the developer portal
const workingCopy = await app.createTemporaryWorkingCopy("main");
const model = await workingCopy.openModel();
const modules = model.allModules();
const usages = new Map<string, string[]>();
// Add module names to usages map
modules.forEach((module) => {
usages.set(module.name, []);
})
// Get all page interfaces
const pageInterfaces = model.allPages();
// Get page usages
(await getPageUsages(pageInterfaces, usages));
// Get all snippet interfaces
const snippetInterfaces = model.allSnippets();
// Get snippet usages
(await getSnippetUsages(snippetInterfaces, usages));
// Get all domain model interfaces
const domainModelInterfaces = model.allDomainModels();
// Get domain model usages
(await getDomainModelUsages(domainModelInterfaces, usages));
// Get all layout interfaces
const layoutInterfaces = model.allLayouts();
// Get layout usages
(await getLayoutUsages(layoutInterfaces, usages));
// Get all document template interfaces
const documentTemplateInterfaces = model.allDocumentTemplates();
// Get document template usages
(await getDocumentTemplateUsages(documentTemplateInterfaces, usages));
// Get all nanoflow interfaces
const nanoflowInterfaces = model.allNanoflows();
// Get nanoflow usages
(await getNanoflowUsages(nanoflowInterfaces, usages));
// Get all microflow interfaces
const microInterfaces = model.allMicroflowBases();
// Get and print microflow usages
console.log(await getMicroflowUsages(microInterfaces, usages));
}
async function getMicroflowUsages(microInterfaces: microflows.IMicroflowBase[], usages: Map<string, string[]>) {
console.log(`Getting microflow usages...`)
// Initialize variable to hold module name to which microflow belongs
var microModule: any;
for (const microInterface of microInterfaces) {
// Load microflow to get all properties
const micro = await microInterface.load();
// Derive module name microflow belongs to from fully qualified name which would be for example Login_EndUser.ACT_Signup
microModule = micro.qualifiedName?.substring(0, micro.qualifiedName.indexOf("."));
// Using a for-of loop to exclude microModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== microModule) {
filteredKeys.push(key);
}
}
for (const action of micro.objectCollection.objects) {
// Get JSON for microflow
const json = action.toJSON();
// console.log(json); // <------ use if you need to find out something this script is reporting but Studio Pro isn't
// Convert it to a String for searching
const jsonString = JSON.stringify(json);
for (const usagesKey of filteredKeys) {
// Search for a reference in action of microflow to Module Name which is represented with each key in map "usages"
// and append to value array if it isn't already in there
if (jsonString.includes(usagesKey) && !usages.get(microModule)?.includes(usagesKey)) {
usages.get(microModule)?.push(usagesKey);
}
}
}
}
return usages;
}
async function getPageUsages(pageInterfaces: pages.IPage[], usages: Map<string, string[]>) {
console.log(`Getting page usages...`)
// Initialize variable to hold module name to which page belongs
var pageModule: any;
for (const pageInterface of pageInterfaces) {
// Load page to get all properties
const page = await pageInterface.load();
// Derive module name to which page belongs from fully qualified name, which would be for example Login_EndUser.Login_Page
pageModule = page.qualifiedName?.substring(0, page.qualifiedName.indexOf("."));
// Get reverse engineered JavaScript that generates page which seems to be the easiest way to
// get all different widgets and containers for the page
const js = JavaScriptSerializer.serializeToJs(page);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude pageModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== pageModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in page JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(pageModule)?.includes(usagesKey)) {
usages.get(pageModule)?.push(usagesKey);
}
}
}
return usages;
}
async function getSnippetUsages(snippetInterfaces: pages.ISnippet[], usages: Map<string, string[]>) {
console.log(`Getting snippet usages...`)
// Initialize variable to hold module name to which snippet belongs
var snippetModule: any;
for (const snippetInterface of snippetInterfaces) {
// Load snippet to get all properties
const snippet = await snippetInterface.load();
// Derive module name to which snippet belongs from fully qualified name, which would be for example ContentEditor_Core.SNIP_MobileButton01
snippetModule = snippet.qualifiedName?.substring(0, snippet.qualifiedName.indexOf("."));
// Get reverse engineered JavaScript that generates snippet
const js = JavaScriptSerializer.serializeToJs(snippet);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude snippetModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== snippetModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in snippet JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(snippetModule)?.includes(usagesKey)) {
usages.get(snippetModule)?.push(usagesKey);
}
}
}
return usages;
}
async function getDomainModelUsages(domainModelInterfaces: domainmodels.IDomainModel[], usages: Map<string, string[]>) {
console.log(`Getting domain model usages...`)
// Initialize variable to hold module name to which domain model belongs
var domainModelModule: any;
for (const domainModelInterface of domainModelInterfaces) {
// Load domain model to get all properties
const domainModel = await domainModelInterface.load();
// Get module name from container name
domainModelModule = domainModel.containerAsModule.name;
// Get reverse engineered JavaScript that generates domain model
const js = JavaScriptSerializer.serializeToJs(domainModel);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude domainModelModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== domainModelModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in domain module JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(domainModelModule)?.includes(usagesKey)) {
usages.get(domainModelModule)?.push(usagesKey);
}
}
}
return usages;
}
async function getNanoflowUsages(nanoflowInterfaces: microflows.INanoflow[], usages: Map<string, string[]>) {
console.log(`Getting nanoflow usages...`)
// Initialize variable to hold module name to which nanoflow belongs
var nanoflowModule: any;
for (const nanoflowInterface of nanoflowInterfaces) {
// Load nanoflow to get all properties
const nanoflow = await nanoflowInterface.load();
// Derive module name to which nanoflow belongs from fully qualified name, which would be for example QRManager_EndUser.ACT_Group_CreatWizard_ShortcutValidation
nanoflowModule = nanoflow.qualifiedName?.substring(0, nanoflow.qualifiedName.indexOf("."));
// Get reverse engineered JavaScript that generates nanoflow
const js = JavaScriptSerializer.serializeToJs(nanoflow);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude nanoflowModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== nanoflowModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in nanoflow JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(nanoflowModule)?.includes(usagesKey)) {
usages.get(nanoflowModule)?.push(usagesKey);
}
}
}
return usages;
}
async function getLayoutUsages(layoutInterfaces: pages.ILayout[], usages: Map<string, string[]>) {
console.log(`Getting layout usages...`)
// Initialize variable to hold module name to which layout belongs
var layoutModule: any;
for (const layoutInterface of layoutInterfaces) {
// Load layout to get all properties
const layout = await layoutInterface.load();
// Derive module name to which layout belongs from fully qualified name
layoutModule = layout.qualifiedName?.substring(0, layout.qualifiedName.indexOf("."));
// Get reverse engineered JavaScript that generates layout
const js = JavaScriptSerializer.serializeToJs(layout);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude layoutModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== layoutModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in layout JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(layoutModule)?.includes(usagesKey)) {
usages.get(layoutModule)?.push(usagesKey);
}
}
}
return usages;
}
async function getDocumentTemplateUsages(documentTemplateInterfaces: documenttemplates.IDocumentTemplate[], usages: Map<string, string[]>) {
console.log(`Getting document template usages...`)
// Initialize variable to hold module name to which documentTemplate belongs
var documentTemplateModule: any;
for (const documentTemplateInterface of documentTemplateInterfaces) {
// Load documentTemplate to get all properties
const documentTemplate = await documentTemplateInterface.load();
// Derive module name to which documentTemplate belongs from fully qualified name
documentTemplateModule = documentTemplate.qualifiedName?.substring(0, documentTemplate.qualifiedName.indexOf("."));
// Get reverse engineered JavaScript that generates documentTemplate
const js = JavaScriptSerializer.serializeToJs(documentTemplate);
// Convert it to a String for searching
const jsString = JSON.stringify(js);
// Using a for-of loop to exclude documentTemplateModule from "usages" map so usages refer to those of other modules
// and we don't get the result that the module is using itself
const filteredKeys: string[] = [];
for (const key of usages.keys()) {
if (key !== documentTemplateModule) {
filteredKeys.push(key);
}
}
for (const usagesKey of filteredKeys) {
// Search for a reference in documentTemplate JavaScript to Module with name of usagesKey
// and append to value array if it isn't already in there
if (jsString.includes(usagesKey) && !usages.get(documentTemplateModule)?.includes(usagesKey)) {
usages.get(documentTemplateModule)?.push(usagesKey);
}
}
}
return usages;
}
main().catch(console.error);
It eventually outputs a neat map with all modules and their references to other modules, similar to what the “Find other module usages by this module” function in Studio Pro would do.
PS > node .\script.js
Creating temporary working copy for branch 'main'...
Successfully created temporary working copy with id '90ab5b0c-0906-4987-9222-6c0be19221b7' based on branch 'main'
Getting page usages...
Getting snippet usages...
Getting domain model usages...
Getting layout usages...
Getting document template usages...
Getting nanoflow usages...
Getting microflow usages...
Map(34) {
'ForgotPassword' => [
'Atlas_Core',
'Email_Connector',
'Administration',
'Encryption',
'DeepLink',
'MxModelReflection',
'Login_EndUser'
],
'ModuleTemplate_EndUser' => [
'ModuleTemplate_Core',
'DonQR_Theming',
'Atlas_Core',
'ContentEditor_Core'
],
'FeedbackModule' => [],
'Atlas_Web_Content' => [],
'ExcelImporter' => [ 'Atlas_Core', 'MxModelReflection' ],
'DragAndDrop_Foundation' => [],
'Administration' => [ 'Atlas_Core' ],
'AppSetup_Foundation' => [ 'DeepLink' ],
'QRManager_EndUser' => [
'QRGenerator_Core',
'ContentEditor_Core',
'QRManager_Core',
'DonQR_Theming',
'ForgotPassword',
'Atlas_Core',
'XLSReport',
'ImageCrop',
'DragAndDrop_Foundation',
'NanoflowCommons',
'WebActions',
'CommunityCommons',
'QRGenerator_Foundation',
'Login_Core'
],
'CommunityCommons' => [],
'Atlas_Core' => [],
'QRGenerator_Foundation' => [],
'WebActions' => [],
'QRConnect_EndUser' => [
'ContentEditor_Core',
'QRManager_Core',
'DonQR_Theming',
'QRGenerator_Core',
'RESTDeeplinkModule',
'CommunityCommons'
],
'XLSReport' => [ 'Atlas_Core', 'MxModelReflection' ],
'QRGenerator_Core' => [
'QRManager_Core',
'QRGenerator_Foundation',
'CommunityCommons',
'AppSetup_Foundation',
'Barcode'
],
'RESTDeeplinkModule' => [ 'DeepLink' ],
'AppSetup_EndUser' => [
'QRManager_Core',
'DonQR_Theming',
'Atlas_Core',
'ImageCrop',
'TaskQueueHelpers',
'ContentEditor_Core'
],
'Barcode' => [],
'Encryption' => [],
'Login_EndUser' => [ 'Administration', 'Login_Core', 'DonQR_Theming', 'ForgotPassword' ],
'Login_Core' => [ 'Administration' ],
'DomainModel_MockUp' => [],
'DeepLink' => [],
'Email_Connector' => [ 'Atlas_Core', 'Encryption', 'MxModelReflection', 'ForgotPassword' ],
'ContentEditor_Core' => [
'Atlas_Core',
'DonQR_Theming',
'QRGenerator_Core',
'ImageCrop',
'QRManager_Core',
'ModuleTemplate_Core',
'NanoflowCommons',
'CommunityCommons',
'DragAndDrop_Foundation'
],
'ImageCrop' => [],
'QRManager_Core' => [
'QRGenerator_Core',
'Login_Core',
'DonQR_Theming',
'QRManager_EndUser',
'XLSReport',
'CommunityCommons',
'ContentEditor_Core'
],
'ModuleTemplate_Core' => [],
'NanoflowCommons' => [],
'DonQR_Theming' => [ 'Login_Core', 'Atlas_Core', 'QRManager_EndUser' ],
'DataWidgets' => [],
'MxModelReflection' => [],
'TaskQueueHelpers' => [ 'Atlas_Core' ]
}
Would be interested to know if anyone can come up with any more efficient way to do this.