Skip to content

Commit d09a459

Browse files
committed
chore: better api for widget package.xml
1 parent 71f6a22 commit d09a459

File tree

3 files changed

+184
-0
lines changed

3 files changed

+184
-0
lines changed
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
import { XMLBuilder, XMLParser } from "fast-xml-parser";
2+
import { readFile, writeFile } from "fs/promises";
3+
import { Version, VersionString } from "../version";
4+
import { ClientModulePackageFile } from "./schema";
5+
6+
export function xmlTextToXmlJson(xmlText: string | Buffer): unknown {
7+
const parser = new XMLParser({ ignoreAttributes: false });
8+
return parser.parse(xmlText);
9+
}
10+
11+
export function xmlJsonToXmlText(xmlObject: any): string {
12+
const builder = new XMLBuilder({
13+
ignoreAttributes: false,
14+
format: true,
15+
indentBy: " ",
16+
suppressEmptyNode: true
17+
});
18+
return builder
19+
.build(xmlObject)
20+
.replaceAll(/(<[^>]*?)\/>/g, "$1 />") // Add space before /> in self-closing tags
21+
.replaceAll(/(<\?[^>]*?)\?>/g, "$1 ?>"); // Add space before ?> in XML declarations
22+
}
23+
24+
export interface ClientPackageXML {
25+
name: string;
26+
version: Version;
27+
widgetFiles: string[];
28+
files: string[];
29+
}
30+
31+
export async function readClientPackageXml(path: string): Promise<ClientPackageXML> {
32+
return parseClientPackageXml(ClientModulePackageFile.passthrough().parse(xmlTextToXmlJson(await readFile(path))));
33+
}
34+
35+
export async function writeClientPackageXml(path: string, data: ClientPackageXML): Promise<void> {
36+
await writeFile(path, xmlJsonToXmlText(buildClientPackageXml(data)));
37+
}
38+
39+
function parseClientPackageXml(xmlJson: ClientModulePackageFile): ClientPackageXML {
40+
const clientModule = xmlJson?.package?.clientModule ?? {};
41+
const widgetFilesNode = clientModule.widgetFiles !== "" ? clientModule.widgetFiles?.widgetFile : undefined;
42+
const filesNode = clientModule.files !== "" ? clientModule.files?.file : undefined;
43+
44+
const extractPaths = (node: any): string[] => {
45+
if (!node) return [];
46+
if (Array.isArray(node)) {
47+
return node.map((item: any) => item["@_path"]);
48+
}
49+
return [node["@_path"]];
50+
};
51+
52+
const name = clientModule["@_name"] ?? "";
53+
const versionString = clientModule["@_version"] ?? "1.0.0";
54+
55+
return {
56+
name,
57+
version: Version.fromString(versionString as VersionString),
58+
widgetFiles: extractPaths(widgetFilesNode),
59+
files: extractPaths(filesNode)
60+
};
61+
}
62+
63+
function buildClientPackageXml(clientPackage: ClientPackageXML): ClientModulePackageFile {
64+
const toXmlNode = <T extends "file" | "widgetFile">(arr: string[], tag: T) => {
65+
if (arr.length === 0) return "";
66+
if (arr.length === 1) {
67+
return { [tag]: { "@_path": arr[0] } };
68+
}
69+
return { [tag]: arr.map(path => ({ "@_path": path })) };
70+
};
71+
72+
return {
73+
"?xml": {
74+
"@_version": "1.0",
75+
"@_encoding": "utf-8"
76+
},
77+
package: {
78+
clientModule: {
79+
widgetFiles: toXmlNode(
80+
clientPackage.widgetFiles,
81+
"widgetFile"
82+
) as ClientModulePackageFile["package"]["clientModule"]["widgetFiles"],
83+
files: toXmlNode(
84+
clientPackage.files,
85+
"file"
86+
) as ClientModulePackageFile["package"]["clientModule"]["files"],
87+
"@_name": clientPackage.name,
88+
"@_version": clientPackage.version.format(),
89+
"@_xmlns": "http://www.mendix.com/clientModule/1.0/"
90+
},
91+
"@_xmlns": "http://www.mendix.com/package/1.0/"
92+
}
93+
};
94+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { z } from "zod";
2+
import { xmlTextToXmlJson } from "./index";
3+
import { readFile } from "node:fs/promises";
4+
5+
export const PropertiesXMLFile = z.object({
6+
"?xml": z.object({
7+
"@_version": z.literal("1.0"),
8+
"@_encoding": z.literal("utf-8")
9+
}),
10+
widget: z.object({
11+
"@_id": z.string(),
12+
"@_xmlns": z.literal("http://www.mendix.com/widget/1.0/"),
13+
"@_xmlns:xsi": z.literal("http://www.w3.org/2001/XMLSchema-instance")
14+
})
15+
});
16+
17+
type PropertiesXMLFile = z.infer<typeof PropertiesXMLFile>;
18+
19+
export async function readPropertiesFile(filePath: string): Promise<PropertiesXMLFile> {
20+
return PropertiesXMLFile.passthrough().parse(xmlTextToXmlJson(await readFile(filePath, "utf-8")));
21+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { z } from "zod";
2+
3+
const FileTag = z.object({
4+
"@_path": z.string()
5+
});
6+
7+
const FileNode = z.union([FileTag, FileTag.array()]);
8+
9+
export const ModelerProjectPackageFile = z.object({
10+
"?xml": z.object({
11+
"@_version": z.literal("1.0"),
12+
"@_encoding": z.literal("utf-8")
13+
}),
14+
package: z.object({
15+
"@_xmlns": z.literal("http://www.mendix.com/package/1.0/"),
16+
modelerProject: z.object({
17+
"@_xmlns": z.literal("http://www.mendix.com/modelerProject/1.0/"),
18+
module: z.object({
19+
"@_name": z.string()
20+
}),
21+
projectFile: z.object({
22+
"@_path": z.string()
23+
}),
24+
files: z.union([
25+
z.literal(""),
26+
z.object({
27+
file: FileNode
28+
})
29+
])
30+
})
31+
})
32+
});
33+
34+
export type ModelerProjectPackageFile = z.infer<typeof ModelerProjectPackageFile>;
35+
36+
export const ClientModulePackageFile = z.object({
37+
"?xml": z.object({
38+
"@_version": z.literal("1.0"),
39+
"@_encoding": z.literal("utf-8")
40+
}),
41+
package: z.object({
42+
"@_xmlns": z.literal("http://www.mendix.com/package/1.0/"),
43+
clientModule: z.object({
44+
"@_name": z.string({
45+
required_error: "name attribute is required"
46+
}),
47+
"@_version": z.string({
48+
required_error: "version attribute is required"
49+
}),
50+
"@_xmlns": z.literal("http://www.mendix.com/clientModule/1.0/"),
51+
52+
files: z.union([
53+
z.literal(""),
54+
z.object({
55+
file: FileNode
56+
})
57+
]),
58+
59+
widgetFiles: z.union([
60+
z.literal(""),
61+
z.object({
62+
widgetFile: FileNode
63+
})
64+
])
65+
})
66+
})
67+
});
68+
69+
export type ClientModulePackageFile = z.infer<typeof ClientModulePackageFile>;

0 commit comments

Comments
 (0)