Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
59 changes: 59 additions & 0 deletions automation/utils/bin/rui-generate-package-xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
#!/usr/bin/env ts-node

import { writeClientPackageXml, ClientPackageXML } from "../src/package-xml-v2";
import { getWidgetInfo } from "../src/package-info";
import { readPropertiesFile } from "../src/package-xml-v2/properties-xml";
import path from "node:path";
import { existsSync } from "node:fs";

async function generatePackageXml(): Promise<void> {
const widgetDir = process.cwd();

// Read package.json info
const packageInfo = await getWidgetInfo(widgetDir);

const srcDir = path.join(widgetDir, "src");

// Create src directory if it doesn't exist
if (!existsSync(srcDir)) {
throw new Error(`Src folder not found: ${srcDir}`);
}

// Get properties file name from package.json (mxpackage.name + ".xml")
const propertiesFileName = packageInfo.mxpackage.name + ".xml";
const propertiesFilePath = path.join(srcDir, propertiesFileName);

// Properties file must exist
if (!existsSync(propertiesFilePath)) {
throw new Error(`Properties file not found: ${propertiesFilePath}`);
}

// Read properties file and extract widget ID
const propertiesXml = await readPropertiesFile(propertiesFilePath);
const widgetId = propertiesXml.widget["@_id"];

// Generate ClientPackageXML structure
const clientPackageXml: ClientPackageXML = {
name: packageInfo.mxpackage.name,
version: packageInfo.version,
widgetFiles: [packageInfo.mxpackage.name + ".xml"],
files: [widgetId.split(".").slice(0, -1).join("/") + "/"]
};

// Write the generated package.xml
const packageXmlPath = path.join(srcDir, "package.xml");
await writeClientPackageXml(packageXmlPath, clientPackageXml);
}

async function main() {
try {
await generatePackageXml();
} catch (error) {
console.error("Error generating package.xml:", error instanceof Error ? error.message : String(error));
process.exit(1);
}
}

if (require.main === module) {
main();
}
1 change: 1 addition & 0 deletions automation/utils/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
"rui-agent-rules": "bin/rui-agent-rules.ts",
"rui-create-gh-release": "bin/rui-create-gh-release.ts",
"rui-create-translation": "bin/rui-create-translation.ts",
"rui-generate-package-xml": "bin/rui-generate-package-xml.ts",
"rui-prepare-release": "bin/rui-prepare-release.ts",
"rui-publish-marketplace": "bin/rui-publish-marketplace.ts",
"rui-update-changelog-module": "bin/rui-update-changelog-module.ts",
Expand Down
2 changes: 1 addition & 1 deletion automation/utils/src/package-info.ts
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ export const PackageSchema = z.object({
appNumber: true
}),
repository: RepositorySchema,
testProject: TestProjectSchema
testProject: TestProjectSchema.optional()
});

export const PublishedPackageSchema = PackageSchema.extend({
Expand Down
94 changes: 94 additions & 0 deletions automation/utils/src/package-xml-v2/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { XMLBuilder, XMLParser } from "fast-xml-parser";
import { readFile, writeFile } from "fs/promises";
import { Version, VersionString } from "../version";
import { ClientModulePackageFile } from "./schema";

export function xmlTextToXmlJson(xmlText: string | Buffer): unknown {
const parser = new XMLParser({ ignoreAttributes: false });
return parser.parse(xmlText);
}

export function xmlJsonToXmlText(xmlObject: any): string {
const builder = new XMLBuilder({
ignoreAttributes: false,
format: true,
indentBy: " ",
suppressEmptyNode: true
});
return builder
.build(xmlObject)
.replaceAll(/(<[^>]*?)\/>/g, "$1 />") // Add space before /> in self-closing tags
.replaceAll(/(<\?[^>]*?)\?>/g, "$1 ?>"); // Add space before ?> in XML declarations
}

export interface ClientPackageXML {
name: string;
version: Version;
widgetFiles: string[];
files: string[];
}

export async function readClientPackageXml(path: string): Promise<ClientPackageXML> {
return parseClientPackageXml(ClientModulePackageFile.passthrough().parse(xmlTextToXmlJson(await readFile(path))));
}

export async function writeClientPackageXml(path: string, data: ClientPackageXML): Promise<void> {
await writeFile(path, xmlJsonToXmlText(buildClientPackageXml(data)));
}

function parseClientPackageXml(xmlJson: ClientModulePackageFile): ClientPackageXML {
const clientModule = xmlJson?.package?.clientModule ?? {};
const widgetFilesNode = clientModule.widgetFiles !== "" ? clientModule.widgetFiles?.widgetFile : undefined;
const filesNode = clientModule.files !== "" ? clientModule.files?.file : undefined;

const extractPaths = (node: any): string[] => {
if (!node) return [];
if (Array.isArray(node)) {
return node.map((item: any) => item["@_path"]);
}
return [node["@_path"]];
};

const name = clientModule["@_name"] ?? "";
const versionString = clientModule["@_version"] ?? "1.0.0";

return {
name,
version: Version.fromString(versionString as VersionString),
widgetFiles: extractPaths(widgetFilesNode),
files: extractPaths(filesNode)
};
}

function buildClientPackageXml(clientPackage: ClientPackageXML): ClientModulePackageFile {
const toXmlNode = <T extends "file" | "widgetFile">(arr: string[], tag: T) => {
if (arr.length === 0) return "";
if (arr.length === 1) {
return { [tag]: { "@_path": arr[0] } };
}
return { [tag]: arr.map(path => ({ "@_path": path })) };
};

return {
"?xml": {
"@_version": "1.0",
"@_encoding": "utf-8"
},
package: {
clientModule: {
widgetFiles: toXmlNode(
clientPackage.widgetFiles,
"widgetFile"
) as ClientModulePackageFile["package"]["clientModule"]["widgetFiles"],
files: toXmlNode(
clientPackage.files,
"file"
) as ClientModulePackageFile["package"]["clientModule"]["files"],
"@_name": clientPackage.name,
"@_version": clientPackage.version.format(),
"@_xmlns": "http://www.mendix.com/clientModule/1.0/"
},
"@_xmlns": "http://www.mendix.com/package/1.0/"
}
};
}
21 changes: 21 additions & 0 deletions automation/utils/src/package-xml-v2/properties-xml.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { z } from "zod";
import { xmlTextToXmlJson } from "./index";
import { readFile } from "node:fs/promises";

export const PropertiesXMLFile = z.object({
"?xml": z.object({
"@_version": z.literal("1.0"),
"@_encoding": z.literal("utf-8")
}),
widget: z.object({
"@_id": z.string(),
"@_xmlns": z.literal("http://www.mendix.com/widget/1.0/"),
"@_xmlns:xsi": z.literal("http://www.w3.org/2001/XMLSchema-instance")
})
});

type PropertiesXMLFile = z.infer<typeof PropertiesXMLFile>;

export async function readPropertiesFile(filePath: string): Promise<PropertiesXMLFile> {
return PropertiesXMLFile.passthrough().parse(xmlTextToXmlJson(await readFile(filePath, "utf-8")));
}
69 changes: 69 additions & 0 deletions automation/utils/src/package-xml-v2/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { z } from "zod";

const FileTag = z.object({
"@_path": z.string()
});

const FileNode = z.union([FileTag, FileTag.array()]);

export const ModelerProjectPackageFile = z.object({
"?xml": z.object({
"@_version": z.literal("1.0"),
"@_encoding": z.literal("utf-8")
}),
package: z.object({
"@_xmlns": z.literal("http://www.mendix.com/package/1.0/"),
modelerProject: z.object({
"@_xmlns": z.literal("http://www.mendix.com/modelerProject/1.0/"),
module: z.object({
"@_name": z.string()
}),
projectFile: z.object({
"@_path": z.string()
}),
files: z.union([
z.literal(""),
z.object({
file: FileNode
})
])
})
})
});

export type ModelerProjectPackageFile = z.infer<typeof ModelerProjectPackageFile>;

export const ClientModulePackageFile = z.object({
"?xml": z.object({
"@_version": z.literal("1.0"),
"@_encoding": z.literal("utf-8")
}),
package: z.object({
"@_xmlns": z.literal("http://www.mendix.com/package/1.0/"),
clientModule: z.object({
"@_name": z.string({
required_error: "name attribute is required"
}),
"@_version": z.string({
required_error: "version attribute is required"
}),
"@_xmlns": z.literal("http://www.mendix.com/clientModule/1.0/"),

files: z.union([
z.literal(""),
z.object({
file: FileNode
})
]),

widgetFiles: z.union([
z.literal(""),
z.object({
widgetFile: FileNode
})
])
})
})
});

export type ClientModulePackageFile = z.infer<typeof ClientModulePackageFile>;
4 changes: 2 additions & 2 deletions automation/utils/src/steps.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,8 @@ export async function cloneTestProject({ info, config }: CommonStepParams): Prom
const clone = process.env.CI ? cloneRepoShallow : cloneRepo;
rm("-rf", config.paths.targetProject);
await clone({
remoteUrl: testProject.githubUrl,
branch: testProject.branchName,
remoteUrl: testProject!.githubUrl,
branch: testProject!.branchName,
localFolder: config.paths.targetProject
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="AccessibilityHelper.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/accessibilityhelper" />
<file path="com/mendix/widget/web/accessibilityhelper/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/accordion-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="Accordion.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/accordion" />
<file path="com/mendix/widget/web/accordion/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/badge-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="Badge.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/custom/badge" />
<file path="com/mendix/widget/custom/badge/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/bar-chart-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="BarChart.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/barchart" />
<file path="com/mendix/widget/web/barchart/" />
</files>
</clientModule>
</package>
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<?xml version="1.0" encoding="utf-8" ?>
<package xmlns="http://www.mendix.com/package/1.0/">
<clientModule name="Barcode Scanner" version="2.4.2" xmlns="http://www.mendix.com/clientModule/1.0/">
<clientModule name="BarcodeScanner" version="2.4.2" xmlns="http://www.mendix.com/clientModule/1.0/">
<widgetFiles>
<widgetFile path="BarcodeScanner.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/barcodescanner" />
<file path="com/mendix/widget/web/barcodescanner/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/calendar-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="Calendar.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/calendar" />
<file path="com/mendix/widget/web/calendar/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/carousel-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="Carousel.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/custom/carousel" />
<file path="com/mendix/widget/custom/carousel/" />
</files>
</clientModule>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="CheckboxRadioSelection.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/checkboxradioselection" />
<file path="com/mendix/widget/web/checkboxradioselection/" />
</files>
</clientModule>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="ColorPicker.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/custom/colorpicker" />
<file path="com/mendix/widget/custom/colorpicker/" />
</files>
</clientModule>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="ColumnChart.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/columnchart" />
<file path="com/mendix/widget/web/columnchart/" />
</files>
</clientModule>
</package>
2 changes: 1 addition & 1 deletion packages/pluggableWidgets/combobox-web/src/package.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="Combobox.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/combobox" />
<file path="com/mendix/widget/web/combobox/" />
</files>
</clientModule>
</package>
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<widgetFile path="CustomChart.xml" />
</widgetFiles>
<files>
<file path="com/mendix/widget/web/customchart" />
<file path="com/mendix/widget/web/customchart/" />
</files>
</clientModule>
</package>
Loading
Loading