Skip to content
Open
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
14 changes: 14 additions & 0 deletions .pnpmfile.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
function beforePacking(pkg) {
// Remove bundled dependency from published package
delete pkg.dependencies['@mendix/widget-typings-generator'];

console.log('✓ Removed bundled dependency from package.json');

return pkg;
}

module.exports = {
hooks: {
beforePacking
}
};
2 changes: 1 addition & 1 deletion packages/command-tests/commands.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ async function main() {
console.log("Preparing...");

const pluggableWidgetsToolsPath = "../pluggable-widgets-tools";
const { stdout: packOutput } = await execAsync("npm pack", join(__dirname, pluggableWidgetsToolsPath));
const { stdout: packOutput } = await execAsync("pnpm pack", join(__dirname, pluggableWidgetsToolsPath));
const toolsPackagePath = join(__dirname, pluggableWidgetsToolsPath, packOutput.trim().split(/\n/g).pop());

const workDirs = [];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import { promises as fs } from "fs";
import { extname, join } from "path";
import { listDir } from "./shared.mjs";

const { transformPackage } = await import(new URL("../dist/typings-generator/index.js", import.meta.url));
import { transformPackage } from "../dist/widget-typings-generator.js";

export function widgetTyping({ sourceDir }) {
let firstRun = true;
Expand Down
1 change: 0 additions & 1 deletion packages/pluggable-widgets-tools/jest.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ const webConfig = {
rootDir: ".",
testMatch: [
"<rootDir>/src/web/**/*.spec.{ts,tsx}",
"<rootDir>/src/typings-generator/**/*.spec.{ts,tsx}",
"<rootDir>/src/utils/**/*.spec.{ts,tsx}"
]
};
Expand Down
4 changes: 3 additions & 1 deletion packages/pluggable-widgets-tools/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"pluggable-widgets-tools": "bin/mx-scripts.js"
},
"scripts": {
"prepack": "shx rm -rf dist && tsc",
"build:typings": "pnpm --filter=@mendix/widget-typings-generator run build",
"prepack": "shx rm -rf dist && pnpm build:typings && tsc && rollup -c prepack.rollup.mjs",
"test": "jest"
},
"files": [
Expand All @@ -25,6 +26,7 @@
"utils"
],
"dependencies": {
"@mendix/widget-typings-generator": "workspace:*",
"@babel/core": "^7.26.0",
"@babel/plugin-transform-class-properties": "^7.25.9",
"@babel/plugin-transform-private-methods": "^7.25.9",
Expand Down
21 changes: 21 additions & 0 deletions packages/pluggable-widgets-tools/prepack.rollup.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { nodeResolve } from '@rollup/plugin-node-resolve';
import commonjs from '@rollup/plugin-commonjs';

export default [
{
treeshake: false,
input: 'node_modules/@mendix/widget-typings-generator/dist/index.js',
output: {
file: 'dist/widget-typings-generator.js',
format: 'cjs',
exports: 'named',
sourcemap: false
},
plugins: [
commonjs(),
nodeResolve({
preferBuiltins: true
}),
]
}
];
5 changes: 3 additions & 2 deletions packages/pluggable-widgets-tools/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
export * from "./common";
export * from "./native/common";
export * from "./web/common";
export * from "./utils/typings";
export * from "./utils";
export * from "./utils/typings";
export * from "./web/common";

81 changes: 81 additions & 0 deletions packages/widget-typings-generator/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# @mendix/widget-typings-generator

TypeScript typings generator for Mendix Pluggable Widgets.

## Overview

Generates TypeScript definition files (`.d.ts`) from Mendix widget XML schema files, providing type-safe development experience for widget creators.

## Usage

### Basic Usage

```typescript
import { transformPackage } from "@mendix/widget-typings-generator";
import { readFile } from "fs/promises";

// Read your widget's package.xml
const packageXml = await readFile("./src/{WidgetName}.xml", "utf-8");

// Generate TypeScript definitions
await transformPackage(packageXml, "./src");
```

This will:
1. Parse `{WidgetName}.xml` to find all widget XML files
2. Generate TypeScript interfaces for each widget
3. Create `.d.ts` files in `../typings/{WidgetName}.xml/` directory

### Generated Types

For a widget named `MyWidget`, it generates:
- **Web:** `MyWidgetContainerProps` (runtime) and `MyWidgetPreviewProps` (Studio Pro)
- **Native:** `MyWidgetProps<Style>` (runtime) and `MyWidgetPreviewProps` (Studio Pro)

### Integration with Rollup

```javascript
import { transformPackage } from "@mendix/widget-typings-generator";
import { readFile } from "fs/promises";

export function widgetTypingPlugin({ sourceDir }) {
return {
name: "widget-typing",
async buildStart() {
const packageXml = await readFile(`${sourceDir}/package.xml`, "utf-8");
await transformPackage(packageXml, sourceDir);
}
};
}
```

## API

### `transformPackage(content: string, basePath: string): Promise<void>`

Parses package.xml and generates TypeScript definitions for all widgets.

**Parameters:**
- `content` - XML content of package.xml file
- `basePath` - Base directory path for resolving widget XML files

**Returns:** Promise that resolves when all type files are generated

**Throws:** Error if XML parsing fails or widget structure is invalid

## Supported Property Types

The generator supports all Mendix widget property types:
- Actions (single, list, with variables)
- Attributes (string, boolean, integer, decimal, datetime, etc.)
- Associations (reference, reference set)
- Datasources (with filtering, sorting)
- Expressions (with type validation)
- Objects (nested properties)
- Icons, Images, Files
- Enumerations, Selections
- Text templates, Widgets (containment)

## License

Apache-2.0 © Mendix Technology BV 2026. All rights reserved.
16 changes: 16 additions & 0 deletions packages/widget-typings-generator/build.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { build } from 'esbuild';
import { execSync } from 'child_process';

await build({
entryPoints: ['src/index.ts'],
bundle: true,
platform: 'node',
target: 'node20',
format: 'esm',
outfile: 'dist/index.js',
external: ['xml2js'],
sourcemap: false,
minify: false
});

console.log('✓ Bundled with esbuild');
17 changes: 17 additions & 0 deletions packages/widget-typings-generator/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
export default {
preset: "ts-jest/presets/default-esm",
testEnvironment: "node",
testMatch: ["**/__tests__/**/*.spec.ts"],
extensionsToTreatAsEsm: [".ts"],
moduleNameMapper: {
"^(\\.{1,2}/.*)\\.js$": "$1"
},
transform: {
"^.+\\.tsx?$": [
"ts-jest",
{
useESM: true
}
]
}
};
47 changes: 47 additions & 0 deletions packages/widget-typings-generator/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "@mendix/widget-typings-generator",
"version": "1.0.0",
"description": "TypeScript typings generator for Mendix Pluggable Widgets",
"type": "module",
"engines": {
"node": ">=20"
},
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"test": "jest",
"build": "premove dist && node build.mjs"
},
"files": [
"dist",
"src"
],
"dependencies": {
"xml2js": "^0.6.2"
},
"devDependencies": {
"@types/jest": "^30.0.0",
"@types/node": "^16.18.126",
"@types/xml2js": "^0.4.5",
"esbuild": "^0.24.2",
"jest": "^29.4.1",
"premove": "^4.0.0",
"ts-jest": "^29.2.5",
"typescript": "^5.6.3"
},
"keywords": [
"mendix",
"widgets",
"typescript",
"typings",
"generator",
"pluggable-widgets"
],
"license": "Apache-2.0",
"copyright": "© Mendix Technology BV 2026. All rights reserved.",
"homepage": "https://github.com/mendix/widgets-tools/tree/master/packages/widget-typings-generator",
"repository": {
"type": "git",
"url": "https://github.com/mendix/widgets-tools/"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ function generateNativeTypesFor(xml: string) {
function convertXmltoJson(xml: string): WidgetXml {
let content: WidgetXml = {};
if (xml) {
parseString(xml, {}, (err: Error, result: any) => {
parseString(xml, {}, (err: Error | null, result: any) => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hello Ilia 👋 Why do we need the null here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IDK, probably just to avoid ts error.

if (err) {
throw err;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ export async function transformPackage(content: string, basePath: string) {
const sourceXml = (await parseStringPromise(source)) as WidgetXml;
generatedContent = generateForWidget(sourceXml, toWidgetName(sourcePath));
} catch (err) {
const message = err instanceof Error ? err.message : String(err);
throw new Error(
`Incorrect widget xml file ${sourcePath}, please check Mendix Documentation: ${err.message}`
`Incorrect widget xml file ${sourcePath}, please check Mendix Documentation: ${message}`
);
}

Expand Down
21 changes: 21 additions & 0 deletions packages/widget-typings-generator/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"compilerOptions": {
"target": "ES2020",
"module": "ES2020",
"lib": ["ES2020"],
"moduleResolution": "node",
"outDir": "./dist",
"rootDir": "./src",
"declaration": true,
"declarationMap": true,
"sourceMap": true,
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true
},
"include": ["src/**/*"],
"exclude": ["src/**/*.spec.ts", "src/**/__tests__"]
}
Loading
Loading