diff --git a/.husky/pre-commit b/.husky/pre-commit
new file mode 100644
index 0000000..23b9a80
--- /dev/null
+++ b/.husky/pre-commit
@@ -0,0 +1 @@
+pnpm lint:fix
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..d6117c5
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,5 @@
+{
+ "editor.codeActionsOnSave": {
+ "source.fixAll.biome": "always"
+ }
+}
diff --git a/benchmark/benchmarkWidget/.eslintrc.js b/benchmark/benchmarkWidget/.eslintrc.js
index a81cc74..1bab70f 100644
--- a/benchmark/benchmarkWidget/.eslintrc.js
+++ b/benchmark/benchmarkWidget/.eslintrc.js
@@ -1,5 +1,5 @@
const base = require("@mendix/pluggable-widgets-tools/configs/eslint.ts.base.json");
module.exports = {
- ...base
+ ...base,
};
diff --git a/benchmark/benchmarkWidget/prettier.config.js b/benchmark/benchmarkWidget/prettier.config.js
index 013fbe7..abfef46 100644
--- a/benchmark/benchmarkWidget/prettier.config.js
+++ b/benchmark/benchmarkWidget/prettier.config.js
@@ -1,6 +1,10 @@
const base = require("@mendix/pluggable-widgets-tools/configs/prettier.base.json");
module.exports = {
- ...base,
- plugins: [require.resolve("@prettier/plugin-xml")],
+ ...base,
+ plugins: [
+ require.resolve(
+ "@prettier/plugin-xml",
+ ),
+ ],
};
diff --git a/benchmark/benchmarkWidget/src/BenchmarkWidget.editorConfig.ts b/benchmark/benchmarkWidget/src/BenchmarkWidget.editorConfig.ts
index e70f58b..6100b79 100644
--- a/benchmark/benchmarkWidget/src/BenchmarkWidget.editorConfig.ts
+++ b/benchmark/benchmarkWidget/src/BenchmarkWidget.editorConfig.ts
@@ -1,115 +1,144 @@
import { BenchmarkWidgetPreviewProps } from "../typings/BenchmarkWidgetProps";
-export type Platform = "web" | "desktop";
+export type Platform =
+ | "web"
+ | "desktop";
-export type Properties = PropertyGroup[];
+export type Properties =
+ PropertyGroup[];
-type PropertyGroup = {
+type PropertyGroup =
+ {
caption: string;
propertyGroups?: PropertyGroup[];
properties?: Property[];
-};
+ };
type Property = {
- key: string;
- caption: string;
- description?: string;
- objectHeaders?: string[]; // used for customizing object grids
- objects?: ObjectProperties[];
- properties?: Properties[];
+ key: string;
+ caption: string;
+ description?: string;
+ objectHeaders?: string[]; // used for customizing object grids
+ objects?: ObjectProperties[];
+ properties?: Properties[];
};
-type ObjectProperties = {
+type ObjectProperties =
+ {
properties: PropertyGroup[];
captions?: string[]; // used for customizing object grids
-};
+ };
-export type Problem = {
+export type Problem =
+ {
property?: string; // key of the property, at which the problem exists
- severity?: "error" | "warning" | "deprecation"; // default = "error"
+ severity?:
+ | "error"
+ | "warning"
+ | "deprecation"; // default = "error"
message: string; // description of the problem
studioMessage?: string; // studio-specific message, defaults to message
url?: string; // link with more information about the problem
studioUrl?: string; // studio-specific link
-};
+ };
type BaseProps = {
- type: "Image" | "Container" | "RowLayout" | "Text" | "DropZone" | "Selectable" | "Datasource";
- grow?: number; // optionally sets a growth factor if used in a layout (default = 1)
+ type:
+ | "Image"
+ | "Container"
+ | "RowLayout"
+ | "Text"
+ | "DropZone"
+ | "Selectable"
+ | "Datasource";
+ grow?: number; // optionally sets a growth factor if used in a layout (default = 1)
};
-type ImageProps = BaseProps & {
+type ImageProps =
+ BaseProps & {
type: "Image";
document?: string; // svg image
data?: string; // base64 image
property?: object; // widget image property object from Values API
width?: number; // sets a fixed maximum width
height?: number; // sets a fixed maximum height
-};
+ };
-type ContainerProps = BaseProps & {
- type: "Container" | "RowLayout";
+type ContainerProps =
+ BaseProps & {
+ type:
+ | "Container"
+ | "RowLayout";
children: PreviewProps[]; // any other preview element
borders?: boolean; // sets borders around the layout to visually group its children
borderRadius?: number; // integer. Can be used to create rounded borders
backgroundColor?: string; // HTML color, formatted #RRGGBB
borderWidth?: number; // sets the border width
padding?: number; // integer. adds padding around the container
-};
+ };
-type RowLayoutProps = ContainerProps & {
+type RowLayoutProps =
+ ContainerProps & {
type: "RowLayout";
- columnSize?: "fixed" | "grow"; // default is fixed
-};
+ columnSize?:
+ | "fixed"
+ | "grow"; // default is fixed
+ };
-type TextProps = BaseProps & {
+type TextProps =
+ BaseProps & {
type: "Text";
content: string; // text that should be shown
fontSize?: number; // sets the font size
fontColor?: string; // HTML color, formatted #RRGGBB
bold?: boolean;
italic?: boolean;
-};
+ };
-type DropZoneProps = BaseProps & {
+type DropZoneProps =
+ BaseProps & {
type: "DropZone";
property: object; // widgets property object from Values API
placeholder: string; // text to be shown inside the dropzone when empty
showDataSourceHeader?: boolean; // true by default. Toggles whether to show a header containing information about the datasource
-};
+ };
-type SelectableProps = BaseProps & {
+type SelectableProps =
+ BaseProps & {
type: "Selectable";
object: object; // object property instance from the Value API
child: PreviewProps; // any type of preview property to visualize the object instance
-};
+ };
-type DatasourceProps = BaseProps & {
+type DatasourceProps =
+ BaseProps & {
type: "Datasource";
- property: object | null; // datasource property object from Values API
+ property:
+ | object
+ | null; // datasource property object from Values API
child?: PreviewProps; // any type of preview property component (optional)
-};
+ };
export type PreviewProps =
- | ImageProps
- | ContainerProps
- | RowLayoutProps
- | TextProps
- | DropZoneProps
- | SelectableProps
- | DatasourceProps;
+ | ImageProps
+ | ContainerProps
+ | RowLayoutProps
+ | TextProps
+ | DropZoneProps
+ | SelectableProps
+ | DatasourceProps;
export function getProperties(
- _values: BenchmarkWidgetPreviewProps,
- defaultProperties: Properties /* , target: Platform*/
+ _values: BenchmarkWidgetPreviewProps,
+ defaultProperties: Properties /* , target: Platform*/,
): Properties {
- // Do the values manipulation here to control the visibility of properties in Studio and Studio Pro conditionally.
- /* Example
+ // Do the values manipulation here to control the visibility of properties in Studio and Studio Pro conditionally.
+ /* Example
if (values.myProperty === "custom") {
delete defaultProperties.properties.myOtherProperty;
}
*/
- return defaultProperties;
+ return defaultProperties;
}
// export function check(_values: BenchmarkWidgetPreviewProps): Problem[] {
diff --git a/benchmark/benchmarkWidget/src/BenchmarkWidget.editorPreview.tsx b/benchmark/benchmarkWidget/src/BenchmarkWidget.editorPreview.tsx
index 8ec2074..2ba5fe5 100644
--- a/benchmark/benchmarkWidget/src/BenchmarkWidget.editorPreview.tsx
+++ b/benchmark/benchmarkWidget/src/BenchmarkWidget.editorPreview.tsx
@@ -1,9 +1,17 @@
-import { ReactElement, createElement } from "react";
+import {
+ ReactElement,
+ createElement,
+} from "react";
export function preview(): ReactElement {
- return
Benchmark Widget
;
+ return (
+
+ Benchmark
+ Widget
+
+ );
}
export function getPreviewCss(): string {
- return require("./ui/BenchmarkWidget.css");
+ return require("./ui/BenchmarkWidget.css");
}
diff --git a/benchmark/benchmarkWidget/src/BenchmarkWidget.tsx b/benchmark/benchmarkWidget/src/BenchmarkWidget.tsx
index 0aded0f..d476dc3 100644
--- a/benchmark/benchmarkWidget/src/BenchmarkWidget.tsx
+++ b/benchmark/benchmarkWidget/src/BenchmarkWidget.tsx
@@ -1,8 +1,13 @@
-import { ReactElement, createElement } from "react";
+import {
+ ReactElement,
+ createElement,
+} from "react";
import { CalendarWidget } from "./components/CalendarWidget";
import "./ui/BenchmarkWidget.css";
export function BenchmarkWidget(): ReactElement {
- return ;
+ return (
+
+ );
}
diff --git a/benchmark/benchmarkWidget/src/components/CalendarWidget.tsx b/benchmark/benchmarkWidget/src/components/CalendarWidget.tsx
index bbbc2c9..6fb515c 100644
--- a/benchmark/benchmarkWidget/src/components/CalendarWidget.tsx
+++ b/benchmark/benchmarkWidget/src/components/CalendarWidget.tsx
@@ -1,18 +1,34 @@
-import { ReactElement, createElement } from "react";
-import { Calendar, dayjsLocalizer } from "react-big-calendar";
+import {
+ ReactElement,
+ createElement,
+} from "react";
+import {
+ Calendar,
+ dayjsLocalizer,
+} from "react-big-calendar";
import dayjs from "dayjs";
import "react-big-calendar/lib/css/react-big-calendar.css";
-const localizer = dayjsLocalizer(dayjs);
+const localizer =
+ dayjsLocalizer(
+ dayjs,
+ );
export function CalendarWidget(): ReactElement {
- return (
-
- );
+ return (
+
+ );
}
diff --git a/benchmark/benchmarkWidget/src/ui/BenchmarkWidget.css b/benchmark/benchmarkWidget/src/ui/BenchmarkWidget.css
index 1c2d0a2..950373b 100644
--- a/benchmark/benchmarkWidget/src/ui/BenchmarkWidget.css
+++ b/benchmark/benchmarkWidget/src/ui/BenchmarkWidget.css
@@ -2,5 +2,4 @@
Place your custom CSS here
*/
.widget-hello-world {
-
}
diff --git a/benchmark/benchmarkWidget/tsconfig.json b/benchmark/benchmarkWidget/tsconfig.json
index 80cca52..666fb0c 100644
--- a/benchmark/benchmarkWidget/tsconfig.json
+++ b/benchmark/benchmarkWidget/tsconfig.json
@@ -3,5 +3,8 @@
"compilerOptions": {
"baseUrl": "./"
},
- "include": ["./src", "./typings"]
+ "include": [
+ "./src",
+ "./typings"
+ ]
}
diff --git a/benchmark/benchmarkWidget/typings/BenchmarkWidgetProps.d.ts b/benchmark/benchmarkWidget/typings/BenchmarkWidgetProps.d.ts
index 93d71af..cb3a6fb 100644
--- a/benchmark/benchmarkWidget/typings/BenchmarkWidgetProps.d.ts
+++ b/benchmark/benchmarkWidget/typings/BenchmarkWidgetProps.d.ts
@@ -1,12 +1,12 @@
/**
* This file was automatically generated by @repixelcorp/hyper-pwt v0.3.1
* DO NOT MODIFY THIS FILE DIRECTLY
- *
+ *
* To regenerate this file, run the type generator with your widget XML file.
* Any manual changes to this file will be lost when the types are regenerated.
*/
-import { CSSProperties } from 'mendix';
+import { CSSProperties } from "mendix";
/**
* Props for Benchmark Widget
@@ -27,7 +27,10 @@ export interface BenchmarkWidgetContainerProps {
sampleText?: string;
}
-import type { CSSProperties, PreviewValue } from 'react';
+import type {
+ CSSProperties,
+ PreviewValue,
+} from "react";
/**
* Preview props for Benchmark Widget
@@ -42,7 +45,10 @@ export interface BenchmarkWidgetPreviewProps {
/**
* The render mode of the widget preview
*/
- renderMode?: "design" | "xray" | "structure";
+ renderMode?:
+ | "design"
+ | "xray"
+ | "structure";
/**
* @deprecated Use class property instead
*/
diff --git a/benchmark/package.json b/benchmark/package.json
index ee9c705..1ace6e1 100644
--- a/benchmark/package.json
+++ b/benchmark/package.json
@@ -18,4 +18,4 @@
"fs-extra": "^11.2.0"
},
"devDependencies": {}
-}
\ No newline at end of file
+}
diff --git a/benchmark/src/benchmark.js b/benchmark/src/benchmark.js
index c5e00af..4cf0401 100644
--- a/benchmark/src/benchmark.js
+++ b/benchmark/src/benchmark.js
@@ -1,52 +1,52 @@
-import { execSync } from 'child_process';
-import fs from 'fs-extra';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import chalk from 'chalk';
-import Table from 'cli-table3';
+import { execSync } from "node:child_process";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import chalk from "chalk";
+import Table from "cli-table3";
+import fs from "fs-extra";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
-const rootDir = path.join(__dirname, '..');
-const widgetDir = path.join(rootDir, 'benchmarkWidget');
+const rootDir = path.join(__dirname, "..");
+const widgetDir = path.join(rootDir, "benchmarkWidget");
class WidgetBuildBenchmark {
constructor(options = {}) {
- this.verbose = options.verbose || process.argv.includes('--verbose');
- this.jsonOutput = options.json || process.argv.includes('--json');
+ this.verbose = options.verbose || process.argv.includes("--verbose");
+ this.jsonOutput = options.json || process.argv.includes("--json");
this.results = {
standardBuild: {},
hyperBuild: {},
- comparison: {}
+ comparison: {},
};
}
- log(message, type = 'info') {
+ log(message, type = "info") {
if (this.jsonOutput) return;
-
+
const prefix = {
- info: chalk.blue('[INFO]'),
- success: chalk.green('[SUCCESS]'),
- warning: chalk.yellow('[WARNING]'),
- error: chalk.red('[ERROR]'),
- debug: chalk.gray('[DEBUG]')
+ info: chalk.blue("[INFO]"),
+ success: chalk.green("[SUCCESS]"),
+ warning: chalk.yellow("[WARNING]"),
+ error: chalk.red("[ERROR]"),
+ debug: chalk.gray("[DEBUG]"),
};
-
+
console.log(`${prefix[type]} ${message}`);
}
async clean() {
- this.log('Cleaning previous build artifacts...');
- const distPath = path.join(widgetDir, 'dist');
- const tmpPath = path.join(widgetDir, 'tmp');
-
+ this.log("Cleaning previous build artifacts...");
+ const distPath = path.join(widgetDir, "dist");
+ const tmpPath = path.join(widgetDir, "tmp");
+
await fs.remove(distPath);
await fs.remove(tmpPath);
-
+
// Clean any .mpk files
const files = await fs.readdir(widgetDir);
for (const file of files) {
- if (file.endsWith('.mpk')) {
+ if (file.endsWith(".mpk")) {
await fs.remove(path.join(widgetDir, file));
}
}
@@ -54,62 +54,63 @@ class WidgetBuildBenchmark {
runBuild(command, buildType) {
this.log(`Running ${buildType} build...`);
-
+
const startTime = Date.now();
const startMemory = process.memoryUsage();
-
+
try {
const output = execSync(`npm run ${command}`, {
cwd: widgetDir,
- stdio: this.verbose ? 'inherit' : 'pipe',
- encoding: 'utf8'
+ stdio: this.verbose ? "inherit" : "pipe",
+ encoding: "utf8",
});
-
+
const endTime = Date.now();
const endMemory = process.memoryUsage();
-
+
const buildTime = endTime - startTime;
- const memoryUsed = (endMemory.heapUsed - startMemory.heapUsed) / 1024 / 1024;
-
- this.log(`${buildType} build completed in ${buildTime}ms`, 'success');
-
+ const memoryUsed =
+ (endMemory.heapUsed - startMemory.heapUsed) / 1024 / 1024;
+
+ this.log(`${buildType} build completed in ${buildTime}ms`, "success");
+
return {
success: true,
buildTime,
memoryUsed,
- output: this.verbose ? output : null
+ output: this.verbose ? output : null,
};
} catch (error) {
- this.log(`${buildType} build failed: ${error.message}`, 'error');
+ this.log(`${buildType} build failed: ${error.message}`, "error");
return {
success: false,
- error: error.message
+ error: error.message,
};
}
}
- async analyzeBuildOutput(buildType) {
- const distPath = path.join(widgetDir, 'dist');
- const distMpkPath = path.join(distPath, '1.0.0');
-
+ async analyzeBuildOutput() {
+ const distPath = path.join(widgetDir, "dist");
+ const distMpkPath = path.join(distPath, "1.0.0");
+
const analysis = {
distSize: 0,
fileCount: 0,
files: [],
- mpkSize: 0
+ mpkSize: 0,
};
// Analyze dist folder
if (await fs.pathExists(distPath)) {
const files = await this.getFilesRecursively(distPath);
analysis.fileCount = files.length;
-
+
for (const file of files) {
const stats = await fs.stat(file);
const relPath = path.relative(distPath, file);
analysis.files.push({
path: relPath,
- size: stats.size
+ size: stats.size,
});
analysis.distSize += stats.size;
}
@@ -117,8 +118,8 @@ class WidgetBuildBenchmark {
// Find and analyze .mpk file
const widgetFiles = await fs.readdir(distMpkPath);
- const mpkFile = widgetFiles.find(f => f.endsWith('.mpk'));
-
+ const mpkFile = widgetFiles.find((f) => f.endsWith(".mpk"));
+
if (mpkFile) {
const mpkPath = path.join(distMpkPath, mpkFile);
const stats = await fs.stat(mpkPath);
@@ -132,39 +133,39 @@ class WidgetBuildBenchmark {
async getFilesRecursively(dir) {
const files = [];
const items = await fs.readdir(dir);
-
+
for (const item of items) {
const fullPath = path.join(dir, item);
const stats = await fs.stat(fullPath);
-
+
if (stats.isDirectory()) {
- files.push(...await this.getFilesRecursively(fullPath));
+ files.push(...(await this.getFilesRecursively(fullPath)));
} else {
files.push(fullPath);
}
}
-
+
return files;
}
async saveBuildArtifacts(buildType) {
- const artifactsDir = path.join(rootDir, 'artifacts', buildType);
+ const artifactsDir = path.join(rootDir, "artifacts", buildType);
await fs.ensureDir(artifactsDir);
-
+
// Copy dist folder
- const distPath = path.join(widgetDir, 'dist');
+ const distPath = path.join(widgetDir, "dist");
if (await fs.pathExists(distPath)) {
- await fs.copy(distPath, path.join(artifactsDir, 'dist'));
+ await fs.copy(distPath, path.join(artifactsDir, "dist"));
}
-
+
// Copy .mpk file
const widgetFiles = await fs.readdir(widgetDir);
- const mpkFile = widgetFiles.find(f => f.endsWith('.mpk'));
-
+ const mpkFile = widgetFiles.find((f) => f.endsWith(".mpk"));
+
if (mpkFile) {
await fs.copy(
path.join(widgetDir, mpkFile),
- path.join(artifactsDir, mpkFile)
+ path.join(artifactsDir, mpkFile),
);
}
}
@@ -172,31 +173,47 @@ class WidgetBuildBenchmark {
calculateComparison() {
const std = this.results.standardBuild;
const hyper = this.results.hyperBuild;
-
+
if (!std.metrics || !hyper.metrics) {
return null;
}
-
+
return {
buildTime: {
difference: hyper.metrics.buildTime - std.metrics.buildTime,
- percentage: ((hyper.metrics.buildTime - std.metrics.buildTime) / std.metrics.buildTime * 100).toFixed(2)
+ percentage: (
+ ((hyper.metrics.buildTime - std.metrics.buildTime) /
+ std.metrics.buildTime) *
+ 100
+ ).toFixed(2),
},
memoryUsage: {
difference: hyper.metrics.memoryUsed - std.metrics.memoryUsed,
- percentage: ((hyper.metrics.memoryUsed - std.metrics.memoryUsed) / std.metrics.memoryUsed * 100).toFixed(2)
+ percentage: (
+ ((hyper.metrics.memoryUsed - std.metrics.memoryUsed) /
+ std.metrics.memoryUsed) *
+ 100
+ ).toFixed(2),
},
distSize: {
difference: hyper.analysis.distSize - std.analysis.distSize,
- percentage: ((hyper.analysis.distSize - std.analysis.distSize) / std.analysis.distSize * 100).toFixed(2)
+ percentage: (
+ ((hyper.analysis.distSize - std.analysis.distSize) /
+ std.analysis.distSize) *
+ 100
+ ).toFixed(2),
},
mpkSize: {
difference: hyper.analysis.mpkSize - std.analysis.mpkSize,
- percentage: ((hyper.analysis.mpkSize - std.analysis.mpkSize) / std.analysis.mpkSize * 100).toFixed(2)
+ percentage: (
+ ((hyper.analysis.mpkSize - std.analysis.mpkSize) /
+ std.analysis.mpkSize) *
+ 100
+ ).toFixed(2),
},
fileCount: {
- difference: hyper.analysis.fileCount - std.analysis.fileCount
- }
+ difference: hyper.analysis.fileCount - std.analysis.fileCount,
+ },
};
}
@@ -206,14 +223,14 @@ class WidgetBuildBenchmark {
return;
}
- console.log('\n' + chalk.bold.cyan('=== Build Benchmark Results ===\n'));
+ console.log(`\n${chalk.bold.cyan("=== Build Benchmark Results ===\n")}`);
// Build metrics table
const metricsTable = new Table({
- head: ['Metric', 'Standard Build', 'Hyper Build', 'Difference'],
+ head: ["Metric", "Standard Build", "Hyper Build", "Difference"],
style: {
- head: ['cyan']
- }
+ head: ["cyan"],
+ },
});
const std = this.results.standardBuild;
@@ -223,55 +240,89 @@ class WidgetBuildBenchmark {
if (std.metrics && hyper.metrics && comp) {
metricsTable.push(
[
- 'Build Time',
+ "Build Time",
`${std.metrics.buildTime}ms`,
`${hyper.metrics.buildTime}ms`,
- this.formatDifference(comp.buildTime.difference, comp.buildTime.percentage, 'ms')
+ this.formatDifference(
+ comp.buildTime.difference,
+ comp.buildTime.percentage,
+ "ms",
+ ),
],
[
- 'Memory Usage',
+ "Memory Usage",
`${std.metrics.memoryUsed.toFixed(2)}MB`,
`${hyper.metrics.memoryUsed.toFixed(2)}MB`,
- this.formatDifference(comp.memoryUsage.difference, comp.memoryUsage.percentage, 'MB')
+ this.formatDifference(
+ comp.memoryUsage.difference,
+ comp.memoryUsage.percentage,
+ "MB",
+ ),
],
[
- 'Dist Size',
+ "Dist Size",
this.formatBytes(std.analysis.distSize),
this.formatBytes(hyper.analysis.distSize),
- this.formatDifference(comp.distSize.difference, comp.distSize.percentage, 'bytes', true)
+ this.formatDifference(
+ comp.distSize.difference,
+ comp.distSize.percentage,
+ "bytes",
+ true,
+ ),
],
[
- 'MPK Size',
+ "MPK Size",
this.formatBytes(std.analysis.mpkSize),
this.formatBytes(hyper.analysis.mpkSize),
- this.formatDifference(comp.mpkSize.difference, comp.mpkSize.percentage, 'bytes', true)
+ this.formatDifference(
+ comp.mpkSize.difference,
+ comp.mpkSize.percentage,
+ "bytes",
+ true,
+ ),
],
[
- 'File Count',
+ "File Count",
std.analysis.fileCount,
hyper.analysis.fileCount,
- comp.fileCount.difference > 0 ? `+${comp.fileCount.difference}` : `${comp.fileCount.difference}`
- ]
+ comp.fileCount.difference > 0
+ ? `+${comp.fileCount.difference}`
+ : `${comp.fileCount.difference}`,
+ ],
);
console.log(metricsTable.toString());
// Summary
- console.log('\n' + chalk.bold('Summary:'));
-
+ console.log(`\n${chalk.bold("Summary:")}`);
+
const buildTimeImproved = comp.buildTime.difference < 0;
const sizeImproved = comp.mpkSize.difference < 0;
-
+
if (buildTimeImproved) {
- console.log(chalk.green(`✓ Hyper build is ${Math.abs(comp.buildTime.percentage)}% faster`));
+ console.log(
+ chalk.green(
+ `✓ Hyper build is ${Math.abs(comp.buildTime.percentage)}% faster`,
+ ),
+ );
} else {
- console.log(chalk.yellow(`⚠ Hyper build is ${comp.buildTime.percentage}% slower`));
+ console.log(
+ chalk.yellow(`⚠ Hyper build is ${comp.buildTime.percentage}% slower`),
+ );
}
-
+
if (sizeImproved) {
- console.log(chalk.green(`✓ Hyper build produces ${Math.abs(comp.mpkSize.percentage)}% smaller package`));
+ console.log(
+ chalk.green(
+ `✓ Hyper build produces ${Math.abs(comp.mpkSize.percentage)}% smaller package`,
+ ),
+ );
} else if (comp.mpkSize.difference > 0) {
- console.log(chalk.yellow(`⚠ Hyper build produces ${comp.mpkSize.percentage}% larger package`));
+ console.log(
+ chalk.yellow(
+ `⚠ Hyper build produces ${comp.mpkSize.percentage}% larger package`,
+ ),
+ );
} else {
console.log(chalk.blue(`• Package size is identical`));
}
@@ -279,10 +330,12 @@ class WidgetBuildBenchmark {
}
formatDifference(diff, percentage, unit, isBytes = false) {
- const value = isBytes ? this.formatBytes(Math.abs(diff)) : `${Math.abs(diff).toFixed(2)}${unit}`;
- const sign = diff > 0 ? '+' : '-';
+ const value = isBytes
+ ? this.formatBytes(Math.abs(diff))
+ : `${Math.abs(diff).toFixed(2)}${unit}`;
+ const sign = diff > 0 ? "+" : "-";
const color = diff > 0 ? chalk.red : chalk.green;
-
+
return color(`${sign}${value} (${sign}${Math.abs(percentage)}%)`);
}
@@ -294,63 +347,71 @@ class WidgetBuildBenchmark {
async run() {
try {
- this.log('Starting Widget Build Benchmark', 'info');
- this.log(`Widget directory: ${widgetDir}`, 'debug');
+ this.log("Starting Widget Build Benchmark", "info");
+ this.log(`Widget directory: ${widgetDir}`, "debug");
// Install dependencies if needed
- if (!await fs.pathExists(path.join(widgetDir, 'node_modules'))) {
- this.log('Installing widget dependencies...');
- execSync('npm install', { cwd: widgetDir, stdio: 'inherit' });
+ if (!(await fs.pathExists(path.join(widgetDir, "node_modules")))) {
+ this.log("Installing widget dependencies...");
+ execSync("npm install", {
+ cwd: widgetDir,
+ stdio: "inherit",
+ });
}
// Standard build
- this.log('\n' + chalk.bold('Phase 1: Standard Build'), 'info');
+ this.log(`\n${chalk.bold("Phase 1: Standard Build")}`, "info");
await this.clean();
- const standardMetrics = this.runBuild('build', 'Standard');
-
+ const standardMetrics = this.runBuild("build", "Standard");
+
if (standardMetrics.success) {
- const standardAnalysis = await this.analyzeBuildOutput('standard');
- await this.saveBuildArtifacts('standard');
-
+ const standardAnalysis = await this.analyzeBuildOutput("standard");
+ await this.saveBuildArtifacts("standard");
+
this.results.standardBuild = {
metrics: standardMetrics,
- analysis: standardAnalysis
+ analysis: standardAnalysis,
};
} else {
- throw new Error('Standard build failed');
+ throw new Error("Standard build failed");
}
// Hyper build
- this.log('\n' + chalk.bold('Phase 2: Hyper Build'), 'info');
+ this.log(`\n${chalk.bold("Phase 2: Hyper Build")}`, "info");
await this.clean();
- const hyperMetrics = this.runBuild('build:hyper', 'Hyper');
-
+ const hyperMetrics = this.runBuild("build:hyper", "Hyper");
+
if (hyperMetrics.success) {
- const hyperAnalysis = await this.analyzeBuildOutput('hyper');
- await this.saveBuildArtifacts('hyper');
-
+ const hyperAnalysis = await this.analyzeBuildOutput("hyper");
+ await this.saveBuildArtifacts("hyper");
+
this.results.hyperBuild = {
metrics: hyperMetrics,
- analysis: hyperAnalysis
+ analysis: hyperAnalysis,
};
} else {
- throw new Error('Hyper build failed');
+ throw new Error("Hyper build failed");
}
// Calculate comparison
this.results.comparison = this.calculateComparison();
// Save results
- const resultsPath = path.join(rootDir, 'results', `benchmark-${Date.now()}.json`);
- await fs.ensureDir(path.join(rootDir, 'results'));
- await fs.writeJson(resultsPath, this.results, { spaces: 2 });
- this.log(`Results saved to: ${resultsPath}`, 'success');
+ const resultsPath = path.join(
+ rootDir,
+ "results",
+ `benchmark-${Date.now()}.json`,
+ );
+ await fs.ensureDir(path.join(rootDir, "results"));
+ await fs.writeJson(resultsPath, this.results, {
+ spaces: 2,
+ });
+ this.log(`Results saved to: ${resultsPath}`, "success");
// Display results
this.displayResults();
-
} catch (error) {
- this.log(`Benchmark failed: ${error.message}`, 'error');
+ this.log(`Benchmark failed: ${error.message}`, "error");
if (this.verbose) {
console.error(error);
}
@@ -361,4 +422,4 @@ class WidgetBuildBenchmark {
// Run benchmark
const benchmark = new WidgetBuildBenchmark();
-benchmark.run();
\ No newline at end of file
+benchmark.run();
diff --git a/benchmark/src/compare.js b/benchmark/src/compare.js
index ca08c36..e096781 100644
--- a/benchmark/src/compare.js
+++ b/benchmark/src/compare.js
@@ -1,24 +1,29 @@
-import fs from 'fs-extra';
-import path from 'path';
-import { fileURLToPath } from 'url';
-import chalk from 'chalk';
-import Table from 'cli-table3';
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import chalk from "chalk";
+import Table from "cli-table3";
+import fs from "fs-extra";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
-const rootDir = path.join(__dirname, '..');
+const rootDir = path.join(__dirname, "..");
class BuildComparator {
constructor() {
- this.artifactsDir = path.join(rootDir, 'artifacts');
+ this.artifactsDir = path.join(rootDir, "artifacts");
}
async compareFiles() {
- const standardDir = path.join(this.artifactsDir, 'standard', 'dist');
- const hyperDir = path.join(this.artifactsDir, 'hyper', 'dist');
-
- if (!await fs.pathExists(standardDir) || !await fs.pathExists(hyperDir)) {
- console.error(chalk.red('Build artifacts not found. Please run benchmark first.'));
+ const standardDir = path.join(this.artifactsDir, "standard", "dist");
+ const hyperDir = path.join(this.artifactsDir, "hyper", "dist");
+
+ if (
+ !(await fs.pathExists(standardDir)) ||
+ !(await fs.pathExists(hyperDir))
+ ) {
+ console.error(
+ chalk.red("Build artifacts not found. Please run benchmark first."),
+ );
return;
}
@@ -26,11 +31,16 @@ class BuildComparator {
const hyperFiles = await this.getFileMap(hyperDir);
const table = new Table({
- head: ['File', 'Standard Size', 'Hyper Size', 'Difference'],
- style: { head: ['cyan'] }
+ head: ["File", "Standard Size", "Hyper Size", "Difference"],
+ style: {
+ head: ["cyan"],
+ },
});
- const allFiles = new Set([...Object.keys(standardFiles), ...Object.keys(hyperFiles)]);
+ const allFiles = new Set([
+ ...Object.keys(standardFiles),
+ ...Object.keys(hyperFiles),
+ ]);
let totalStandard = 0;
let totalHyper = 0;
@@ -39,66 +49,80 @@ class BuildComparator {
const stdSize = standardFiles[file] || 0;
const hyperSize = hyperFiles[file] || 0;
const diff = hyperSize - stdSize;
-
+
totalStandard += stdSize;
totalHyper += hyperSize;
if (stdSize === 0) {
table.push([
file,
- '-',
+ "-",
this.formatBytes(hyperSize),
- chalk.yellow('New file')
+ chalk.yellow("New file"),
]);
} else if (hyperSize === 0) {
table.push([
file,
this.formatBytes(stdSize),
- '-',
- chalk.red('Removed')
+ "-",
+ chalk.red("Removed"),
]);
} else {
const percentage = ((diff / stdSize) * 100).toFixed(1);
- const diffStr = diff > 0
- ? chalk.red(`+${this.formatBytes(diff)} (+${percentage}%)`)
- : diff < 0
- ? chalk.green(`${this.formatBytes(diff)} (${percentage}%)`)
- : chalk.gray('No change');
-
+ const diffStr =
+ diff > 0
+ ? chalk.red(`+${this.formatBytes(diff)} (+${percentage}%)`)
+ : diff < 0
+ ? chalk.green(`${this.formatBytes(diff)} (${percentage}%)`)
+ : chalk.gray("No change");
+
table.push([
file,
this.formatBytes(stdSize),
this.formatBytes(hyperSize),
- diffStr
+ diffStr,
]);
}
}
// Add total row
table.push([
- chalk.bold('TOTAL'),
+ chalk.bold("TOTAL"),
chalk.bold(this.formatBytes(totalStandard)),
chalk.bold(this.formatBytes(totalHyper)),
- chalk.bold(this.formatDifference(totalHyper - totalStandard, totalStandard))
+ chalk.bold(
+ this.formatDifference(totalHyper - totalStandard, totalStandard),
+ ),
]);
- console.log('\n' + chalk.bold.cyan('=== File Size Comparison ===\n'));
+ console.log(`\n${chalk.bold.cyan("=== File Size Comparison ===\n")}`);
console.log(table.toString());
// Check for content differences
- await this.compareFileContents(standardFiles, hyperFiles, standardDir, hyperDir);
+ await this.compareFileContents(
+ standardFiles,
+ hyperFiles,
+ standardDir,
+ hyperDir,
+ );
}
async compareFileContents(standardFiles, hyperFiles, standardDir, hyperDir) {
- const jsFiles = Object.keys(standardFiles).filter(f => f.endsWith('.js'));
+ const jsFiles = Object.keys(standardFiles).filter((f) => f.endsWith(".js"));
let identicalCount = 0;
let differentCount = 0;
for (const file of jsFiles) {
if (hyperFiles[file]) {
- const stdContent = await fs.readFile(path.join(standardDir, file), 'utf8');
- const hyperContent = await fs.readFile(path.join(hyperDir, file), 'utf8');
-
+ const stdContent = await fs.readFile(
+ path.join(standardDir, file),
+ "utf8",
+ );
+ const hyperContent = await fs.readFile(
+ path.join(hyperDir, file),
+ "utf8",
+ );
+
if (stdContent === hyperContent) {
identicalCount++;
} else {
@@ -107,12 +131,12 @@ class BuildComparator {
}
}
- console.log('\n' + chalk.bold('Content Analysis:'));
+ console.log(`\n${chalk.bold("Content Analysis:")}`);
console.log(`• ${identicalCount} files have identical content`);
console.log(`• ${differentCount} files have different content`);
}
- async getFileMap(dir, basePath = '') {
+ async getFileMap(dir, basePath = "") {
const files = {};
const items = await fs.readdir(dir);
@@ -146,10 +170,10 @@ class BuildComparator {
} else if (diff < 0) {
return chalk.green(`${this.formatBytes(diff)} (${percentage}%)`);
}
- return chalk.gray('No change');
+ return chalk.gray("No change");
}
}
// Run comparison
const comparator = new BuildComparator();
-comparator.compareFiles();
\ No newline at end of file
+comparator.compareFiles();
diff --git a/biome.json b/biome.json
new file mode 100644
index 0000000..47cbfbd
--- /dev/null
+++ b/biome.json
@@ -0,0 +1,41 @@
+{
+ "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json",
+ "vcs": {
+ "enabled": true,
+ "clientKind": "git"
+ },
+ "files": {
+ "ignoreUnknown": false,
+ "includes": [
+ "**",
+ "!dist/**/*",
+ "!**/benchmark/artifacts",
+ "!**/benchmark/benchmarkWidget"
+ ]
+ },
+ "formatter": {
+ "enabled": true,
+ "indentStyle": "space",
+ "indentWidth": 2,
+ "lineWidth": 80
+ },
+ "linter": {
+ "enabled": true,
+ "rules": {
+ "recommended": true
+ }
+ },
+ "javascript": {
+ "formatter": {
+ "quoteStyle": "double"
+ }
+ },
+ "assist": {
+ "enabled": true,
+ "actions": {
+ "source": {
+ "organizeImports": "on"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
index 6279a47..5971f15 100644
--- a/package.json
+++ b/package.json
@@ -11,7 +11,12 @@
"watch": "rslib build --watch",
"start": "pnpm build && node ./dist/cli.js",
"package": "pnpm build && pnpm pack",
- "prepare": "node ./tools/copy-widget-schema.js"
+ "prepare": "husky",
+ "lint": "biome check",
+ "lint:fix": "biome check --write"
+ },
+ "lint-staged": {
+ "*": "biome check --write"
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
@@ -35,10 +40,12 @@
],
"author": "Repixel Co., Ltd.",
"license": "MIT",
- "packageManager": "pnpm@10.15.0",
+ "packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
"devDependencies": {
+ "@biomejs/biome": "2.2.2",
"@rslib/core": "0.12.2",
"@types/node": "22.17.2",
+ "husky": "9.1.7",
"type-fest": "4.41.0",
"typescript": "5.9.2"
},
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 872c695..c82b972 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -10,7 +10,7 @@ importers:
dependencies:
'@vitejs/plugin-react':
specifier: 5.0.2
- version: 5.0.2(rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1))
+ version: 5.0.2(rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)(yaml@2.8.1))
chalk:
specifier: 5.6.0
version: 5.6.0
@@ -28,17 +28,23 @@ importers:
version: 0.36.0(rollup@4.49.0)(typescript@5.9.2)
vite:
specifier: npm:rolldown-vite@7.1.5
- version: rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)
+ version: rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)(yaml@2.8.1)
zip-a-folder:
specifier: 3.1.9
version: 3.1.9
devDependencies:
+ '@biomejs/biome':
+ specifier: 2.2.2
+ version: 2.2.2
'@rslib/core':
specifier: 0.12.2
version: 0.12.2(typescript@5.9.2)
'@types/node':
specifier: 22.17.2
version: 22.17.2
+ husky:
+ specifier: 9.1.7
+ version: 9.1.7
type-fest:
specifier: 4.41.0
version: 4.41.0
@@ -193,6 +199,59 @@ packages:
resolution: {integrity: sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==}
engines: {node: '>=6.9.0'}
+ '@biomejs/biome@2.2.2':
+ resolution: {integrity: sha512-j1omAiQWCkhuLgwpMKisNKnsM6W8Xtt1l0WZmqY/dFj8QPNkIoTvk4tSsi40FaAAkBE1PU0AFG2RWFBWenAn+w==}
+ engines: {node: '>=14.21.3'}
+ hasBin: true
+
+ '@biomejs/cli-darwin-arm64@2.2.2':
+ resolution: {integrity: sha512-6ePfbCeCPryWu0CXlzsWNZgVz/kBEvHiPyNpmViSt6A2eoDf4kXs3YnwQPzGjy8oBgQulrHcLnJL0nkCh80mlQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@biomejs/cli-darwin-x64@2.2.2':
+ resolution: {integrity: sha512-Tn4JmVO+rXsbRslml7FvKaNrlgUeJot++FkvYIhl1OkslVCofAtS35MPlBMhXgKWF9RNr9cwHanrPTUUXcYGag==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@biomejs/cli-linux-arm64-musl@2.2.2':
+ resolution: {integrity: sha512-/MhYg+Bd6renn6i1ylGFL5snYUn/Ct7zoGVKhxnro3bwekiZYE8Kl39BSb0MeuqM+72sThkQv4TnNubU9njQRw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-arm64@2.2.2':
+ resolution: {integrity: sha512-JfrK3gdmWWTh2J5tq/rcWCOsImVyzUnOS2fkjhiYKCQ+v8PqM+du5cfB7G1kXas+7KQeKSWALv18iQqdtIMvzw==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64-musl@2.2.2':
+ resolution: {integrity: sha512-ZCLXcZvjZKSiRY/cFANKg+z6Fhsf9MHOzj+NrDQcM+LbqYRT97LyCLWy2AS+W2vP+i89RyRM+kbGpUzbRTYWig==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-linux-x64@2.2.2':
+ resolution: {integrity: sha512-Ogb+77edO5LEP/xbNicACOWVLt8mgC+E1wmpUakr+O4nKwLt9vXe74YNuT3T1dUBxC/SnrVmlzZFC7kQJEfquQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [linux]
+
+ '@biomejs/cli-win32-arm64@2.2.2':
+ resolution: {integrity: sha512-wBe2wItayw1zvtXysmHJQoQqXlTzHSpQRyPpJKiNIR21HzH/CrZRDFic1C1jDdp+zAPtqhNExa0owKMbNwW9cQ==}
+ engines: {node: '>=14.21.3'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@biomejs/cli-win32-x64@2.2.2':
+ resolution: {integrity: sha512-DAuHhHekGfiGb6lCcsT4UyxQmVwQiBCBUMwVra/dcOSs9q8OhfaZgey51MlekT3p8UwRqtXQfFuEJBhJNdLZwg==}
+ engines: {node: '>=14.21.3'}
+ cpu: [x64]
+ os: [win32]
+
'@emnapi/core@1.4.5':
resolution: {integrity: sha512-XsLw1dEOpkSX/WucdqUhPWP7hDxSvZiY+fsUC14h+FtQ2Ifni4znbBt8punRX+Uj2JG/uDb8nEHVKvrVlvdZ5Q==}
@@ -927,6 +986,11 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ husky@9.1.7:
+ resolution: {integrity: sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==}
+ engines: {node: '>=18'}
+ hasBin: true
+
ieee754@1.2.1:
resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==}
@@ -1371,6 +1435,11 @@ packages:
yallist@3.1.1:
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
+ yaml@2.8.1:
+ resolution: {integrity: sha512-lcYcMxX2PO9XMGvAJkJ3OsNMw+/7FKes7/hgerGUYWIoWu5j/+YQqcZr5JnPZWzOsEBgMbSbiSTn/dv/69Mkpw==}
+ engines: {node: '>= 14.6'}
+ hasBin: true
+
zip-a-folder@3.1.9:
resolution: {integrity: sha512-0TPP3eK5mbZxHnOE8w/Jg6gwxsxZOrA3hXHMfC3I4mcTvyJwNt7GZP8i6uiAMVNu43QTmVz0ngEMKcjgpLZLmQ==}
@@ -1536,6 +1605,41 @@ snapshots:
'@babel/helper-string-parser': 7.27.1
'@babel/helper-validator-identifier': 7.27.1
+ '@biomejs/biome@2.2.2':
+ optionalDependencies:
+ '@biomejs/cli-darwin-arm64': 2.2.2
+ '@biomejs/cli-darwin-x64': 2.2.2
+ '@biomejs/cli-linux-arm64': 2.2.2
+ '@biomejs/cli-linux-arm64-musl': 2.2.2
+ '@biomejs/cli-linux-x64': 2.2.2
+ '@biomejs/cli-linux-x64-musl': 2.2.2
+ '@biomejs/cli-win32-arm64': 2.2.2
+ '@biomejs/cli-win32-x64': 2.2.2
+
+ '@biomejs/cli-darwin-arm64@2.2.2':
+ optional: true
+
+ '@biomejs/cli-darwin-x64@2.2.2':
+ optional: true
+
+ '@biomejs/cli-linux-arm64-musl@2.2.2':
+ optional: true
+
+ '@biomejs/cli-linux-arm64@2.2.2':
+ optional: true
+
+ '@biomejs/cli-linux-x64-musl@2.2.2':
+ optional: true
+
+ '@biomejs/cli-linux-x64@2.2.2':
+ optional: true
+
+ '@biomejs/cli-win32-arm64@2.2.2':
+ optional: true
+
+ '@biomejs/cli-win32-x64@2.2.2':
+ optional: true
+
'@emnapi/core@1.4.5':
dependencies:
'@emnapi/wasi-threads': 1.0.4
@@ -1928,7 +2032,7 @@ snapshots:
'@types/scheduler@0.26.0': {}
- '@vitejs/plugin-react@5.0.2(rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1))':
+ '@vitejs/plugin-react@5.0.2(rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)(yaml@2.8.1))':
dependencies:
'@babel/core': 7.28.3
'@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.3)
@@ -1936,7 +2040,7 @@ snapshots:
'@rolldown/pluginutils': 1.0.0-beta.34
'@types/babel__core': 7.20.5
react-refresh: 0.17.0
- vite: rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)
+ vite: rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)(yaml@2.8.1)
transitivePeerDependencies:
- supports-color
@@ -2157,6 +2261,8 @@ snapshots:
graceful-fs@4.2.11: {}
+ husky@9.1.7: {}
+
ieee754@1.2.1: {}
inherits@2.0.4: {}
@@ -2369,7 +2475,7 @@ snapshots:
dependencies:
minimatch: 5.1.6
- rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1):
+ rolldown-vite@7.1.5(@types/node@22.17.2)(esbuild@0.25.9)(jiti@2.5.1)(yaml@2.8.1):
dependencies:
fdir: 6.5.0(picomatch@4.0.3)
lightningcss: 1.30.1
@@ -2382,6 +2488,7 @@ snapshots:
esbuild: 0.25.9
fsevents: 2.3.3
jiti: 2.5.1
+ yaml: 2.8.1
rolldown@1.0.0-beta.34:
dependencies:
@@ -2566,6 +2673,9 @@ snapshots:
yallist@3.1.1: {}
+ yaml@2.8.1:
+ optional: true
+
zip-a-folder@3.1.9:
dependencies:
archiver: 7.0.1
diff --git a/rslib.config.ts b/rslib.config.ts
index 70632f4..b07ef5f 100644
--- a/rslib.config.ts
+++ b/rslib.config.ts
@@ -1,68 +1,68 @@
-import { defineConfig } from "@rslib/core";
-
-export default defineConfig({
- lib: [
- {
- format: 'cjs',
- bundle: true,
- dts: true,
- source: {
- entry: {
- cli: 'src/cli.ts'
- }
- },
- output: {
- filename: {
- js: '[name].js'
- }
- }
- },
- {
- format: 'esm',
- bundle: true,
- dts: true,
- source: {
- entry: {
- index: 'src/index.ts'
- }
- },
- output: {
- filename: {
- js: '[name].mjs'
- }
- }
- },
- {
- format: 'cjs',
- bundle: true,
- dts: false,
- source: {
- entry: {
- index: 'src/index.ts'
- }
- },
- output: {
- filename: {
- js: '[name].cjs'
- }
- }
- }
- ],
- output: {
- minify: {
- js: true,
- jsOptions: {
- minimizerOptions: {
- mangle: true,
- minify: true,
- compress: {
- defaults: true,
- unused: true,
- dead_code: true,
- toplevel: true
- }
- }
- }
- }
- }
-});
\ No newline at end of file
+import { defineConfig } from "@rslib/core";
+
+export default defineConfig({
+ lib: [
+ {
+ format: "cjs",
+ bundle: true,
+ dts: true,
+ source: {
+ entry: {
+ cli: "src/cli.ts",
+ },
+ },
+ output: {
+ filename: {
+ js: "[name].js",
+ },
+ },
+ },
+ {
+ format: "esm",
+ bundle: true,
+ dts: true,
+ source: {
+ entry: {
+ index: "src/index.ts",
+ },
+ },
+ output: {
+ filename: {
+ js: "[name].mjs",
+ },
+ },
+ },
+ {
+ format: "cjs",
+ bundle: true,
+ dts: false,
+ source: {
+ entry: {
+ index: "src/index.ts",
+ },
+ },
+ output: {
+ filename: {
+ js: "[name].cjs",
+ },
+ },
+ },
+ ],
+ output: {
+ minify: {
+ js: true,
+ jsOptions: {
+ minimizerOptions: {
+ mangle: true,
+ minify: true,
+ compress: {
+ defaults: true,
+ unused: true,
+ dead_code: true,
+ toplevel: true,
+ },
+ },
+ },
+ },
+ },
+});
diff --git a/src/cli.ts b/src/cli.ts
index 7ca916e..0454ef9 100644
--- a/src/cli.ts
+++ b/src/cli.ts
@@ -1,30 +1,34 @@
-#!/usr/bin/env node
-
-import { program } from "commander";
-
-import packageJson from "../package.json";
-import buildWebCommand from "./commands/build/web";
-import startWebCommand from "./commands/start/web";
-
-program.version(packageJson.version, '-v, --version', 'display current version');
-
-program
- .command('build:web')
- .summary('build web widget')
- .action(async () => {
- await buildWebCommand();
- });
-
-program
- .command('release:web')
- .summary('release web widget')
- .action(async () => {
- await buildWebCommand(true);
- });
-
-program
- .command('start:web')
- .summary('start web widget live reload')
- .action(startWebCommand);
-
-program.parse();
\ No newline at end of file
+#!/usr/bin/env node
+
+import { program } from "commander";
+
+import packageJson from "../package.json";
+import buildWebCommand from "./commands/build/web";
+import startWebCommand from "./commands/start/web";
+
+program.version(
+ packageJson.version,
+ "-v, --version",
+ "display current version",
+);
+
+program
+ .command("build:web")
+ .summary("build web widget")
+ .action(async () => {
+ await buildWebCommand();
+ });
+
+program
+ .command("release:web")
+ .summary("release web widget")
+ .action(async () => {
+ await buildWebCommand(true);
+ });
+
+program
+ .command("start:web")
+ .summary("start web widget live reload")
+ .action(startWebCommand);
+
+program.parse();
diff --git a/src/commands/build/web/index.ts b/src/commands/build/web/index.ts
index 472c400..e799860 100644
--- a/src/commands/build/web/index.ts
+++ b/src/commands/build/web/index.ts
@@ -1,121 +1,163 @@
-import fs from 'fs/promises';
-import path from 'path';
-import { InlineConfig, UserConfig, build as viteBuild } from 'vite';
-import { zip } from 'zip-a-folder';
-
-import { COLOR_ERROR, COLOR_GREEN, DIST_DIRECTORY_NAME, PROJECT_DIRECTORY, VITE_CONFIGURATION_FILENAME, WEB_OUTPUT_DIRECTORY } from '../../../constants';
-import pathIsExists from '../../../utils/pathIsExists';
-import getWidgetVersion from '../../../utils/getWidgetVersion';
-import showMessage from '../../../utils/showMessage';
-import { getEditorConfigDefaultConfig, getEditorPreviewDefaultConfig, getViteDefaultConfig } from '../../../configurations/vite';
-import getWidgetName from '../../../utils/getWidgetName';
-import getWidgetPackageJson from '../../../utils/getWidgetPackageJson';
-import getMendixWidgetDirectory from '../../../utils/getMendixWidgetDirectory';
-import getViteUserConfiguration from '../../../utils/getViteUserConfiguration';
-import { generateTypesFromFile } from '../../../type-generator';
-
-const buildWebCommand = async (isProduction: boolean = false) => {
- try {
- showMessage('Generate types');
-
- const widgetName = await getWidgetName();
- const originWidgetXmlPath = path.join(PROJECT_DIRECTORY, `src/${widgetName}.xml`);
- const typingsPath = path.join(PROJECT_DIRECTORY, 'typings');
- const typingsDirExists = await pathIsExists(typingsPath);
-
- if (typingsDirExists) {
- await fs.rm(typingsPath, { recursive: true, force: true });
- }
-
- await fs.mkdir(typingsPath);
-
- const newTypingsFilePath = path.join(typingsPath, `${widgetName}Props.d.ts`);
- const typingContents = await generateTypesFromFile(originWidgetXmlPath, 'web');
-
- await fs.writeFile(newTypingsFilePath, typingContents);
-
- showMessage('Remove previous builds');
-
- const distDir = path.join(PROJECT_DIRECTORY, DIST_DIRECTORY_NAME);
- const distIsExists = await pathIsExists(distDir);
-
- if (distIsExists) {
- await fs.rm(distDir, { recursive: true, force: true });
- }
-
- await fs.mkdir(distDir);
-
- showMessage('Copy resources');
-
- const widgetVersion = await getWidgetVersion();
- const outputDir = path.join(distDir, widgetVersion);
-
- await fs.mkdir(outputDir);
- await fs.mkdir(WEB_OUTPUT_DIRECTORY, { recursive: true });
-
- const customViteConfigPath = path.join(PROJECT_DIRECTORY, VITE_CONFIGURATION_FILENAME);
- const viteConfigIsExists = await pathIsExists(customViteConfigPath);
- let resultViteConfig: UserConfig;
-
- if (viteConfigIsExists) {
- const userConfig = await getViteUserConfiguration(customViteConfigPath);
-
- resultViteConfig = await getViteDefaultConfig(false, userConfig);
- } else {
- resultViteConfig = await getViteDefaultConfig(false);
- }
-
- const originPackageXmlPath = path.join(PROJECT_DIRECTORY, 'src/package.xml');
- const destPackageXmlPath = path.join(WEB_OUTPUT_DIRECTORY, 'package.xml');
- const destWidgetXmlPath = path.join(WEB_OUTPUT_DIRECTORY, `${widgetName}.xml`);
-
- await fs.copyFile(originPackageXmlPath, destPackageXmlPath);
- await fs.copyFile(originWidgetXmlPath, destWidgetXmlPath);
-
- showMessage('Start build');
-
- const editorConfigViteConfig = await getEditorConfigDefaultConfig(isProduction);
- const editorPreviewViteConfig = await getEditorPreviewDefaultConfig(isProduction);
- const viteBuildConfigs: InlineConfig[] = [
- {
- ...resultViteConfig,
- configFile: false,
- root: PROJECT_DIRECTORY
- },
- {
- ...editorConfigViteConfig,
- configFile: false,
- root: PROJECT_DIRECTORY,
- logLevel: 'silent'
- },
- {
- ...editorPreviewViteConfig,
- configFile: false,
- root: PROJECT_DIRECTORY,
- logLevel: 'silent'
- }
- ];
-
- await Promise.all(viteBuildConfigs.map(async (config) => {
- await viteBuild(config);
- }));
-
- showMessage('Generate mpk file');
-
- const packageJson = await getWidgetPackageJson();
- const packageName = packageJson.packagePath;
- const mpkFileName = `${packageName}.${widgetName}.mpk`;
- const mpkFileDestPath = path.join(outputDir, mpkFileName);
- const mendixWidgetDirectory = await getMendixWidgetDirectory();
- const mendixMpkFileDestPath = path.join(mendixWidgetDirectory, mpkFileName);
-
- await zip(WEB_OUTPUT_DIRECTORY, mpkFileDestPath);
- await fs.copyFile(mpkFileDestPath, mendixMpkFileDestPath);
-
- showMessage(`${COLOR_GREEN('Build complete.')}`);
- } catch (error) {
- showMessage(`${COLOR_ERROR('Build failed.')}\nError occurred: ${COLOR_ERROR((error as Error).stack)}`);
- }
-};
-
-export default buildWebCommand;
\ No newline at end of file
+import fs from "node:fs/promises";
+import path from "node:path";
+import { type InlineConfig, type UserConfig, build as viteBuild } from "vite";
+import { zip } from "zip-a-folder";
+import {
+ getEditorConfigDefaultConfig,
+ getEditorPreviewDefaultConfig,
+ getViteDefaultConfig,
+} from "../../../configurations/vite";
+import {
+ COLOR_ERROR,
+ COLOR_GREEN,
+ DIST_DIRECTORY_NAME,
+ PROJECT_DIRECTORY,
+ VITE_CONFIGURATION_FILENAME,
+ WEB_OUTPUT_DIRECTORY,
+} from "../../../constants";
+import { generateTypesFromFile } from "../../../type-generator";
+import getMendixWidgetDirectory from "../../../utils/getMendixWidgetDirectory";
+import getViteUserConfiguration from "../../../utils/getViteUserConfiguration";
+import getWidgetName from "../../../utils/getWidgetName";
+import getWidgetPackageJson from "../../../utils/getWidgetPackageJson";
+import getWidgetVersion from "../../../utils/getWidgetVersion";
+import pathIsExists from "../../../utils/pathIsExists";
+import showMessage from "../../../utils/showMessage";
+
+const buildWebCommand = async (isProduction: boolean = false) => {
+ try {
+ showMessage("Generate types");
+
+ const widgetName = await getWidgetName();
+ const originWidgetXmlPath = path.join(
+ PROJECT_DIRECTORY,
+ `src/${widgetName}.xml`,
+ );
+ const typingsPath = path.join(PROJECT_DIRECTORY, "typings");
+ const typingsDirExists = await pathIsExists(typingsPath);
+
+ if (typingsDirExists) {
+ await fs.rm(typingsPath, {
+ recursive: true,
+ force: true,
+ });
+ }
+
+ await fs.mkdir(typingsPath);
+
+ const newTypingsFilePath = path.join(
+ typingsPath,
+ `${widgetName}Props.d.ts`,
+ );
+ const typingContents = await generateTypesFromFile(
+ originWidgetXmlPath,
+ "web",
+ );
+
+ await fs.writeFile(newTypingsFilePath, typingContents);
+
+ showMessage("Remove previous builds");
+
+ const distDir = path.join(PROJECT_DIRECTORY, DIST_DIRECTORY_NAME);
+ const distIsExists = await pathIsExists(distDir);
+
+ if (distIsExists) {
+ await fs.rm(distDir, {
+ recursive: true,
+ force: true,
+ });
+ }
+
+ await fs.mkdir(distDir);
+
+ showMessage("Copy resources");
+
+ const widgetVersion = await getWidgetVersion();
+ const outputDir = path.join(distDir, widgetVersion);
+
+ await fs.mkdir(outputDir);
+ await fs.mkdir(WEB_OUTPUT_DIRECTORY, {
+ recursive: true,
+ });
+
+ const customViteConfigPath = path.join(
+ PROJECT_DIRECTORY,
+ VITE_CONFIGURATION_FILENAME,
+ );
+ const viteConfigIsExists = await pathIsExists(customViteConfigPath);
+ let resultViteConfig: UserConfig;
+
+ if (viteConfigIsExists) {
+ const userConfig = await getViteUserConfiguration(customViteConfigPath);
+
+ resultViteConfig = await getViteDefaultConfig(false, userConfig);
+ } else {
+ resultViteConfig = await getViteDefaultConfig(false);
+ }
+
+ const originPackageXmlPath = path.join(
+ PROJECT_DIRECTORY,
+ "src/package.xml",
+ );
+ const destPackageXmlPath = path.join(WEB_OUTPUT_DIRECTORY, "package.xml");
+ const destWidgetXmlPath = path.join(
+ WEB_OUTPUT_DIRECTORY,
+ `${widgetName}.xml`,
+ );
+
+ await fs.copyFile(originPackageXmlPath, destPackageXmlPath);
+ await fs.copyFile(originWidgetXmlPath, destWidgetXmlPath);
+
+ showMessage("Start build");
+
+ const editorConfigViteConfig =
+ await getEditorConfigDefaultConfig(isProduction);
+ const editorPreviewViteConfig =
+ await getEditorPreviewDefaultConfig(isProduction);
+ const viteBuildConfigs: InlineConfig[] = [
+ {
+ ...resultViteConfig,
+ configFile: false,
+ root: PROJECT_DIRECTORY,
+ },
+ {
+ ...editorConfigViteConfig,
+ configFile: false,
+ root: PROJECT_DIRECTORY,
+ logLevel: "silent",
+ },
+ {
+ ...editorPreviewViteConfig,
+ configFile: false,
+ root: PROJECT_DIRECTORY,
+ logLevel: "silent",
+ },
+ ];
+
+ await Promise.all(
+ viteBuildConfigs.map(async (config) => {
+ await viteBuild(config);
+ }),
+ );
+
+ showMessage("Generate mpk file");
+
+ const packageJson = await getWidgetPackageJson();
+ const packageName = packageJson.packagePath;
+ const mpkFileName = `${packageName}.${widgetName}.mpk`;
+ const mpkFileDestPath = path.join(outputDir, mpkFileName);
+ const mendixWidgetDirectory = await getMendixWidgetDirectory();
+ const mendixMpkFileDestPath = path.join(mendixWidgetDirectory, mpkFileName);
+
+ await zip(WEB_OUTPUT_DIRECTORY, mpkFileDestPath);
+ await fs.copyFile(mpkFileDestPath, mendixMpkFileDestPath);
+
+ showMessage(`${COLOR_GREEN("Build complete.")}`);
+ } catch (error) {
+ showMessage(
+ `${COLOR_ERROR("Build failed.")}\nError occurred: ${COLOR_ERROR((error as Error).stack)}`,
+ );
+ }
+};
+
+export default buildWebCommand;
diff --git a/src/commands/start/web/index.ts b/src/commands/start/web/index.ts
index c825766..d850925 100644
--- a/src/commands/start/web/index.ts
+++ b/src/commands/start/web/index.ts
@@ -1,136 +1,167 @@
-import fs from 'fs/promises';
-import path from 'path';
-import { UserConfig, createServer } from 'vite';
-import { PluginOption } from 'vite';
-
-import { CLI_DIRECTORY, COLOR_ERROR, COLOR_GREEN, PROJECT_DIRECTORY, VITE_CONFIGURATION_FILENAME } from "../../../constants";
-import showMessage from "../../../utils/showMessage";
-import getViteWatchOutputDirectory from "../../../utils/getViteWatchOutputDirectory";
-import pathIsExists from "../../../utils/pathIsExists";
-import { getViteDefaultConfig } from '../../../configurations/vite';
-import getWidgetName from '../../../utils/getWidgetName';
-import getViteUserConfiguration from '../../../utils/getViteUserConfiguration';
-import { generateTypesFromFile } from '../../../type-generator';
-import { mendixHotreloadReactPlugin } from '../../../configurations/vite/plugins/mendix-hotreload-react-plugin';
-import { mendixPatchViteClientPlugin } from '../../../configurations/vite/plugins/mendix-patch-vite-client-plugin';
-import typescript from 'rollup-plugin-typescript2';
-
-const generateTyping = async () => {
- const widgetName = await getWidgetName();
- const originWidgetXmlPath = path.join(PROJECT_DIRECTORY, `src/${widgetName}.xml`);
- const typingsPath = path.join(PROJECT_DIRECTORY, 'typings');
- const typingsDirExists = await pathIsExists(typingsPath);
-
- if (typingsDirExists) {
- await fs.rm(typingsPath, { recursive: true, force: true });
- }
-
- await fs.mkdir(typingsPath);
-
- const newTypingsFilePath = path.join(typingsPath, `${widgetName}Props.d.ts`);
- const typingContents = await generateTypesFromFile(originWidgetXmlPath, 'web');
-
- await fs.writeFile(newTypingsFilePath, typingContents);
-};
-
-const startWebCommand = async () => {
- try {
- showMessage('Start widget server');
-
- await generateTyping();
-
- const customViteConfigPath = path.join(PROJECT_DIRECTORY, VITE_CONFIGURATION_FILENAME);
- const viteConfigIsExists = await pathIsExists(customViteConfigPath);
- let resultViteConfig: UserConfig;
- const widgetName = await getWidgetName();
-
- if (viteConfigIsExists) {
- const userConfig = await getViteUserConfiguration(customViteConfigPath);
-
- resultViteConfig = await getViteDefaultConfig(false, userConfig);
- } else {
- resultViteConfig = await getViteDefaultConfig(false);
- }
-
- const viteCachePath = path.join(PROJECT_DIRECTORY, 'node_modules/.vite');
- const viteCachePathExists = await pathIsExists(viteCachePath);
-
- if (viteCachePathExists) {
- await fs.rm(viteCachePath, { recursive: true, force: true });
- }
-
- const viteServer = await createServer({
- ...resultViteConfig,
- root: PROJECT_DIRECTORY,
- server: {
- fs: {
- strict: false
- },
- watch: {
- usePolling: true,
- interval: 100
- },
- },
- plugins: [
- typescript({
- tsconfig: path.join(PROJECT_DIRECTORY, 'tsconfig.json'),
- tsconfigOverride: {
- compilerOptions: {
- jsx: 'preserve',
- preserveConstEnums: false,
- isolatedModules: false,
- declaration: false
- }
- },
- include: ["src/**/*.ts", "src/**/*.tsx"],
- exclude: ["node_modules/**", "src/**/*.d.ts"],
- check: false,
- }),
- ...resultViteConfig.plugins as PluginOption[],
- mendixHotreloadReactPlugin(),
- mendixPatchViteClientPlugin(),
- {
- name: 'mendix-xml-watch-plugin',
- configureServer(server) {
- server.watcher.on('change', (file) => {
- if (file.endsWith('xml')) {
- generateTyping();
- }
- });
- }
- },
- ],
- });
-
- await viteServer.listen();
-
- showMessage('Generate hot reload widget');
-
- const hotReloadTemplate = path.join(CLI_DIRECTORY, 'src/configurations/hotReload/widget.proxy.js.template');
- const hotReloadContents = await fs.readFile(hotReloadTemplate, 'utf-8');
- const devServerUrl = viteServer.resolvedUrls?.local[0] || '';
- const newHotReloadContents = hotReloadContents
- .replaceAll('{{ WIDGET_NAME }}', widgetName)
- .replaceAll('{{ DEV_SERVER_URL }}', devServerUrl)
-
- const distDir = await getViteWatchOutputDirectory();
- const distIsExists = await pathIsExists(distDir);
- const hotReloadWidgetPath = path.join(distDir, `${widgetName}.mjs`);
- const dummyCssPath = path.join(distDir, `${widgetName}.css`);
-
- if (distIsExists) {
- await fs.rm(distDir, { recursive: true, force: true });
- }
-
- await fs.mkdir(distDir, { recursive: true });
- await fs.writeFile(hotReloadWidgetPath, newHotReloadContents);
- await fs.writeFile(dummyCssPath, '');
-
- showMessage(`${COLOR_GREEN('Widget hot reload is ready!')}`);
- showMessage(`${COLOR_GREEN('Mendix webpage will refresh shortly. Hot reload will work after refreshing.')}`);
- } catch (error) {
- showMessage(`${COLOR_ERROR('Build failed.')}\nError occurred: ${COLOR_ERROR((error as Error).message)}`);
- }
-};
-
-export default startWebCommand;
\ No newline at end of file
+import fs from "node:fs/promises";
+import path from "node:path";
+import typescript from "rollup-plugin-typescript2";
+import { createServer, type PluginOption, type UserConfig } from "vite";
+import { getViteDefaultConfig } from "../../../configurations/vite";
+import { mendixHotreloadReactPlugin } from "../../../configurations/vite/plugins/mendix-hotreload-react-plugin";
+import { mendixPatchViteClientPlugin } from "../../../configurations/vite/plugins/mendix-patch-vite-client-plugin";
+import {
+ CLI_DIRECTORY,
+ COLOR_ERROR,
+ COLOR_GREEN,
+ PROJECT_DIRECTORY,
+ VITE_CONFIGURATION_FILENAME,
+} from "../../../constants";
+import { generateTypesFromFile } from "../../../type-generator";
+import getViteUserConfiguration from "../../../utils/getViteUserConfiguration";
+import getViteWatchOutputDirectory from "../../../utils/getViteWatchOutputDirectory";
+import getWidgetName from "../../../utils/getWidgetName";
+import pathIsExists from "../../../utils/pathIsExists";
+import showMessage from "../../../utils/showMessage";
+
+const generateTyping = async () => {
+ const widgetName = await getWidgetName();
+ const originWidgetXmlPath = path.join(
+ PROJECT_DIRECTORY,
+ `src/${widgetName}.xml`,
+ );
+ const typingsPath = path.join(PROJECT_DIRECTORY, "typings");
+ const typingsDirExists = await pathIsExists(typingsPath);
+
+ if (typingsDirExists) {
+ await fs.rm(typingsPath, {
+ recursive: true,
+ force: true,
+ });
+ }
+
+ await fs.mkdir(typingsPath);
+
+ const newTypingsFilePath = path.join(typingsPath, `${widgetName}Props.d.ts`);
+ const typingContents = await generateTypesFromFile(
+ originWidgetXmlPath,
+ "web",
+ );
+
+ await fs.writeFile(newTypingsFilePath, typingContents);
+};
+
+const startWebCommand = async () => {
+ try {
+ showMessage("Start widget server");
+
+ await generateTyping();
+
+ const customViteConfigPath = path.join(
+ PROJECT_DIRECTORY,
+ VITE_CONFIGURATION_FILENAME,
+ );
+ const viteConfigIsExists = await pathIsExists(customViteConfigPath);
+ let resultViteConfig: UserConfig;
+ const widgetName = await getWidgetName();
+
+ if (viteConfigIsExists) {
+ const userConfig = await getViteUserConfiguration(customViteConfigPath);
+
+ resultViteConfig = await getViteDefaultConfig(false, userConfig);
+ } else {
+ resultViteConfig = await getViteDefaultConfig(false);
+ }
+
+ const viteCachePath = path.join(PROJECT_DIRECTORY, "node_modules/.vite");
+ const viteCachePathExists = await pathIsExists(viteCachePath);
+
+ if (viteCachePathExists) {
+ await fs.rm(viteCachePath, {
+ recursive: true,
+ force: true,
+ });
+ }
+
+ const viteServer = await createServer({
+ ...resultViteConfig,
+ root: PROJECT_DIRECTORY,
+ server: {
+ fs: {
+ strict: false,
+ },
+ watch: {
+ usePolling: true,
+ interval: 100,
+ },
+ },
+ plugins: [
+ typescript({
+ tsconfig: path.join(PROJECT_DIRECTORY, "tsconfig.json"),
+ tsconfigOverride: {
+ compilerOptions: {
+ jsx: "preserve",
+ preserveConstEnums: false,
+ isolatedModules: false,
+ declaration: false,
+ },
+ },
+ include: ["src/**/*.ts", "src/**/*.tsx"],
+ exclude: ["node_modules/**", "src/**/*.d.ts"],
+ check: false,
+ }),
+ ...(resultViteConfig.plugins as PluginOption[]),
+ mendixHotreloadReactPlugin(),
+ mendixPatchViteClientPlugin(),
+ {
+ name: "mendix-xml-watch-plugin",
+ configureServer(server) {
+ server.watcher.on("change", (file) => {
+ if (file.endsWith("xml")) {
+ generateTyping();
+ }
+ });
+ },
+ },
+ ],
+ });
+
+ await viteServer.listen();
+
+ showMessage("Generate hot reload widget");
+
+ const hotReloadTemplate = path.join(
+ CLI_DIRECTORY,
+ "src/configurations/hotReload/widget.proxy.js.template",
+ );
+ const hotReloadContents = await fs.readFile(hotReloadTemplate, "utf-8");
+ const devServerUrl = viteServer.resolvedUrls?.local[0] || "";
+ const newHotReloadContents = hotReloadContents
+ .replaceAll("{{ WIDGET_NAME }}", widgetName)
+ .replaceAll("{{ DEV_SERVER_URL }}", devServerUrl);
+
+ const distDir = await getViteWatchOutputDirectory();
+ const distIsExists = await pathIsExists(distDir);
+ const hotReloadWidgetPath = path.join(distDir, `${widgetName}.mjs`);
+ const dummyCssPath = path.join(distDir, `${widgetName}.css`);
+
+ if (distIsExists) {
+ await fs.rm(distDir, {
+ recursive: true,
+ force: true,
+ });
+ }
+
+ await fs.mkdir(distDir, {
+ recursive: true,
+ });
+ await fs.writeFile(hotReloadWidgetPath, newHotReloadContents);
+ await fs.writeFile(dummyCssPath, "");
+
+ showMessage(`${COLOR_GREEN("Widget hot reload is ready!")}`);
+ showMessage(
+ `${COLOR_GREEN("Mendix webpage will refresh shortly. Hot reload will work after refreshing.")}`,
+ );
+ } catch (error) {
+ showMessage(
+ `${COLOR_ERROR("Build failed.")}\nError occurred: ${COLOR_ERROR((error as Error).message)}`,
+ );
+ }
+};
+
+export default startWebCommand;
diff --git a/src/configurations/typescript/tsconfig.base.json b/src/configurations/typescript/tsconfig.base.json
index f02bb19..93a0799 100644
--- a/src/configurations/typescript/tsconfig.base.json
+++ b/src/configurations/typescript/tsconfig.base.json
@@ -24,4 +24,4 @@
"preserveConstEnums": false,
"isolatedModules": false
}
-}
\ No newline at end of file
+}
diff --git a/src/configurations/vite/index.ts b/src/configurations/vite/index.ts
index 1c56827..479e77d 100644
--- a/src/configurations/vite/index.ts
+++ b/src/configurations/vite/index.ts
@@ -1,153 +1,165 @@
-import { UserConfig } from "vite";
-import react from '@vitejs/plugin-react';
-import path from "path";
-import typescript from "rollup-plugin-typescript2";
-
-import getWidgetName from "../../utils/getWidgetName";
-import { PROJECT_DIRECTORY, WEB_OUTPUT_DIRECTORY } from "../../constants";
-import getViteOutputDirectory from "../../utils/getViteOutputDirectory";
-import { PWTConfig } from "../..";
-
-export const getEditorConfigDefaultConfig = async (isProduction: boolean): Promise => {
- const widgetName = await getWidgetName();
-
- return {
- plugins: [],
- build: {
- outDir: WEB_OUTPUT_DIRECTORY,
- minify: isProduction ? true : false,
- emptyOutDir: false,
- sourcemap: isProduction ? false : true,
- lib: {
- entry: path.join(PROJECT_DIRECTORY, `/src/${widgetName}.editorConfig.ts`),
- name: `${widgetName}.editorConfig`,
- fileName: () => {
- return `${widgetName}.editorConfig.js`;
- },
- formats: ['umd']
- },
- },
- };
-};
-
-export const getEditorPreviewDefaultConfig = async (isProduction: boolean): Promise => {
- const widgetName = await getWidgetName();
-
- return {
- plugins: [
- react({
- jsxRuntime: 'classic'
- })
- ],
- define: {
- 'process.env': {},
- 'process.env.NODE_ENV': '"production"'
- },
- build: {
- outDir: WEB_OUTPUT_DIRECTORY,
- minify: isProduction ? true : false,
- emptyOutDir: false,
- sourcemap: isProduction ? false : true,
- lib: {
- entry: path.join(PROJECT_DIRECTORY, `/src/${widgetName}.editorPreview.tsx`),
- name: `${widgetName}.editorPreview`,
- fileName: () => {
- return `${widgetName}.editorPreview.js`;
- },
- formats: ['umd']
- },
- rolldownOptions: {
- external: [
- 'react',
- 'react-dom',
- 'react-dom/client',
- 'react/jsx-runtime',
- 'react/jsx-dev-runtime',
- /^mendix($|\/)/
- ],
- output: {
- globals: {
- react: 'React',
- 'react-dom': 'ReactDOM',
- 'react-dom/client': 'ReactDOM'
- }
- }
- }
- },
- };
-};
-
-export const getViteDefaultConfig = async (isProduction: boolean, userCustomConfig?: PWTConfig): Promise => {
- const widgetName = await getWidgetName();
- const viteOutputDirectory = await getViteOutputDirectory();
-
- return {
- plugins: [
- react({
- ...userCustomConfig?.reactPluginOptions || {},
- jsxRuntime: 'classic'
- })
- ],
- define: {
- 'process.env': {},
- 'process.env.NODE_ENV': isProduction ? '"production"' : '"development"'
- },
- build: {
- outDir: viteOutputDirectory,
- minify: isProduction ? true : false,
- cssMinify: isProduction ? true : false,
- sourcemap: isProduction ? false : true,
- lib: {
- formats: isProduction ? ['umd'] : ['es', 'umd'],
- entry: path.join(PROJECT_DIRECTORY, `/src/${widgetName}.tsx`),
- name: widgetName,
- fileName: (format, entry) => {
- if (format === 'umd') {
- return `${widgetName}.js`;
- }
-
- if (format === 'es') {
- return `${widgetName}.mjs`;
- }
-
- return entry;
- },
- cssFileName: widgetName
- },
- rolldownOptions: {
- plugins: [
- typescript({
- tsconfig: path.join(PROJECT_DIRECTORY, 'tsconfig.json'),
- tsconfigOverride: {
- compilerOptions: {
- jsx: 'preserve',
- preserveConstEnums: false,
- isolatedModules: false,
- declaration: false
- }
- },
- include: ["src/**/*.ts", "src/**/*.tsx"],
- exclude: ["node_modules/**", "src/**/*.d.ts"],
- check: false,
- })
- ],
- external: [
- 'react',
- 'react-dom',
- 'react-dom/client',
- 'react/jsx-runtime',
- 'react/jsx-dev-runtime',
- /^mendix($|\/)/
- ],
- output: {
- globals: {
- react: 'React',
- 'react-dom': 'ReactDOM',
- 'react-dom/client': 'ReactDOM'
- }
- }
- }
- },
- ...userCustomConfig
- }
-};
\ No newline at end of file
+import path from "node:path";
+import react from "@vitejs/plugin-react";
+import typescript from "rollup-plugin-typescript2";
+import type { UserConfig } from "vite";
+import type { PWTConfig } from "../..";
+import { PROJECT_DIRECTORY, WEB_OUTPUT_DIRECTORY } from "../../constants";
+import getViteOutputDirectory from "../../utils/getViteOutputDirectory";
+import getWidgetName from "../../utils/getWidgetName";
+
+export const getEditorConfigDefaultConfig = async (
+ isProduction: boolean,
+): Promise => {
+ const widgetName = await getWidgetName();
+
+ return {
+ plugins: [],
+ build: {
+ outDir: WEB_OUTPUT_DIRECTORY,
+ minify: !!isProduction,
+ emptyOutDir: false,
+ sourcemap: !isProduction,
+ lib: {
+ entry: path.join(
+ PROJECT_DIRECTORY,
+ `/src/${widgetName}.editorConfig.ts`,
+ ),
+ name: `${widgetName}.editorConfig`,
+ fileName: () => {
+ return `${widgetName}.editorConfig.js`;
+ },
+ formats: ["umd"],
+ },
+ },
+ };
+};
+
+export const getEditorPreviewDefaultConfig = async (
+ isProduction: boolean,
+): Promise => {
+ const widgetName = await getWidgetName();
+
+ return {
+ plugins: [
+ react({
+ jsxRuntime: "classic",
+ }),
+ ],
+ define: {
+ "process.env": {},
+ "process.env.NODE_ENV": '"production"',
+ },
+ build: {
+ outDir: WEB_OUTPUT_DIRECTORY,
+ minify: !!isProduction,
+ emptyOutDir: false,
+ sourcemap: !isProduction,
+ lib: {
+ entry: path.join(
+ PROJECT_DIRECTORY,
+ `/src/${widgetName}.editorPreview.tsx`,
+ ),
+ name: `${widgetName}.editorPreview`,
+ fileName: () => {
+ return `${widgetName}.editorPreview.js`;
+ },
+ formats: ["umd"],
+ },
+ rolldownOptions: {
+ external: [
+ "react",
+ "react-dom",
+ "react-dom/client",
+ "react/jsx-runtime",
+ "react/jsx-dev-runtime",
+ /^mendix($|\/)/,
+ ],
+ output: {
+ globals: {
+ react: "React",
+ "react-dom": "ReactDOM",
+ "react-dom/client": "ReactDOM",
+ },
+ },
+ },
+ },
+ };
+};
+
+export const getViteDefaultConfig = async (
+ isProduction: boolean,
+ userCustomConfig?: PWTConfig,
+): Promise => {
+ const widgetName = await getWidgetName();
+ const viteOutputDirectory = await getViteOutputDirectory();
+
+ return {
+ plugins: [
+ react({
+ ...(userCustomConfig?.reactPluginOptions || {}),
+ jsxRuntime: "classic",
+ }),
+ ],
+ define: {
+ "process.env": {},
+ "process.env.NODE_ENV": isProduction ? '"production"' : '"development"',
+ },
+ build: {
+ outDir: viteOutputDirectory,
+ minify: !!isProduction,
+ cssMinify: !!isProduction,
+ sourcemap: !isProduction,
+ lib: {
+ formats: isProduction ? ["umd"] : ["es", "umd"],
+ entry: path.join(PROJECT_DIRECTORY, `/src/${widgetName}.tsx`),
+ name: widgetName,
+ fileName: (format, entry) => {
+ if (format === "umd") {
+ return `${widgetName}.js`;
+ }
+
+ if (format === "es") {
+ return `${widgetName}.mjs`;
+ }
+
+ return entry;
+ },
+ cssFileName: widgetName,
+ },
+ rolldownOptions: {
+ plugins: [
+ typescript({
+ tsconfig: path.join(PROJECT_DIRECTORY, "tsconfig.json"),
+ tsconfigOverride: {
+ compilerOptions: {
+ jsx: "preserve",
+ preserveConstEnums: false,
+ isolatedModules: false,
+ declaration: false,
+ },
+ },
+ include: ["src/**/*.ts", "src/**/*.tsx"],
+ exclude: ["node_modules/**", "src/**/*.d.ts"],
+ check: false,
+ }),
+ ],
+ external: [
+ "react",
+ "react-dom",
+ "react-dom/client",
+ "react/jsx-runtime",
+ "react/jsx-dev-runtime",
+ /^mendix($|\/)/,
+ ],
+ output: {
+ globals: {
+ react: "React",
+ "react-dom": "ReactDOM",
+ "react-dom/client": "ReactDOM",
+ },
+ },
+ },
+ },
+ ...userCustomConfig,
+ };
+};
diff --git a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts
index 91e6880..7d3691c 100644
--- a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts
+++ b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts
@@ -1,34 +1,49 @@
-import { Plugin } from "vite";
+import type { Plugin } from "vite";
// @note When the React version of Mendix is updated, the following content must also be updated.
// @todo Depending on the React version, we need to consider whether there is a way to handle this automatically rather than manually.
export function mendixHotreloadReactPlugin(): Plugin {
return {
- name: 'mendix-hotreload-react-18.2.0',
- enforce: 'pre',
+ name: "mendix-hotreload-react-18.2.0",
+ enforce: "pre",
resolveId(id) {
- if (id === 'react') {
- return { id: 'mendix:react', external: true };
+ if (id === "react") {
+ return {
+ id: "mendix:react",
+ external: true,
+ };
}
- if (id === 'react-dom') {
- return { id: 'mendix:react-dom', external: true };
+ if (id === "react-dom") {
+ return {
+ id: "mendix:react-dom",
+ external: true,
+ };
}
- if (id === 'react-dom/client') {
- return { id: 'mendix:react-dom/client', external: true };
+ if (id === "react-dom/client") {
+ return {
+ id: "mendix:react-dom/client",
+ external: true,
+ };
}
- if (id === 'react/jsx-runtime') {
- return { id: 'mendix:react/jsx-runtime', external: true };
+ if (id === "react/jsx-runtime") {
+ return {
+ id: "mendix:react/jsx-runtime",
+ external: true,
+ };
}
- if (id === 'react/jsx-dev-runtime') {
- return { id: 'mendix:react/jsx-dev-runtime', external: true };
+ if (id === "react/jsx-dev-runtime") {
+ return {
+ id: "mendix:react/jsx-dev-runtime",
+ external: true,
+ };
}
},
load(id) {
- if (id === 'mendix:react') {
+ if (id === "mendix:react") {
return `
const React = window.React;
@@ -71,7 +86,7 @@ export function mendixHotreloadReactPlugin(): Plugin {
`;
}
- if (id === 'mendix:react-dom') {
+ if (id === "mendix:react-dom") {
return `
const ReactDOM = window.ReactDOM;
@@ -91,7 +106,7 @@ export function mendixHotreloadReactPlugin(): Plugin {
`;
}
- if (id === 'mendix:react-dom/client') {
+ if (id === "mendix:react-dom/client") {
return `
const ReactDOMClient = window.ReactDOMClient;
@@ -102,7 +117,7 @@ export function mendixHotreloadReactPlugin(): Plugin {
`;
}
- if (id === 'mendix:react/jsx-runtime') {
+ if (id === "mendix:react/jsx-runtime") {
return `
const ReactJSXRuntime = window.ReactJSXRuntime;
@@ -114,7 +129,7 @@ export function mendixHotreloadReactPlugin(): Plugin {
`;
}
- if (id === 'mendix:react/jsx-dev-runtime') {
+ if (id === "mendix:react/jsx-dev-runtime") {
return `
const ReactJSXDevRuntime = window.ReactJSXDevRuntime;
@@ -124,6 +139,6 @@ export function mendixHotreloadReactPlugin(): Plugin {
export default ReactJSXDevRuntime;
`;
}
- }
+ },
};
-}
\ No newline at end of file
+}
diff --git a/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts b/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts
index 5513bb2..095290c 100644
--- a/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts
+++ b/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts
@@ -1,18 +1,20 @@
-import { Plugin } from "vite";
+import type { Plugin } from "vite";
export function mendixPatchViteClientPlugin(): Plugin {
return {
- name: 'mendix-patch-vite-client',
- enforce: 'pre',
- apply: 'serve',
+ name: "mendix-patch-vite-client",
+ enforce: "pre",
+ apply: "serve",
configureServer(server) {
server.middlewares.use(async (req, res, next) => {
- const url = req.url || '';
+ const url = req.url || "";
- if (url.includes('@vite/client.mjs')) {
- const transformed = await server.transformRequest('/@vite/client.mjs');
- let code = transformed?.code || '';
- const rePageReload = /const\s+pageReload\s*=\s*debounceReload\(\s*(\d+)\s*\)/;
+ if (url.includes("@vite/client.mjs")) {
+ const transformed =
+ await server.transformRequest("/@vite/client.mjs");
+ let code = transformed?.code || "";
+ const rePageReload =
+ /const\s+pageReload\s*=\s*debounceReload\(\s*(\d+)\s*\)/;
const m = code.match(rePageReload);
if (m) {
@@ -41,16 +43,22 @@ const __mx_debounceReload = (time) => {
};
`;
- code = code.replace(rePageReload, `${injectScript}\nconst pageReload = __mx_debounceReload(${delay})`);
+ code = code.replace(
+ rePageReload,
+ `${injectScript}\nconst pageReload = __mx_debounceReload(${delay})`,
+ );
}
- res.setHeader('Content-Type', 'application/javascript; charset=utf-8');
+ res.setHeader(
+ "Content-Type",
+ "application/javascript; charset=utf-8",
+ );
res.end(code);
return;
}
next();
});
- }
+ },
};
-}
\ No newline at end of file
+}
diff --git a/src/constants/index.ts b/src/constants/index.ts
index e871067..7fe2d98 100644
--- a/src/constants/index.ts
+++ b/src/constants/index.ts
@@ -1,18 +1,24 @@
-import chalk from "chalk";
-import path from "path";
-
-export const PROJECT_DIRECTORY = process.cwd();
-
-export const CLI_DIRECTORY = path.join(PROJECT_DIRECTORY, 'node_modules/@repixelcorp/hyper-pwt');
-
-export const DIST_DIRECTORY_NAME = 'dist';
-
-export const WEB_OUTPUT_DIRECTORY = path.join(PROJECT_DIRECTORY, `/${DIST_DIRECTORY_NAME}/tmp/widgets`);
-
-export const VITE_CONFIGURATION_FILENAME = 'vite.config.mjs';
-
-export const COLOR_NAME = chalk.bold.blueBright;
-
-export const COLOR_ERROR = chalk.bold.red;
-
-export const COLOR_GREEN = chalk.bold.greenBright;
\ No newline at end of file
+import path from "node:path";
+import chalk from "chalk";
+
+export const PROJECT_DIRECTORY = process.cwd();
+
+export const CLI_DIRECTORY = path.join(
+ PROJECT_DIRECTORY,
+ "node_modules/@repixelcorp/hyper-pwt",
+);
+
+export const DIST_DIRECTORY_NAME = "dist";
+
+export const WEB_OUTPUT_DIRECTORY = path.join(
+ PROJECT_DIRECTORY,
+ `/${DIST_DIRECTORY_NAME}/tmp/widgets`,
+);
+
+export const VITE_CONFIGURATION_FILENAME = "vite.config.mjs";
+
+export const COLOR_NAME = chalk.bold.blueBright;
+
+export const COLOR_ERROR = chalk.bold.red;
+
+export const COLOR_GREEN = chalk.bold.greenBright;
diff --git a/src/index.ts b/src/index.ts
index d13c5e8..f3f9cfb 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -1,13 +1,15 @@
-import type { UserConfig } from "vite";
-import reactPlugin from "@vitejs/plugin-react";
-
-export type PWTConfig = UserConfig & {
- reactPluginOptions?: Parameters[0];
-};
-
-export type PWTConfigFnPromise = () => Promise;
-export type PWTConfigFn = () => PWTConfig | Promise;
-
-export function definePWTConfig(config: PWTConfigFn | PWTConfigFnPromise): PWTConfigFn | PWTConfigFnPromise {
- return config;
-}
+import type reactPlugin from "@vitejs/plugin-react";
+import type { UserConfig } from "vite";
+
+export type PWTConfig = UserConfig & {
+ reactPluginOptions?: Parameters[0];
+};
+
+export type PWTConfigFnPromise = () => Promise;
+export type PWTConfigFn = () => PWTConfig | Promise;
+
+export function definePWTConfig(
+ config: PWTConfigFn | PWTConfigFnPromise,
+): PWTConfigFn | PWTConfigFnPromise {
+ return config;
+}
diff --git a/src/type-generator/generator.ts b/src/type-generator/generator.ts
index 372dd79..0b34c7a 100644
--- a/src/type-generator/generator.ts
+++ b/src/type-generator/generator.ts
@@ -1,146 +1,166 @@
-import type {
- WidgetDefinition,
- Property,
- PropertyGroup,
- SystemProperty,
-} from './types';
-import type { GenerateTargetPlatform } from './mendix-types';
-import {
- mapPropertyTypeToTS,
- pascalCase,
- sanitizePropertyKey,
- formatDescription,
-} from './utils';
-import { getMendixImports, generateMendixImports } from './mendix-types';
-import {
- extractSystemProperties,
- hasLabelProperty,
- generateSystemProps,
- getSystemPropsImports
-} from './system-props';
-import { generateHeaderComment } from './header';
-
-export function generateTypeDefinition(widget: WidgetDefinition, target: GenerateTargetPlatform): string {
- const interfaceName = generateInterfaceName(widget.name);
- const properties = extractAllProperties(widget.properties);
- const systemProps = extractSystemProperties(widget.properties);
- const hasLabel = hasLabelProperty(systemProps);
- const widgetProperties = properties.filter(p => !isSystemProperty(p)) as Property[];
-
- let output = '';
-
- output += generateHeaderComment();
-
- const imports = getMendixImports(widgetProperties, target);
- const systemImports = getSystemPropsImports({ hasLabel, platform: target });
- const allImports = [...imports, ...systemImports];
- const importStatements = generateMendixImports(allImports);
-
- if (importStatements) {
- output += importStatements + '\n';
- }
-
- output += generateJSDoc(widget);
- output += `export interface ${interfaceName} {\n`;
-
- const systemPropsLines = generateSystemProps({ hasLabel, platform: target });
-
- for (const line of systemPropsLines) {
- output += ` ${line}\n`;
- }
-
- if (systemPropsLines.length > 0 && widgetProperties.length > 0) {
- output += `\n // Widget properties\n`;
- }
-
- for (const property of widgetProperties) {
- output += generatePropertyDefinition(property, target);
- }
-
- output += '}\n';
-
- return output;
-}
-
-function generateInterfaceName(widgetName: string): string {
- return `${pascalCase(widgetName)}ContainerProps`;
-}
-
-export function extractAllProperties(properties: PropertyGroup[] | Property[]): (Property | SystemProperty)[] {
- const result: (Property | SystemProperty)[] = [];
-
- for (const item of properties) {
- if (isPropertyGroup(item)) {
- result.push(...item.properties);
- } else {
- result.push(item);
- }
- }
-
- return result;
-}
-
-function isPropertyGroup(item: PropertyGroup | Property | SystemProperty): item is PropertyGroup {
- return 'caption' in item && 'properties' in item;
-}
-
-function isSystemProperty(item: Property | SystemProperty): item is SystemProperty {
- return !('type' in item) && 'key' in item;
-}
-
-function generateJSDoc(widget: WidgetDefinition): string {
- let jsDoc = '/**\n';
- jsDoc += ` * Props for ${widget.name}\n`;
-
- if (widget.description) {
- jsDoc += ` * ${formatDescription(widget.description)}\n`;
- }
-
- if (widget.needsEntityContext) {
- jsDoc += ` * @needsEntityContext true\n`;
- }
-
- if (widget.supportedPlatform && widget.supportedPlatform !== 'Web') {
- jsDoc += ` * @platform ${widget.supportedPlatform}\n`;
- }
-
- jsDoc += ' */\n';
- return jsDoc;
-}
-
-function generatePropertyDefinition(property: Property, target: GenerateTargetPlatform): string {
- const indent = ' ';
- let output = '';
-
- if (property.description) {
- output += `${indent}/**\n`;
- output += `${indent} * ${formatDescription(property.description)}\n`;
-
- if (property.caption && property.caption !== property.description) {
- output += `${indent} * @caption ${property.caption}\n`;
- }
-
- if (property.defaultValue !== undefined && property.defaultValue !== '') {
- output += `${indent} * @default ${property.defaultValue}\n`;
- }
-
- if (property.type === 'attribute' && property.attributeTypes) {
- output += `${indent} * @attributeTypes ${property.attributeTypes.join(', ')}\n`;
- }
-
- if (property.type === 'enumeration' && property.enumerationValues) {
- const values = property.enumerationValues.map(ev => ev.key).join(', ');
- output += `${indent} * @enum {${values}}\n`;
- }
-
- output += `${indent} */\n`;
- }
-
- const propertyKey = sanitizePropertyKey(property.key);
- const optional = property.required === false ? '?' : '';
- const propertyType = mapPropertyTypeToTS(property, target);
-
- output += `${indent}${propertyKey}${optional}: ${propertyType};\n`;
-
- return output;
-}
\ No newline at end of file
+import { generateHeaderComment } from "./header";
+import type { GenerateTargetPlatform } from "./mendix-types";
+import { generateMendixImports, getMendixImports } from "./mendix-types";
+import {
+ extractSystemProperties,
+ generateSystemProps,
+ getSystemPropsImports,
+ hasLabelProperty,
+} from "./system-props";
+import type {
+ Property,
+ PropertyGroup,
+ SystemProperty,
+ WidgetDefinition,
+} from "./types";
+import {
+ formatDescription,
+ mapPropertyTypeToTS,
+ pascalCase,
+ sanitizePropertyKey,
+} from "./utils";
+
+export function generateTypeDefinition(
+ widget: WidgetDefinition,
+ target: GenerateTargetPlatform,
+): string {
+ const interfaceName = generateInterfaceName(widget.name);
+ const properties = extractAllProperties(widget.properties);
+ const systemProps = extractSystemProperties(widget.properties);
+ const hasLabel = hasLabelProperty(systemProps);
+ const widgetProperties = properties.filter(
+ (p) => !isSystemProperty(p),
+ ) as Property[];
+
+ let output = "";
+
+ output += generateHeaderComment();
+
+ const imports = getMendixImports(widgetProperties, target);
+ const systemImports = getSystemPropsImports({
+ hasLabel,
+ platform: target,
+ });
+ const allImports = [...imports, ...systemImports];
+ const importStatements = generateMendixImports(allImports);
+
+ if (importStatements) {
+ output += `${importStatements}\n`;
+ }
+
+ output += generateJSDoc(widget);
+ output += `export interface ${interfaceName} {\n`;
+
+ const systemPropsLines = generateSystemProps({
+ hasLabel,
+ platform: target,
+ });
+
+ for (const line of systemPropsLines) {
+ output += ` ${line}\n`;
+ }
+
+ if (systemPropsLines.length > 0 && widgetProperties.length > 0) {
+ output += `\n // Widget properties\n`;
+ }
+
+ for (const property of widgetProperties) {
+ output += generatePropertyDefinition(property, target);
+ }
+
+ output += "}\n";
+
+ return output;
+}
+
+function generateInterfaceName(widgetName: string): string {
+ return `${pascalCase(widgetName)}ContainerProps`;
+}
+
+export function extractAllProperties(
+ properties: PropertyGroup[] | Property[],
+): (Property | SystemProperty)[] {
+ const result: (Property | SystemProperty)[] = [];
+
+ for (const item of properties) {
+ if (isPropertyGroup(item)) {
+ result.push(...item.properties);
+ } else {
+ result.push(item);
+ }
+ }
+
+ return result;
+}
+
+function isPropertyGroup(
+ item: PropertyGroup | Property | SystemProperty,
+): item is PropertyGroup {
+ return "caption" in item && "properties" in item;
+}
+
+function isSystemProperty(
+ item: Property | SystemProperty,
+): item is SystemProperty {
+ return !("type" in item) && "key" in item;
+}
+
+function generateJSDoc(widget: WidgetDefinition): string {
+ let jsDoc = "/**\n";
+ jsDoc += ` * Props for ${widget.name}\n`;
+
+ if (widget.description) {
+ jsDoc += ` * ${formatDescription(widget.description)}\n`;
+ }
+
+ if (widget.needsEntityContext) {
+ jsDoc += ` * @needsEntityContext true\n`;
+ }
+
+ if (widget.supportedPlatform && widget.supportedPlatform !== "Web") {
+ jsDoc += ` * @platform ${widget.supportedPlatform}\n`;
+ }
+
+ jsDoc += " */\n";
+ return jsDoc;
+}
+
+function generatePropertyDefinition(
+ property: Property,
+ target: GenerateTargetPlatform,
+): string {
+ const indent = " ";
+ let output = "";
+
+ if (property.description) {
+ output += `${indent}/**\n`;
+ output += `${indent} * ${formatDescription(property.description)}\n`;
+
+ if (property.caption && property.caption !== property.description) {
+ output += `${indent} * @caption ${property.caption}\n`;
+ }
+
+ if (property.defaultValue !== undefined && property.defaultValue !== "") {
+ output += `${indent} * @default ${property.defaultValue}\n`;
+ }
+
+ if (property.type === "attribute" && property.attributeTypes) {
+ output += `${indent} * @attributeTypes ${property.attributeTypes.join(", ")}\n`;
+ }
+
+ if (property.type === "enumeration" && property.enumerationValues) {
+ const values = property.enumerationValues.map((ev) => ev.key).join(", ");
+ output += `${indent} * @enum {${values}}\n`;
+ }
+
+ output += `${indent} */\n`;
+ }
+
+ const propertyKey = sanitizePropertyKey(property.key);
+ const optional = property.required === false ? "?" : "";
+ const propertyType = mapPropertyTypeToTS(property, target);
+
+ output += `${indent}${propertyKey}${optional}: ${propertyType};\n`;
+
+ return output;
+}
diff --git a/src/type-generator/header.ts b/src/type-generator/header.ts
index 0ff29ba..1237084 100644
--- a/src/type-generator/header.ts
+++ b/src/type-generator/header.ts
@@ -1,37 +1,43 @@
-import { readFileSync } from 'fs';
-import { join } from 'path';
-
-export function getPackageVersion(): string {
- try {
- const packageJsonPath = join(process.cwd(), 'node_modules', '@repixelcorp', 'hyper-pwt', 'package.json');
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
-
- return packageJson.version || 'unknown';
- } catch {
- try {
- const currentDir = process.cwd();
- const packageJsonPath = join(currentDir, 'package.json');
- const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf-8'));
-
- if (packageJson.name === '@repixelcorp/hyper-pwt') {
- return packageJson.version || 'unknown';
- }
- } catch {
- return 'unknown';
- }
- }
-}
-
-export function generateHeaderComment(): string {
- const version = getPackageVersion();
-
- return `/**
- * This file was automatically generated by @repixelcorp/hyper-pwt v${version}
- * DO NOT MODIFY THIS FILE DIRECTLY
- *
- * To regenerate this file, run the type generator with your widget XML file.
- * Any manual changes to this file will be lost when the types are regenerated.
- */
-
-`;
-}
\ No newline at end of file
+import { readFileSync } from "node:fs";
+import { join } from "node:path";
+
+export function getPackageVersion(): string {
+ try {
+ const packageJsonPath = join(
+ process.cwd(),
+ "node_modules",
+ "@repixelcorp",
+ "hyper-pwt",
+ "package.json",
+ );
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
+
+ return packageJson.version || "unknown";
+ } catch {
+ try {
+ const currentDir = process.cwd();
+ const packageJsonPath = join(currentDir, "package.json");
+ const packageJson = JSON.parse(readFileSync(packageJsonPath, "utf-8"));
+
+ if (packageJson.name === "@repixelcorp/hyper-pwt") {
+ return packageJson.version || "unknown";
+ }
+ } catch {
+ return "unknown";
+ }
+ }
+}
+
+export function generateHeaderComment(): string {
+ const version = getPackageVersion();
+
+ return `/**
+ * This file was automatically generated by @repixelcorp/hyper-pwt v${version}
+ * DO NOT MODIFY THIS FILE DIRECTLY
+ *
+ * To regenerate this file, run the type generator with your widget XML file.
+ * Any manual changes to this file will be lost when the types are regenerated.
+ */
+
+`;
+}
diff --git a/src/type-generator/index.ts b/src/type-generator/index.ts
index d9cc96c..ff8ba20 100644
--- a/src/type-generator/index.ts
+++ b/src/type-generator/index.ts
@@ -1,25 +1,36 @@
-import { readFile } from 'fs/promises';
-import { parseWidgetXML } from './parser';
-import { generateTypeDefinition } from './generator';
-import { generatePreviewTypeDefinition } from './preview-types';
-import type { GenerateTargetPlatform } from './mendix-types';
-
-export { parseWidgetXML } from './parser';
-export { generateTypeDefinition } from './generator';
-export { generatePreviewTypeDefinition } from './preview-types';
-export type { WidgetDefinition, Property, PropertyGroup, PropertyType } from './types';
-
-export function generateTypes(xmlContent: string, target: GenerateTargetPlatform): string {
- const widget = parseWidgetXML(xmlContent);
- let output = generateTypeDefinition(widget, target);
-
- output += '\n' + generatePreviewTypeDefinition(widget);
-
- return output;
-}
-
-export async function generateTypesFromFile(filePath: string, target: GenerateTargetPlatform): Promise {
- const xmlContent = await readFile(filePath, 'utf-8');
-
- return generateTypes(xmlContent, target);
-}
\ No newline at end of file
+import { readFile } from "node:fs/promises";
+import { generateTypeDefinition } from "./generator";
+import type { GenerateTargetPlatform } from "./mendix-types";
+import { parseWidgetXML } from "./parser";
+import { generatePreviewTypeDefinition } from "./preview-types";
+
+export { generateTypeDefinition } from "./generator";
+export { parseWidgetXML } from "./parser";
+export { generatePreviewTypeDefinition } from "./preview-types";
+export type {
+ Property,
+ PropertyGroup,
+ PropertyType,
+ WidgetDefinition,
+} from "./types";
+
+export function generateTypes(
+ xmlContent: string,
+ target: GenerateTargetPlatform,
+): string {
+ const widget = parseWidgetXML(xmlContent);
+ let output = generateTypeDefinition(widget, target);
+
+ output += `\n${generatePreviewTypeDefinition(widget)}`;
+
+ return output;
+}
+
+export async function generateTypesFromFile(
+ filePath: string,
+ target: GenerateTargetPlatform,
+): Promise {
+ const xmlContent = await readFile(filePath, "utf-8");
+
+ return generateTypes(xmlContent, target);
+}
diff --git a/src/type-generator/mendix-types.ts b/src/type-generator/mendix-types.ts
index 624d9b9..76ae1bc 100644
--- a/src/type-generator/mendix-types.ts
+++ b/src/type-generator/mendix-types.ts
@@ -1,359 +1,385 @@
-import type {
- Property,
- AttributeType,
-} from './types';
-
-export interface MendixTypeMapping {
- type: string;
- imports: Set;
-}
-
-export type GenerateTargetPlatform = 'web' | 'native';
-
-export function getMendixImports(properties: Property[], target: GenerateTargetPlatform): string[] {
- const imports = new Set();
-
- for (const property of properties) {
- const mapping = mapPropertyToMendixType(property, target);
-
- mapping.imports.forEach(imp => imports.add(imp));
- }
-
- return Array.from(imports).sort();
-}
-
-export function mapPropertyToMendixType(property: Property, platform: GenerateTargetPlatform = 'web'): MendixTypeMapping {
- const imports = new Set();
- let type: string;
-
- switch (property.type) {
- case 'string':
- case 'translatableString':
- type = 'string';
- break;
-
- case 'boolean':
- type = 'boolean';
- break;
-
- case 'integer':
- type = 'number';
- break;
-
- case 'decimal':
- imports.add('Big');
- type = 'Big';
- break;
-
- case 'textTemplate':
- imports.add('DynamicValue');
- if (property.dataSource) {
- imports.add('ListExpressionValue');
- type = 'ListExpressionValue';
- } else {
- type = 'DynamicValue';
- }
- break;
-
- case 'action':
- if (property.dataSource) {
- imports.add('ListActionValue');
- type = 'ListActionValue';
- } else {
- imports.add('ActionValue');
- type = 'ActionValue';
- }
- break;
-
- case 'microflow':
- case 'nanoflow':
- imports.add('ActionValue');
- type = 'ActionValue';
- break;
-
- case 'attribute':
- type = mapAttributeToMendixType(property, imports);
- break;
-
- case 'expression':
- type = mapExpressionToMendixType(property, imports);
- break;
-
- case 'datasource':
- imports.add('ListValue');
- type = 'ListValue';
- break;
-
- case 'icon':
- imports.add('DynamicValue');
- if (platform === 'native') {
- imports.add('NativeIcon');
- type = 'DynamicValue';
- } else if (platform === 'web') {
- imports.add('WebIcon');
- type = 'DynamicValue';
- } else {
- imports.add('WebIcon');
- imports.add('NativeIcon');
- type = 'DynamicValue';
- }
- break;
-
- case 'image':
- imports.add('DynamicValue');
- if (platform === 'native') {
- imports.add('NativeImage');
- type = 'DynamicValue';
- } else if (platform === 'web') {
- imports.add('WebImage');
- type = 'DynamicValue';
- } else {
- imports.add('WebImage');
- imports.add('NativeImage');
- type = 'DynamicValue';
- }
- break;
-
- case 'file':
- imports.add('DynamicValue');
- imports.add('FileValue');
- type = 'DynamicValue';
- break;
-
- case 'widgets':
- if (property.dataSource) {
- imports.add('ListWidgetValue');
- type = 'ListWidgetValue';
- } else {
- imports.add('ReactNode');
- type = 'ReactNode';
- }
- break;
-
- case 'object':
- if (property.properties && property.properties.length > 0) {
- type = generateObjectInterface(property);
- } else {
- type = 'object';
- }
- break;
-
- case 'entity':
- imports.add('ObjectItem');
- type = 'ObjectItem';
- break;
-
- case 'entityConstraint':
- type = 'string';
- break;
-
- case 'enumeration':
- if (property.enumerationValues && property.enumerationValues.length > 0) {
- type = property.enumerationValues.map(ev => `"${ev.key}"`).join(' | ');
- } else {
- type = 'string';
- }
- break;
-
- case 'association':
- type = mapAssociationToMendixType(property, imports);
- break;
-
- case 'selection':
- type = mapSelectionToMendixType(property, imports);
- break;
-
- case 'form':
- type = 'string';
- break;
-
- default:
- type = 'any';
- }
-
- if (property.isList && !['datasource', 'widgets'].includes(property.type)) {
- type = `${type}[]`;
- }
-
- return { type, imports };
-}
-
-function mapAttributeToMendixType(property: Property, imports: Set): string {
- const baseType = getAttributeBaseType(property.attributeTypes || []);
-
- if (baseType.includes('Big')) {
- imports.add('Big');
- }
-
- if (property.dataSource) {
- imports.add('ListAttributeValue');
-
- return `ListAttributeValue<${baseType}>`;
- } else {
- imports.add('EditableValue');
-
- return `EditableValue<${baseType}>`;
- }
-}
-
-function mapExpressionToMendixType(property: Property, imports: Set): string {
- const baseType = property.returnType ? mapReturnTypeToTS(property.returnType.type) : 'string';
-
- if (baseType.includes('Big')) {
- imports.add('Big');
- }
-
- if (property.dataSource) {
- imports.add('ListExpressionValue');
-
- const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType;
-
- return `ListExpressionValue<${typeStr}>`;
- } else {
- imports.add('DynamicValue');
-
- const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType;
-
- return `DynamicValue<${typeStr}>`;
- }
-}
-
-function mapAssociationToMendixType(property: Property, imports: Set): string {
- if (!property.associationTypes || property.associationTypes.length === 0) {
- imports.add('ObjectItem');
-
- return 'ObjectItem';
- }
-
- const assocType = property.associationTypes[0];
-
- if (assocType === 'Reference') {
- if (property.dataSource) {
- imports.add('ListReferenceValue');
-
- return 'ListReferenceValue';
- } else {
- imports.add('ReferenceValue');
-
- return 'ReferenceValue';
- }
- } else if (assocType === 'ReferenceSet') {
- if (property.dataSource) {
- imports.add('ListReferenceSetValue');
-
- return 'ListReferenceSetValue';
- } else {
- imports.add('ReferenceSetValue');
-
- return 'ReferenceSetValue';
- }
- }
-
- imports.add('ObjectItem');
-
- return 'ObjectItem';
-}
-
-function mapSelectionToMendixType(property: Property, imports: Set): string {
- if (!property.selectionTypes || property.selectionTypes.length === 0) {
- imports.add('SelectionSingleValue');
-
- return 'SelectionSingleValue';
- }
-
- const selectionType = property.selectionTypes[0];
-
- if (selectionType === 'Multi') {
- imports.add('SelectionMultiValue');
-
- return 'SelectionMultiValue';
- } else {
- imports.add('SelectionSingleValue');
-
- return 'SelectionSingleValue';
- }
-}
-
-function getAttributeBaseType(attributeTypes: AttributeType[]): string {
- if (attributeTypes.length === 0) return 'any';
-
- const types = attributeTypes.map(type => {
- switch (type) {
- case 'String':
- case 'HashString':
- case 'Enum':
- return 'string';
- case 'Boolean':
- return 'boolean';
- case 'Integer':
- case 'Long':
- case 'AutoNumber':
- case 'Currency':
- case 'Decimal':
- return 'Big';
- case 'Float':
- return 'number';
- case 'DateTime':
- return 'Date';
- case 'Binary':
- return 'string';
- default:
- return 'any';
- }
- });
-
- const uniqueTypes = Array.from(new Set(types));
- return uniqueTypes.length === 1 ? uniqueTypes[0] : uniqueTypes.join(' | ');
-}
-
-function mapReturnTypeToTS(returnType: string): string {
- switch (returnType) {
- case 'Void':
- return 'void';
- case 'Boolean':
- return 'boolean';
- case 'Integer':
- case 'Long':
- case 'AutoNumber':
- case 'Decimal':
- return 'Big';
- case 'Float':
- return 'number';
- case 'DateTime':
- return 'Date';
- case 'String':
- return 'string';
- case 'Object':
- return 'object';
- default:
- return 'any';
- }
-}
-
-function generateObjectInterface(property: Property): string {
- return `${property.key}Type`;
-}
-
-export function generateMendixImports(imports: string[]): string {
- if (imports.length === 0) return '';
-
- const reactImports = imports.filter(imp => imp === 'ReactNode');
- const bigJsImports = imports.filter(imp => imp === 'Big');
- const mendixImports = imports.filter(imp => imp !== 'ReactNode' && imp !== 'Big');
-
- let output = '';
-
- if (reactImports.length > 0) {
- output += `import { ${reactImports.join(', ')} } from 'react';\n`;
- }
-
- if (mendixImports.length > 0) {
- output += `import { ${mendixImports.join(', ')} } from 'mendix';\n`;
- }
-
- if (bigJsImports.length > 0) {
- output += `import { ${bigJsImports.join(', ')} } from 'big.js';\n`;
- }
-
- return output;
-}
+import type { AttributeType, Property } from "./types";
+
+export interface MendixTypeMapping {
+ type: string;
+ imports: Set;
+}
+
+export type GenerateTargetPlatform = "web" | "native";
+
+export function getMendixImports(
+ properties: Property[],
+ target: GenerateTargetPlatform,
+): string[] {
+ const imports = new Set();
+
+ for (const property of properties) {
+ const mapping = mapPropertyToMendixType(property, target);
+
+ mapping.imports.forEach((imp) => {
+ imports.add(imp);
+ });
+ }
+
+ return Array.from(imports).sort();
+}
+
+export function mapPropertyToMendixType(
+ property: Property,
+ platform: GenerateTargetPlatform = "web",
+): MendixTypeMapping {
+ const imports = new Set();
+ let type: string;
+
+ switch (property.type) {
+ case "string":
+ case "translatableString":
+ type = "string";
+ break;
+
+ case "boolean":
+ type = "boolean";
+ break;
+
+ case "integer":
+ type = "number";
+ break;
+
+ case "decimal":
+ imports.add("Big");
+ type = "Big";
+ break;
+
+ case "textTemplate":
+ imports.add("DynamicValue");
+ if (property.dataSource) {
+ imports.add("ListExpressionValue");
+ type = "ListExpressionValue";
+ } else {
+ type = "DynamicValue";
+ }
+ break;
+
+ case "action":
+ if (property.dataSource) {
+ imports.add("ListActionValue");
+ type = "ListActionValue";
+ } else {
+ imports.add("ActionValue");
+ type = "ActionValue";
+ }
+ break;
+
+ case "microflow":
+ case "nanoflow":
+ imports.add("ActionValue");
+ type = "ActionValue";
+ break;
+
+ case "attribute":
+ type = mapAttributeToMendixType(property, imports);
+ break;
+
+ case "expression":
+ type = mapExpressionToMendixType(property, imports);
+ break;
+
+ case "datasource":
+ imports.add("ListValue");
+ type = "ListValue";
+ break;
+
+ case "icon":
+ imports.add("DynamicValue");
+ if (platform === "native") {
+ imports.add("NativeIcon");
+ type = "DynamicValue";
+ } else if (platform === "web") {
+ imports.add("WebIcon");
+ type = "DynamicValue";
+ } else {
+ imports.add("WebIcon");
+ imports.add("NativeIcon");
+ type = "DynamicValue";
+ }
+ break;
+
+ case "image":
+ imports.add("DynamicValue");
+ if (platform === "native") {
+ imports.add("NativeImage");
+ type = "DynamicValue";
+ } else if (platform === "web") {
+ imports.add("WebImage");
+ type = "DynamicValue";
+ } else {
+ imports.add("WebImage");
+ imports.add("NativeImage");
+ type = "DynamicValue";
+ }
+ break;
+
+ case "file":
+ imports.add("DynamicValue");
+ imports.add("FileValue");
+ type = "DynamicValue";
+ break;
+
+ case "widgets":
+ if (property.dataSource) {
+ imports.add("ListWidgetValue");
+ type = "ListWidgetValue";
+ } else {
+ imports.add("ReactNode");
+ type = "ReactNode";
+ }
+ break;
+
+ case "object":
+ if (property.properties && property.properties.length > 0) {
+ type = generateObjectInterface(property);
+ } else {
+ type = "object";
+ }
+ break;
+
+ case "entity":
+ imports.add("ObjectItem");
+ type = "ObjectItem";
+ break;
+
+ case "entityConstraint":
+ type = "string";
+ break;
+
+ case "enumeration":
+ if (property.enumerationValues && property.enumerationValues.length > 0) {
+ type = property.enumerationValues
+ .map((ev) => `"${ev.key}"`)
+ .join(" | ");
+ } else {
+ type = "string";
+ }
+ break;
+
+ case "association":
+ type = mapAssociationToMendixType(property, imports);
+ break;
+
+ case "selection":
+ type = mapSelectionToMendixType(property, imports);
+ break;
+
+ case "form":
+ type = "string";
+ break;
+
+ default:
+ type = "any";
+ }
+
+ if (property.isList && !["datasource", "widgets"].includes(property.type)) {
+ type = `${type}[]`;
+ }
+
+ return {
+ type,
+ imports,
+ };
+}
+
+function mapAttributeToMendixType(
+ property: Property,
+ imports: Set,
+): string {
+ const baseType = getAttributeBaseType(property.attributeTypes || []);
+
+ if (baseType.includes("Big")) {
+ imports.add("Big");
+ }
+
+ if (property.dataSource) {
+ imports.add("ListAttributeValue");
+
+ return `ListAttributeValue<${baseType}>`;
+ } else {
+ imports.add("EditableValue");
+
+ return `EditableValue<${baseType}>`;
+ }
+}
+
+function mapExpressionToMendixType(
+ property: Property,
+ imports: Set,
+): string {
+ const baseType = property.returnType
+ ? mapReturnTypeToTS(property.returnType.type)
+ : "string";
+
+ if (baseType.includes("Big")) {
+ imports.add("Big");
+ }
+
+ if (property.dataSource) {
+ imports.add("ListExpressionValue");
+
+ const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType;
+
+ return `ListExpressionValue<${typeStr}>`;
+ } else {
+ imports.add("DynamicValue");
+
+ const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType;
+
+ return `DynamicValue<${typeStr}>`;
+ }
+}
+
+function mapAssociationToMendixType(
+ property: Property,
+ imports: Set,
+): string {
+ if (!property.associationTypes || property.associationTypes.length === 0) {
+ imports.add("ObjectItem");
+
+ return "ObjectItem";
+ }
+
+ const assocType = property.associationTypes[0];
+
+ if (assocType === "Reference") {
+ if (property.dataSource) {
+ imports.add("ListReferenceValue");
+
+ return "ListReferenceValue";
+ } else {
+ imports.add("ReferenceValue");
+
+ return "ReferenceValue";
+ }
+ } else if (assocType === "ReferenceSet") {
+ if (property.dataSource) {
+ imports.add("ListReferenceSetValue");
+
+ return "ListReferenceSetValue";
+ } else {
+ imports.add("ReferenceSetValue");
+
+ return "ReferenceSetValue";
+ }
+ }
+
+ imports.add("ObjectItem");
+
+ return "ObjectItem";
+}
+
+function mapSelectionToMendixType(
+ property: Property,
+ imports: Set,
+): string {
+ if (!property.selectionTypes || property.selectionTypes.length === 0) {
+ imports.add("SelectionSingleValue");
+
+ return "SelectionSingleValue";
+ }
+
+ const selectionType = property.selectionTypes[0];
+
+ if (selectionType === "Multi") {
+ imports.add("SelectionMultiValue");
+
+ return "SelectionMultiValue";
+ } else {
+ imports.add("SelectionSingleValue");
+
+ return "SelectionSingleValue";
+ }
+}
+
+function getAttributeBaseType(attributeTypes: AttributeType[]): string {
+ if (attributeTypes.length === 0) return "any";
+
+ const types = attributeTypes.map((type) => {
+ switch (type) {
+ case "String":
+ case "HashString":
+ case "Enum":
+ return "string";
+ case "Boolean":
+ return "boolean";
+ case "Integer":
+ case "Long":
+ case "AutoNumber":
+ case "Currency":
+ case "Decimal":
+ return "Big";
+ case "Float":
+ return "number";
+ case "DateTime":
+ return "Date";
+ case "Binary":
+ return "string";
+ default:
+ return "any";
+ }
+ });
+
+ const uniqueTypes = Array.from(new Set(types));
+ return uniqueTypes.length === 1 ? uniqueTypes[0] : uniqueTypes.join(" | ");
+}
+
+function mapReturnTypeToTS(returnType: string): string {
+ switch (returnType) {
+ case "Void":
+ return "void";
+ case "Boolean":
+ return "boolean";
+ case "Integer":
+ case "Long":
+ case "AutoNumber":
+ case "Decimal":
+ return "Big";
+ case "Float":
+ return "number";
+ case "DateTime":
+ return "Date";
+ case "String":
+ return "string";
+ case "Object":
+ return "object";
+ default:
+ return "any";
+ }
+}
+
+function generateObjectInterface(property: Property): string {
+ return `${property.key}Type`;
+}
+
+export function generateMendixImports(imports: string[]): string {
+ if (imports.length === 0) return "";
+
+ const reactImports = imports.filter((imp) => imp === "ReactNode");
+ const bigJsImports = imports.filter((imp) => imp === "Big");
+ const mendixImports = imports.filter(
+ (imp) => imp !== "ReactNode" && imp !== "Big",
+ );
+
+ let output = "";
+
+ if (reactImports.length > 0) {
+ output += `import { ${reactImports.join(", ")} } from 'react';\n`;
+ }
+
+ if (mendixImports.length > 0) {
+ output += `import { ${mendixImports.join(", ")} } from 'mendix';\n`;
+ }
+
+ if (bigJsImports.length > 0) {
+ output += `import { ${bigJsImports.join(", ")} } from 'big.js';\n`;
+ }
+
+ return output;
+}
diff --git a/src/type-generator/parser.ts b/src/type-generator/parser.ts
index 3b8bc77..5e2f2e7 100644
--- a/src/type-generator/parser.ts
+++ b/src/type-generator/parser.ts
@@ -1,194 +1,205 @@
-import { XMLParser } from 'fast-xml-parser';
-import type {
- WidgetDefinition,
- Property,
- PropertyGroup,
- SystemProperty,
- AttributeType,
- AssociationType,
- SelectionType,
- EnumerationValue,
- ParsedXMLWidget,
- ParsedXMLProperty,
- ParsedXMLPropertyGroup,
- ParsedXMLSystemProperty,
- ParsedXMLAttributeType,
- ParsedXMLAssociationType,
- ParsedXMLSelectionType,
- ParsedXMLEnumerationValue,
-} from './types';
-import { ensureArray } from './utils';
-
-const parserOptions = {
- ignoreAttributes: false,
- attributeNamePrefix: '',
- textNodeName: '_',
- parseAttributeValue: false,
- trimValues: true,
- parseTrueNumberOnly: false,
- parseTagValue: false,
- allowBooleanAttributes: true,
-};
-
-export function parseWidgetXML(xmlContent: string): WidgetDefinition {
- const parser = new XMLParser(parserOptions);
- const parsedXML = parser.parse(xmlContent) as ParsedXMLWidget;
-
- if (!parsedXML.widget) {
- throw new Error('Invalid widget XML: missing widget element');
- }
-
- const widget = parsedXML.widget;
-
- const widgetDef: WidgetDefinition = {
- id: widget.id,
- name: widget.name,
- description: widget.description,
- needsEntityContext: widget.needsEntityContext === 'true',
- pluginWidget: widget.pluginWidget === 'true',
- offlineCapable: widget.offlineCapable === 'true',
- supportedPlatform: (widget.supportedPlatform as 'All' | 'Native' | 'Web') || 'Web',
- properties: [],
- };
-
- if (widget.properties) {
- widgetDef.properties = parseProperties(widget.properties);
- }
-
- return widgetDef;
-}
-
-function parseProperties(props: any): PropertyGroup[] | Property[] {
- if (props.propertyGroup) {
- const groups = ensureArray(props.propertyGroup);
-
- return groups.map(group => parsePropertyGroup(group));
- }
-
- const properties: Property[] = [];
-
- if (props.property) {
- const propsArray = ensureArray(props.property);
-
- for (const prop of propsArray) {
- properties.push(parseProperty(prop));
- }
- }
-
- return properties;
-}
-
-function parsePropertyGroup(group: ParsedXMLPropertyGroup): PropertyGroup {
- const properties: (Property | SystemProperty)[] = [];
-
- if (group.property) {
- const props = ensureArray(group.property);
- for (const prop of props) {
- properties.push(parseProperty(prop));
- }
- }
-
- if (group.systemProperty) {
- const sysProps = ensureArray(group.systemProperty);
- for (const sysProp of sysProps) {
- properties.push(parseSystemProperty(sysProp));
- }
- }
-
- return {
- caption: group.caption,
- properties,
- };
-}
-
-function parseProperty(prop: ParsedXMLProperty): Property {
- const property: Property = {
- key: prop.key,
- type: prop.type,
- caption: prop.caption || '',
- description: prop.description || '',
- required: prop.required !== 'false',
- isList: prop.isList === 'true',
- };
-
- if (prop.defaultValue !== undefined) {
- property.defaultValue = prop.defaultValue;
- }
-
- if (prop.onChange) {
- property.onChange = prop.onChange;
- }
-
- if (prop.dataSource) {
- property.dataSource = prop.dataSource;
- }
-
- if (prop.attributeTypes) {
- property.attributeTypes = parseAttributeTypes(prop.attributeTypes);
- }
-
- if (prop.associationTypes) {
- property.associationTypes = parseAssociationTypes(prop.associationTypes);
- }
-
- if (prop.selectionTypes) {
- property.selectionTypes = parseSelectionTypes(prop.selectionTypes);
- }
-
- if (prop.enumerationValues) {
- property.enumerationValues = parseEnumerationValues(prop.enumerationValues);
- }
-
- if (prop.properties) {
- const parsedProps = parseProperties(prop.properties);
- property.properties = parsedProps.filter(p => !('caption' in p && 'properties' in p)) as Property[];
- }
-
- if (prop.returnType) {
- property.returnType = {
- type: prop.returnType.type as any,
- isList: prop.returnType.isList === 'true',
- };
- }
-
- return property;
-}
-
-function parseSystemProperty(sysProp: ParsedXMLSystemProperty): SystemProperty {
- const systemProperty: SystemProperty = {
- key: sysProp.key,
- };
-
- if (sysProp.category) {
- systemProperty.category = sysProp.category;
- }
-
- return systemProperty;
-}
-
-function parseAttributeTypes(attributeTypes: { attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[] }): AttributeType[] {
- const types = ensureArray(attributeTypes.attributeType);
-
- return types.map(type => type.name);
-}
-
-function parseAssociationTypes(associationTypes: { associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[] }): AssociationType[] {
- const types = ensureArray(associationTypes.associationType);
-
- return types.map(type => type.name);
-}
-
-function parseSelectionTypes(selectionTypes: { selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[] }): SelectionType[] {
- const types = ensureArray(selectionTypes.selectionType);
-
- return types.map(type => type.name);
-}
-
-function parseEnumerationValues(enumerationValues: { enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[] }): EnumerationValue[] {
- const values = ensureArray(enumerationValues.enumerationValue);
-
- return values.map(value => ({
- key: value.key,
- value: value._ || value.key,
- }));
-}
\ No newline at end of file
+import { XMLParser } from "fast-xml-parser";
+import type {
+ AssociationType,
+ AttributeType,
+ EnumerationValue,
+ ParsedXMLAssociationType,
+ ParsedXMLAttributeType,
+ ParsedXMLEnumerationValue,
+ ParsedXMLProperty,
+ ParsedXMLPropertyGroup,
+ ParsedXMLSelectionType,
+ ParsedXMLSystemProperty,
+ ParsedXMLWidget,
+ Property,
+ PropertyGroup,
+ SelectionType,
+ SystemProperty,
+ WidgetDefinition,
+} from "./types";
+import { ensureArray } from "./utils";
+
+const parserOptions = {
+ ignoreAttributes: false,
+ attributeNamePrefix: "",
+ textNodeName: "_",
+ parseAttributeValue: false,
+ trimValues: true,
+ parseTrueNumberOnly: false,
+ parseTagValue: false,
+ allowBooleanAttributes: true,
+};
+
+export function parseWidgetXML(xmlContent: string): WidgetDefinition {
+ const parser = new XMLParser(parserOptions);
+ const parsedXML = parser.parse(xmlContent) as ParsedXMLWidget;
+
+ if (!parsedXML.widget) {
+ throw new Error("Invalid widget XML: missing widget element");
+ }
+
+ const widget = parsedXML.widget;
+
+ const widgetDef: WidgetDefinition = {
+ id: widget.id,
+ name: widget.name,
+ description: widget.description,
+ needsEntityContext: widget.needsEntityContext === "true",
+ pluginWidget: widget.pluginWidget === "true",
+ offlineCapable: widget.offlineCapable === "true",
+ supportedPlatform:
+ (widget.supportedPlatform as "All" | "Native" | "Web") || "Web",
+ properties: [],
+ };
+
+ if (widget.properties) {
+ widgetDef.properties = parseProperties(widget.properties);
+ }
+
+ return widgetDef;
+}
+
+function parseProperties(props): PropertyGroup[] | Property[] {
+ if (props.propertyGroup) {
+ const groups = ensureArray(props.propertyGroup);
+
+ return groups.map((group) => parsePropertyGroup(group));
+ }
+
+ const properties: Property[] = [];
+
+ if (props.property) {
+ const propsArray = ensureArray(props.property);
+
+ for (const prop of propsArray) {
+ properties.push(parseProperty(prop));
+ }
+ }
+
+ return properties;
+}
+
+function parsePropertyGroup(group: ParsedXMLPropertyGroup): PropertyGroup {
+ const properties: (Property | SystemProperty)[] = [];
+
+ if (group.property) {
+ const props = ensureArray(group.property);
+ for (const prop of props) {
+ properties.push(parseProperty(prop));
+ }
+ }
+
+ if (group.systemProperty) {
+ const sysProps = ensureArray(group.systemProperty);
+ for (const sysProp of sysProps) {
+ properties.push(parseSystemProperty(sysProp));
+ }
+ }
+
+ return {
+ caption: group.caption,
+ properties,
+ };
+}
+
+function parseProperty(prop: ParsedXMLProperty): Property {
+ const property: Property = {
+ key: prop.key,
+ type: prop.type,
+ caption: prop.caption || "",
+ description: prop.description || "",
+ required: prop.required !== "false",
+ isList: prop.isList === "true",
+ };
+
+ if (prop.defaultValue !== undefined) {
+ property.defaultValue = prop.defaultValue;
+ }
+
+ if (prop.onChange) {
+ property.onChange = prop.onChange;
+ }
+
+ if (prop.dataSource) {
+ property.dataSource = prop.dataSource;
+ }
+
+ if (prop.attributeTypes) {
+ property.attributeTypes = parseAttributeTypes(prop.attributeTypes);
+ }
+
+ if (prop.associationTypes) {
+ property.associationTypes = parseAssociationTypes(prop.associationTypes);
+ }
+
+ if (prop.selectionTypes) {
+ property.selectionTypes = parseSelectionTypes(prop.selectionTypes);
+ }
+
+ if (prop.enumerationValues) {
+ property.enumerationValues = parseEnumerationValues(prop.enumerationValues);
+ }
+
+ if (prop.properties) {
+ const parsedProps = parseProperties(prop.properties);
+ property.properties = parsedProps.filter(
+ (p) => !("caption" in p && "properties" in p),
+ ) as Property[];
+ }
+
+ if (prop.returnType) {
+ property.returnType = {
+ type: prop.returnType.type as "String",
+ isList: prop.returnType.isList === "true",
+ };
+ }
+
+ return property;
+}
+
+function parseSystemProperty(sysProp: ParsedXMLSystemProperty): SystemProperty {
+ const systemProperty: SystemProperty = {
+ key: sysProp.key,
+ };
+
+ if (sysProp.category) {
+ systemProperty.category = sysProp.category;
+ }
+
+ return systemProperty;
+}
+
+function parseAttributeTypes(attributeTypes: {
+ attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[];
+}): AttributeType[] {
+ const types = ensureArray(attributeTypes.attributeType);
+
+ return types.map((type) => type.name);
+}
+
+function parseAssociationTypes(associationTypes: {
+ associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[];
+}): AssociationType[] {
+ const types = ensureArray(associationTypes.associationType);
+
+ return types.map((type) => type.name);
+}
+
+function parseSelectionTypes(selectionTypes: {
+ selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[];
+}): SelectionType[] {
+ const types = ensureArray(selectionTypes.selectionType);
+
+ return types.map((type) => type.name);
+}
+
+function parseEnumerationValues(enumerationValues: {
+ enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[];
+}): EnumerationValue[] {
+ const values = ensureArray(enumerationValues.enumerationValue);
+
+ return values.map((value) => ({
+ key: value.key,
+ value: value._ || value.key,
+ }));
+}
diff --git a/src/type-generator/preview-types.ts b/src/type-generator/preview-types.ts
index 9d61759..593b39f 100644
--- a/src/type-generator/preview-types.ts
+++ b/src/type-generator/preview-types.ts
@@ -1,221 +1,215 @@
-import type {
- WidgetDefinition,
- Property,
- PropertyGroup,
- SystemProperty
-} from './types';
-import {
- pascalCase,
- sanitizePropertyKey,
- formatDescription
-} from './utils';
-import {
- extractSystemProperties,
- hasLabelProperty,
- generatePreviewSystemProps
-} from './system-props';
-
-export function generatePreviewTypeDefinition(
- widget: WidgetDefinition,
-): string {
- const interfaceName = `${pascalCase(widget.name)}PreviewProps`;
- const properties = extractAllProperties(widget.properties);
- const systemProps = extractSystemProperties(widget.properties);
- const hasLabel = hasLabelProperty(systemProps);
- const widgetProperties = properties.filter(p => !isSystemProperty(p)) as Property[];
-
- let output = '';
-
- output += generatePreviewImports();
- output += generatePreviewJSDoc(widget);
- output += `export interface ${interfaceName} {\n`;
- output += ' /**\n';
- output += ' * Whether the widget is in read-only mode\n';
- output += ' */\n';
- output += ' readOnly: boolean;\n';
- output += ' /**\n';
- output += ' * The render mode of the widget preview\n';
- output += ' */\n';
- output += ' renderMode?: "design" | "xray" | "structure";\n';
-
- const systemPropsLines = generatePreviewSystemProps(hasLabel);
-
- for (const line of systemPropsLines) {
- output += ' ' + line + '\n';
- }
-
- for (const property of widgetProperties) {
- output += generatePreviewPropertyDefinition(property);
- }
-
- output += '}\n';
-
- return output;
-}
-
-function generatePreviewImports(): string {
- const imports: string[] = [];
-
- imports.push('CSSProperties');
- imports.push('PreviewValue');
-
- let output = '';
-
- if (imports.length > 0) {
- output += `import type { ${imports.join(', ')} } from 'react';\n\n`;
- }
-
- return output;
-}
-
-function generatePreviewJSDoc(widget: WidgetDefinition): string {
- let jsDoc = '/**\n';
- jsDoc += ` * Preview props for ${widget.name}\n`;
-
- if (widget.description) {
- jsDoc += ` * ${formatDescription(widget.description)}\n`;
- }
-
- jsDoc += ' * @preview This interface is used in design mode\n';
- jsDoc += ' */\n';
- return jsDoc;
-}
-
-function generatePreviewPropertyDefinition(
- property: Property,
-): string {
- const indent = ' ';
- let output = '';
-
- if (property.description) {
- output += `${indent}/**\n`;
- output += `${indent} * ${formatDescription(property.description)}\n`;
-
- if (property.caption && property.caption !== property.description) {
- output += `${indent} * @caption ${property.caption}\n`;
- }
-
- output += `${indent} */\n`;
- }
-
- const propertyKey = sanitizePropertyKey(property.key);
- const optional = property.required === false ? '?' : '';
- const propertyType = mapPropertyToPreviewType(property);
-
- output += `${indent}${propertyKey}${optional}: ${propertyType};\n`;
-
- return output;
-}
-
-function mapPropertyToPreviewType(
- property: Property,
-): string {
- const { type, isList, enumerationValues } = property;
-
- let baseType: string;
-
- switch (type) {
- case 'string':
- case 'translatableString':
- baseType = 'string';
- break;
-
- case 'boolean':
- baseType = 'boolean';
- break;
-
- case 'integer':
- case 'decimal':
- baseType = 'number';
- break;
-
- case 'action':
- case 'microflow':
- case 'nanoflow':
- baseType = '{} | null';
- break;
-
- case 'attribute':
- case 'expression':
- case 'entityConstraint':
- baseType = 'string';
- break;
-
- case 'textTemplate':
- baseType = 'string';
- break;
-
- case 'datasource':
- baseType = '{ type: string } | { caption: string } | {}';
- break;
-
- case 'icon':
- case 'image':
- case 'file':
- baseType = '{ uri: string } | null';
- break;
-
- case 'widgets':
- baseType = 'PreviewValue | null';
- break;
-
- case 'enumeration':
- if (enumerationValues && enumerationValues.length > 0) {
- baseType = enumerationValues.map(ev => `"${ev.key}"`).join(' | ');
- } else {
- baseType = 'string';
- }
- break;
-
- case 'object':
- if (property.properties && property.properties.length > 0) {
- baseType = `${pascalCase(property.key)}PreviewType`;
- } else {
- baseType = 'object';
- }
- break;
-
- case 'entity':
- case 'association':
- case 'selection':
- baseType = 'string';
- break;
-
- case 'form':
- baseType = 'string';
- break;
-
- default:
- baseType = 'any';
- }
-
- return isList && type !== 'datasource' ? `${baseType}[]` : baseType;
-}
-
-function extractAllProperties(
- properties: PropertyGroup[] | Property[]
-): (Property | SystemProperty)[] {
- const result: (Property | SystemProperty)[] = [];
-
- for (const item of properties) {
- if (isPropertyGroup(item)) {
- result.push(...item.properties);
- } else {
- result.push(item);
- }
- }
-
- return result;
-}
-
-function isPropertyGroup(
- item: PropertyGroup | Property | SystemProperty
-): item is PropertyGroup {
- return 'caption' in item && 'properties' in item;
-}
-
-function isSystemProperty(
- item: Property | SystemProperty
-): item is SystemProperty {
- return !('type' in item) && 'key' in item;
-}
\ No newline at end of file
+import {
+ extractSystemProperties,
+ generatePreviewSystemProps,
+ hasLabelProperty,
+} from "./system-props";
+import type {
+ Property,
+ PropertyGroup,
+ SystemProperty,
+ WidgetDefinition,
+} from "./types";
+import { formatDescription, pascalCase, sanitizePropertyKey } from "./utils";
+
+export function generatePreviewTypeDefinition(
+ widget: WidgetDefinition,
+): string {
+ const interfaceName = `${pascalCase(widget.name)}PreviewProps`;
+ const properties = extractAllProperties(widget.properties);
+ const systemProps = extractSystemProperties(widget.properties);
+ const hasLabel = hasLabelProperty(systemProps);
+ const widgetProperties = properties.filter(
+ (p) => !isSystemProperty(p),
+ ) as Property[];
+
+ let output = "";
+
+ output += generatePreviewImports();
+ output += generatePreviewJSDoc(widget);
+ output += `export interface ${interfaceName} {\n`;
+ output += " /**\n";
+ output += " * Whether the widget is in read-only mode\n";
+ output += " */\n";
+ output += " readOnly: boolean;\n";
+ output += " /**\n";
+ output += " * The render mode of the widget preview\n";
+ output += " */\n";
+ output += ' renderMode?: "design" | "xray" | "structure";\n';
+
+ const systemPropsLines = generatePreviewSystemProps(hasLabel);
+
+ for (const line of systemPropsLines) {
+ output += ` ${line}\n`;
+ }
+
+ for (const property of widgetProperties) {
+ output += generatePreviewPropertyDefinition(property);
+ }
+
+ output += "}\n";
+
+ return output;
+}
+
+function generatePreviewImports(): string {
+ const imports: string[] = [];
+
+ imports.push("CSSProperties");
+ imports.push("PreviewValue");
+
+ let output = "";
+
+ if (imports.length > 0) {
+ output += `import type { ${imports.join(", ")} } from 'react';\n\n`;
+ }
+
+ return output;
+}
+
+function generatePreviewJSDoc(widget: WidgetDefinition): string {
+ let jsDoc = "/**\n";
+ jsDoc += ` * Preview props for ${widget.name}\n`;
+
+ if (widget.description) {
+ jsDoc += ` * ${formatDescription(widget.description)}\n`;
+ }
+
+ jsDoc += " * @preview This interface is used in design mode\n";
+ jsDoc += " */\n";
+ return jsDoc;
+}
+
+function generatePreviewPropertyDefinition(property: Property): string {
+ const indent = " ";
+ let output = "";
+
+ if (property.description) {
+ output += `${indent}/**\n`;
+ output += `${indent} * ${formatDescription(property.description)}\n`;
+
+ if (property.caption && property.caption !== property.description) {
+ output += `${indent} * @caption ${property.caption}\n`;
+ }
+
+ output += `${indent} */\n`;
+ }
+
+ const propertyKey = sanitizePropertyKey(property.key);
+ const optional = property.required === false ? "?" : "";
+ const propertyType = mapPropertyToPreviewType(property);
+
+ output += `${indent}${propertyKey}${optional}: ${propertyType};\n`;
+
+ return output;
+}
+
+function mapPropertyToPreviewType(property: Property): string {
+ const { type, isList, enumerationValues } = property;
+
+ let baseType: string;
+
+ switch (type) {
+ case "string":
+ case "translatableString":
+ baseType = "string";
+ break;
+
+ case "boolean":
+ baseType = "boolean";
+ break;
+
+ case "integer":
+ case "decimal":
+ baseType = "number";
+ break;
+
+ case "action":
+ case "microflow":
+ case "nanoflow":
+ baseType = "{} | null";
+ break;
+
+ case "attribute":
+ case "expression":
+ case "entityConstraint":
+ baseType = "string";
+ break;
+
+ case "textTemplate":
+ baseType = "string";
+ break;
+
+ case "datasource":
+ baseType = "{ type: string } | { caption: string } | {}";
+ break;
+
+ case "icon":
+ case "image":
+ case "file":
+ baseType = "{ uri: string } | null";
+ break;
+
+ case "widgets":
+ baseType = "PreviewValue | null";
+ break;
+
+ case "enumeration":
+ if (enumerationValues && enumerationValues.length > 0) {
+ baseType = enumerationValues.map((ev) => `"${ev.key}"`).join(" | ");
+ } else {
+ baseType = "string";
+ }
+ break;
+
+ case "object":
+ if (property.properties && property.properties.length > 0) {
+ baseType = `${pascalCase(property.key)}PreviewType`;
+ } else {
+ baseType = "object";
+ }
+ break;
+
+ case "entity":
+ case "association":
+ case "selection":
+ baseType = "string";
+ break;
+
+ case "form":
+ baseType = "string";
+ break;
+
+ default:
+ baseType = "any";
+ }
+
+ return isList && type !== "datasource" ? `${baseType}[]` : baseType;
+}
+
+function extractAllProperties(
+ properties: PropertyGroup[] | Property[],
+): (Property | SystemProperty)[] {
+ const result: (Property | SystemProperty)[] = [];
+
+ for (const item of properties) {
+ if (isPropertyGroup(item)) {
+ result.push(...item.properties);
+ } else {
+ result.push(item);
+ }
+ }
+
+ return result;
+}
+
+function isPropertyGroup(
+ item: PropertyGroup | Property | SystemProperty,
+): item is PropertyGroup {
+ return "caption" in item && "properties" in item;
+}
+
+function isSystemProperty(
+ item: Property | SystemProperty,
+): item is SystemProperty {
+ return !("type" in item) && "key" in item;
+}
diff --git a/src/type-generator/system-props.ts b/src/type-generator/system-props.ts
index 8b20b02..0c15689 100644
--- a/src/type-generator/system-props.ts
+++ b/src/type-generator/system-props.ts
@@ -1,87 +1,93 @@
-import { GenerateTargetPlatform } from './mendix-types';
-import type { SystemProperty, Property, PropertyGroup } from './types';
-
-export interface SystemPropsConfig {
- hasLabel?: boolean;
- platform?: GenerateTargetPlatform;
-}
-
-export function extractSystemProperties(
- properties: PropertyGroup[] | Property[] | (Property | SystemProperty)[]
-): SystemProperty[] {
- const systemProps: SystemProperty[] = [];
-
- for (const item of properties) {
- if (isPropertyGroup(item)) {
- for (const prop of item.properties) {
- if (isSystemProperty(prop)) {
- systemProps.push(prop);
- }
- }
- } else if (isSystemProperty(item)) {
- systemProps.push(item);
- }
- }
-
- return systemProps;
-}
-
-export function hasLabelProperty(systemProperties: SystemProperty[]): boolean {
- return systemProperties.some(prop => prop.key === 'Label');
-}
-
-export function generateSystemProps(config: SystemPropsConfig = {}): string[] {
- const { hasLabel = false, platform = 'web' } = config;
- const props: string[] = [];
-
- props.push('name?: string;');
-
- if (platform !== 'native') {
- if (!hasLabel) {
- props.push('class?: string;');
- props.push('style?: CSSProperties;');
- }
- props.push('tabIndex?: number;');
- }
-
- if (hasLabel) {
- props.push('id?: string;');
- }
-
- return props;
-}
-
-export function getSystemPropsImports(config: SystemPropsConfig = {}): string[] {
- const { platform = 'web' } = config;
- const imports: string[] = [];
-
- if (platform !== 'native') {
- imports.push('CSSProperties');
- }
-
- return imports;
-}
-
-export function generatePreviewSystemProps(hasLabel: boolean): string[] {
- const props: string[] = [];
-
- if (!hasLabel) {
- props.push('/**');
- props.push(' * @deprecated Use class property instead');
- props.push(' */');
- props.push('className: string;');
- props.push('class: string;');
- props.push('style: string;');
- props.push('styleObject?: CSSProperties;');
- }
-
- return props;
-}
-
-function isPropertyGroup(item: PropertyGroup | Property | SystemProperty): item is PropertyGroup {
- return 'caption' in item && 'properties' in item;
-}
-
-function isSystemProperty(item: Property | SystemProperty): item is SystemProperty {
- return !('type' in item) && 'key' in item;
-}
\ No newline at end of file
+import type { GenerateTargetPlatform } from "./mendix-types";
+import type { Property, PropertyGroup, SystemProperty } from "./types";
+
+export interface SystemPropsConfig {
+ hasLabel?: boolean;
+ platform?: GenerateTargetPlatform;
+}
+
+export function extractSystemProperties(
+ properties: PropertyGroup[] | Property[] | (Property | SystemProperty)[],
+): SystemProperty[] {
+ const systemProps: SystemProperty[] = [];
+
+ for (const item of properties) {
+ if (isPropertyGroup(item)) {
+ for (const prop of item.properties) {
+ if (isSystemProperty(prop)) {
+ systemProps.push(prop);
+ }
+ }
+ } else if (isSystemProperty(item)) {
+ systemProps.push(item);
+ }
+ }
+
+ return systemProps;
+}
+
+export function hasLabelProperty(systemProperties: SystemProperty[]): boolean {
+ return systemProperties.some((prop) => prop.key === "Label");
+}
+
+export function generateSystemProps(config: SystemPropsConfig = {}): string[] {
+ const { hasLabel = false, platform = "web" } = config;
+ const props: string[] = [];
+
+ props.push("name?: string;");
+
+ if (platform !== "native") {
+ if (!hasLabel) {
+ props.push("class?: string;");
+ props.push("style?: CSSProperties;");
+ }
+ props.push("tabIndex?: number;");
+ }
+
+ if (hasLabel) {
+ props.push("id?: string;");
+ }
+
+ return props;
+}
+
+export function getSystemPropsImports(
+ config: SystemPropsConfig = {},
+): string[] {
+ const { platform = "web" } = config;
+ const imports: string[] = [];
+
+ if (platform !== "native") {
+ imports.push("CSSProperties");
+ }
+
+ return imports;
+}
+
+export function generatePreviewSystemProps(hasLabel: boolean): string[] {
+ const props: string[] = [];
+
+ if (!hasLabel) {
+ props.push("/**");
+ props.push(" * @deprecated Use class property instead");
+ props.push(" */");
+ props.push("className: string;");
+ props.push("class: string;");
+ props.push("style: string;");
+ props.push("styleObject?: CSSProperties;");
+ }
+
+ return props;
+}
+
+function isPropertyGroup(
+ item: PropertyGroup | Property | SystemProperty,
+): item is PropertyGroup {
+ return "caption" in item && "properties" in item;
+}
+
+function isSystemProperty(
+ item: Property | SystemProperty,
+): item is SystemProperty {
+ return !("type" in item) && "key" in item;
+}
diff --git a/src/type-generator/types.ts b/src/type-generator/types.ts
index 0398013..a21d07b 100644
--- a/src/type-generator/types.ts
+++ b/src/type-generator/types.ts
@@ -1,174 +1,182 @@
-export interface WidgetDefinition {
- id: string;
- name: string;
- description: string;
- needsEntityContext?: boolean;
- pluginWidget?: boolean;
- offlineCapable?: boolean;
- supportedPlatform?: 'All' | 'Native' | 'Web';
- properties: PropertyGroup[] | Property[];
-}
-
-export interface PropertyGroup {
- caption: string;
- properties: (Property | SystemProperty)[];
-}
-
-export interface Property {
- key: string;
- type: PropertyType;
- caption: string;
- description: string;
- required?: boolean;
- isList?: boolean;
- defaultValue?: string;
- attributeTypes?: AttributeType[];
- associationTypes?: AssociationType[];
- selectionTypes?: SelectionType[];
- enumerationValues?: EnumerationValue[];
- properties?: Property[];
- returnType?: ReturnType;
- onChange?: string;
- dataSource?: string;
-}
-
-export interface SystemProperty {
- key: SystemPropertyKey;
- category?: string;
-}
-
-export type PropertyType =
- | 'action'
- | 'association'
- | 'attribute'
- | 'boolean'
- | 'datasource'
- | 'decimal'
- | 'entity'
- | 'entityConstraint'
- | 'enumeration'
- | 'expression'
- | 'file'
- | 'form'
- | 'icon'
- | 'image'
- | 'integer'
- | 'microflow'
- | 'nanoflow'
- | 'object'
- | 'selection'
- | 'string'
- | 'translatableString'
- | 'textTemplate'
- | 'widgets';
-
-export type AttributeType =
- | 'AutoNumber'
- | 'Binary'
- | 'Boolean'
- | 'Currency'
- | 'DateTime'
- | 'Enum'
- | 'Float'
- | 'HashString'
- | 'Integer'
- | 'Long'
- | 'String'
- | 'Decimal';
-
-export type AssociationType = 'Reference' | 'ReferenceSet';
-
-export type SelectionType = 'None' | 'Single' | 'Multi';
-
-export type SystemPropertyKey =
- | 'Label'
- | 'Name'
- | 'TabIndex'
- | 'Editability'
- | 'Visibility';
-
-export interface EnumerationValue {
- key: string;
- value: string;
-}
-
-export interface ReturnType {
- type: 'Void' | 'Boolean' | 'Integer' | 'Float' | 'DateTime' | 'String' | 'Object' | 'Decimal';
- isList?: boolean;
-}
-
-export interface ParsedXMLWidget {
- widget: {
- id: string;
- pluginWidget?: string;
- needsEntityContext?: string;
- offlineCapable?: string;
- supportedPlatform?: string;
- name: string;
- description: string;
- properties: ParsedXMLProperties;
- };
-}
-
-export interface ParsedXMLProperties {
- property?: ParsedXMLProperty | ParsedXMLProperty[];
- propertyGroup?: ParsedXMLPropertyGroup | ParsedXMLPropertyGroup[];
- systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[];
-}
-
-export interface ParsedXMLPropertyGroup {
- caption: string;
- property?: ParsedXMLProperty | ParsedXMLProperty[];
- systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[];
-}
-
-export interface ParsedXMLProperty {
- key: string;
- type: PropertyType;
- required?: string;
- isList?: string;
- defaultValue?: string;
- onChange?: string;
- dataSource?: string;
- caption: string;
- description: string;
- attributeTypes?: {
- attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[];
- };
- associationTypes?: {
- associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[];
- };
- selectionTypes?: {
- selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[];
- };
- enumerationValues?: {
- enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[];
- };
- properties?: ParsedXMLProperties;
- returnType?: {
- type: string;
- isList?: string;
- };
-}
-
-export interface ParsedXMLSystemProperty {
- key: SystemPropertyKey;
- category?: string;
-}
-
-export interface ParsedXMLAttributeType {
- name: AttributeType;
-}
-
-export interface ParsedXMLAssociationType {
- name: AssociationType;
-}
-
-export interface ParsedXMLSelectionType {
- name: SelectionType;
-}
-
-export interface ParsedXMLEnumerationValue {
- _: string;
- key: string;
-}
\ No newline at end of file
+export interface WidgetDefinition {
+ id: string;
+ name: string;
+ description: string;
+ needsEntityContext?: boolean;
+ pluginWidget?: boolean;
+ offlineCapable?: boolean;
+ supportedPlatform?: "All" | "Native" | "Web";
+ properties: PropertyGroup[] | Property[];
+}
+
+export interface PropertyGroup {
+ caption: string;
+ properties: (Property | SystemProperty)[];
+}
+
+export interface Property {
+ key: string;
+ type: PropertyType;
+ caption: string;
+ description: string;
+ required?: boolean;
+ isList?: boolean;
+ defaultValue?: string;
+ attributeTypes?: AttributeType[];
+ associationTypes?: AssociationType[];
+ selectionTypes?: SelectionType[];
+ enumerationValues?: EnumerationValue[];
+ properties?: Property[];
+ returnType?: ReturnType;
+ onChange?: string;
+ dataSource?: string;
+}
+
+export interface SystemProperty {
+ key: SystemPropertyKey;
+ category?: string;
+}
+
+export type PropertyType =
+ | "action"
+ | "association"
+ | "attribute"
+ | "boolean"
+ | "datasource"
+ | "decimal"
+ | "entity"
+ | "entityConstraint"
+ | "enumeration"
+ | "expression"
+ | "file"
+ | "form"
+ | "icon"
+ | "image"
+ | "integer"
+ | "microflow"
+ | "nanoflow"
+ | "object"
+ | "selection"
+ | "string"
+ | "translatableString"
+ | "textTemplate"
+ | "widgets";
+
+export type AttributeType =
+ | "AutoNumber"
+ | "Binary"
+ | "Boolean"
+ | "Currency"
+ | "DateTime"
+ | "Enum"
+ | "Float"
+ | "HashString"
+ | "Integer"
+ | "Long"
+ | "String"
+ | "Decimal";
+
+export type AssociationType = "Reference" | "ReferenceSet";
+
+export type SelectionType = "None" | "Single" | "Multi";
+
+export type SystemPropertyKey =
+ | "Label"
+ | "Name"
+ | "TabIndex"
+ | "Editability"
+ | "Visibility";
+
+export interface EnumerationValue {
+ key: string;
+ value: string;
+}
+
+export interface ReturnType {
+ type:
+ | "Void"
+ | "Boolean"
+ | "Integer"
+ | "Float"
+ | "DateTime"
+ | "String"
+ | "Object"
+ | "Decimal";
+ isList?: boolean;
+}
+
+export interface ParsedXMLWidget {
+ widget: {
+ id: string;
+ pluginWidget?: string;
+ needsEntityContext?: string;
+ offlineCapable?: string;
+ supportedPlatform?: string;
+ name: string;
+ description: string;
+ properties: ParsedXMLProperties;
+ };
+}
+
+export interface ParsedXMLProperties {
+ property?: ParsedXMLProperty | ParsedXMLProperty[];
+ propertyGroup?: ParsedXMLPropertyGroup | ParsedXMLPropertyGroup[];
+ systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[];
+}
+
+export interface ParsedXMLPropertyGroup {
+ caption: string;
+ property?: ParsedXMLProperty | ParsedXMLProperty[];
+ systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[];
+}
+
+export interface ParsedXMLProperty {
+ key: string;
+ type: PropertyType;
+ required?: string;
+ isList?: string;
+ defaultValue?: string;
+ onChange?: string;
+ dataSource?: string;
+ caption: string;
+ description: string;
+ attributeTypes?: {
+ attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[];
+ };
+ associationTypes?: {
+ associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[];
+ };
+ selectionTypes?: {
+ selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[];
+ };
+ enumerationValues?: {
+ enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[];
+ };
+ properties?: ParsedXMLProperties;
+ returnType?: {
+ type: string;
+ isList?: string;
+ };
+}
+
+export interface ParsedXMLSystemProperty {
+ key: SystemPropertyKey;
+ category?: string;
+}
+
+export interface ParsedXMLAttributeType {
+ name: AttributeType;
+}
+
+export interface ParsedXMLAssociationType {
+ name: AssociationType;
+}
+
+export interface ParsedXMLSelectionType {
+ name: SelectionType;
+}
+
+export interface ParsedXMLEnumerationValue {
+ _: string;
+ key: string;
+}
diff --git a/src/type-generator/utils.ts b/src/type-generator/utils.ts
index bb87078..59d5f6f 100644
--- a/src/type-generator/utils.ts
+++ b/src/type-generator/utils.ts
@@ -1,92 +1,92 @@
-import type {
- AttributeType,
- Property,
-} from './types';
-import type { GenerateTargetPlatform } from './mendix-types';
-import { mapPropertyToMendixType } from './mendix-types';
-
-export function mapPropertyTypeToTS(property: Property, target?: GenerateTargetPlatform): string {
- const mapping = mapPropertyToMendixType(property, target);
-
- return mapping.type;
-}
-
+import type { GenerateTargetPlatform } from "./mendix-types";
+import { mapPropertyToMendixType } from "./mendix-types";
+import type { AttributeType, Property } from "./types";
+
+export function mapPropertyTypeToTS(
+ property: Property,
+ target?: GenerateTargetPlatform,
+): string {
+ const mapping = mapPropertyToMendixType(property, target);
+
+ return mapping.type;
+}
+
export function mapAttributeTypeToTS(attributeType: AttributeType): string {
switch (attributeType) {
- case 'String':
- case 'HashString':
- case 'Enum':
- return 'string';
- case 'Boolean':
- return 'boolean';
- case 'Integer':
- case 'Long':
- case 'AutoNumber':
- case 'Currency':
- case 'Decimal':
- return 'Big';
- case 'Float':
- return 'number';
+ case "String":
+ case "HashString":
+ case "Enum":
+ return "string";
+ case "Boolean":
+ return "boolean";
+ case "Integer":
+ case "Long":
+ case "AutoNumber":
+ case "Currency":
+ case "Decimal":
+ return "Big";
+ case "Float":
+ return "number";
- case 'DateTime':
- return 'Date | string';
+ case "DateTime":
+ return "Date | string";
- case 'Binary':
- return 'Blob | string';
+ case "Binary":
+ return "Blob | string";
default:
- return 'any';
+ return "any";
}
}
-
+
export function mapReturnTypeToTS(returnType: string): string {
switch (returnType) {
- case 'Void':
- return 'void';
- case 'Boolean':
- return 'boolean';
- case 'Integer':
- case 'Long':
- case 'AutoNumber':
- case 'Decimal':
- return 'Big';
- case 'Float':
- return 'number';
- case 'DateTime':
- return 'Date | string';
- case 'String':
- return 'string';
- case 'Object':
- return 'object';
+ case "Void":
+ return "void";
+ case "Boolean":
+ return "boolean";
+ case "Integer":
+ case "Long":
+ case "AutoNumber":
+ case "Decimal":
+ return "Big";
+ case "Float":
+ return "number";
+ case "DateTime":
+ return "Date | string";
+ case "String":
+ return "string";
+ case "Object":
+ return "object";
default:
- return 'any';
+ return "any";
}
}
-
-export function ensureArray(value: T | T[] | undefined): T[] {
- if (!value) return [];
- return Array.isArray(value) ? value : [value];
-}
-
-export function pascalCase(str: string): string {
- return str
- .split(/[-_\s]+/)
- .map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
- .join('');
-}
-
-export function sanitizePropertyKey(key: string): string {
- if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
- return key;
- }
- return `'${key}'`;
-}
-
-export function formatDescription(description: string): string {
- return description
- .trim()
- .split('\n')
- .map(line => line.trim())
- .filter(line => line.length > 0)
- .join(' ');
+
+export function ensureArray(value: T | T[] | undefined): T[] {
+ if (!value) return [];
+ return Array.isArray(value) ? value : [value];
+}
+
+export function pascalCase(str: string): string {
+ return str
+ .split(/[-_\s]+/)
+ .map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
+ .join("");
+}
+
+export function sanitizePropertyKey(key: string): string {
+ if (/^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(key)) {
+ return key;
+ }
+ return `'${key}'`;
+}
+
+export function formatDescription(description: string): string {
+ return description
+ .trim()
+ .split("\n")
+ .map((line) => line.trim())
+ .filter((line) => line.length > 0)
+ .join(" ");
}
diff --git a/src/types.d.ts b/src/types.d.ts
index d634f8c..e8a8653 100644
--- a/src/types.d.ts
+++ b/src/types.d.ts
@@ -1,13 +1,13 @@
-import { PackageJson } from "type-fest";
-
-type WidgetConfig = {
- projectPath: string;
- mendixHost: string;
- developmentPort: number;
-}
-
-export type WidgetPackageJson = PackageJson & {
- widgetName: string;
- config: WidgetConfig;
- packagePath: string;
-};
\ No newline at end of file
+import { PackageJson } from "type-fest";
+
+type WidgetConfig = {
+ projectPath: string;
+ mendixHost: string;
+ developmentPort: number;
+};
+
+export type WidgetPackageJson = PackageJson & {
+ widgetName: string;
+ config: WidgetConfig;
+ packagePath: string;
+};
diff --git a/src/utils/getMendixProjectDirectory.ts b/src/utils/getMendixProjectDirectory.ts
index e0b5d83..3fa3576 100644
--- a/src/utils/getMendixProjectDirectory.ts
+++ b/src/utils/getMendixProjectDirectory.ts
@@ -1,13 +1,13 @@
-import path from "path";
-
-import { PROJECT_DIRECTORY } from "../constants";
-import getWidgetPackageJson from "./getWidgetPackageJson";
-
-const getMendixProjectDiectory = async () => {
- const packageJson = await getWidgetPackageJson();
- const widgetPath = PROJECT_DIRECTORY;
-
- return path.join(widgetPath, packageJson.config.projectPath);
-};
-
-export default getMendixProjectDiectory;
\ No newline at end of file
+import path from "node:path";
+
+import { PROJECT_DIRECTORY } from "../constants";
+import getWidgetPackageJson from "./getWidgetPackageJson";
+
+const getMendixProjectDiectory = async () => {
+ const packageJson = await getWidgetPackageJson();
+ const widgetPath = PROJECT_DIRECTORY;
+
+ return path.join(widgetPath, packageJson.config.projectPath);
+};
+
+export default getMendixProjectDiectory;
diff --git a/src/utils/getMendixWidgetDirectory.ts b/src/utils/getMendixWidgetDirectory.ts
index 2bf26c0..ec8a742 100644
--- a/src/utils/getMendixWidgetDirectory.ts
+++ b/src/utils/getMendixWidgetDirectory.ts
@@ -1,11 +1,11 @@
-import path from "path";
-
-import getMendixProjectDiectory from "./getMendixProjectDirectory";
-
-const getMendixWidgetDirectory = async () => {
- const mendixPath = await getMendixProjectDiectory();
-
- return path.join(mendixPath, 'widgets');
-};
-
-export default getMendixWidgetDirectory;
\ No newline at end of file
+import path from "node:path";
+
+import getMendixProjectDiectory from "./getMendixProjectDirectory";
+
+const getMendixWidgetDirectory = async () => {
+ const mendixPath = await getMendixProjectDiectory();
+
+ return path.join(mendixPath, "widgets");
+};
+
+export default getMendixWidgetDirectory;
diff --git a/src/utils/getViteOutputDirectory.ts b/src/utils/getViteOutputDirectory.ts
index adb2378..ef4c41e 100644
--- a/src/utils/getViteOutputDirectory.ts
+++ b/src/utils/getViteOutputDirectory.ts
@@ -1,16 +1,21 @@
-import path from "path";
-
-import { DIST_DIRECTORY_NAME, PROJECT_DIRECTORY } from "../constants";
-import getWidgetPackageJson from "./getWidgetPackageJson";
-
-const getViteOutputDirectory = async (): Promise => {
- const packageJson = await getWidgetPackageJson();
- const packagePath = packageJson.packagePath;
- const widgetName = packageJson.widgetName;
- const packageDirectories = packagePath.split('.');
- const outputDir = path.join(PROJECT_DIRECTORY, `/${DIST_DIRECTORY_NAME}/tmp/widgets`, ...packageDirectories, widgetName.toLowerCase());
-
- return outputDir;
-};
-
-export default getViteOutputDirectory;
\ No newline at end of file
+import path from "node:path";
+
+import { DIST_DIRECTORY_NAME, PROJECT_DIRECTORY } from "../constants";
+import getWidgetPackageJson from "./getWidgetPackageJson";
+
+const getViteOutputDirectory = async (): Promise => {
+ const packageJson = await getWidgetPackageJson();
+ const packagePath = packageJson.packagePath;
+ const widgetName = packageJson.widgetName;
+ const packageDirectories = packagePath.split(".");
+ const outputDir = path.join(
+ PROJECT_DIRECTORY,
+ `/${DIST_DIRECTORY_NAME}/tmp/widgets`,
+ ...packageDirectories,
+ widgetName.toLowerCase(),
+ );
+
+ return outputDir;
+};
+
+export default getViteOutputDirectory;
diff --git a/src/utils/getViteUserConfiguration.ts b/src/utils/getViteUserConfiguration.ts
index 3483564..07df724 100644
--- a/src/utils/getViteUserConfiguration.ts
+++ b/src/utils/getViteUserConfiguration.ts
@@ -1,8 +1,9 @@
-import { PWTConfig, PWTConfigFn, PWTConfigFnPromise } from "..";
+import type { PWTConfig, PWTConfigFn, PWTConfigFnPromise } from "..";
const getViteUserConfiguration = async (path: string): Promise => {
const getUserConfig = await import(`file://${path}`);
- const getUserConfigFn: PWTConfigFn | PWTConfigFnPromise = getUserConfig.default;
+ const getUserConfigFn: PWTConfigFn | PWTConfigFnPromise =
+ getUserConfig.default;
const userConfig = getUserConfigFn();
if (userConfig instanceof Promise) {
@@ -14,4 +15,4 @@ const getViteUserConfiguration = async (path: string): Promise => {
return userConfig;
};
-export default getViteUserConfiguration;
\ No newline at end of file
+export default getViteUserConfiguration;
diff --git a/src/utils/getViteWatchOutputDirectory.ts b/src/utils/getViteWatchOutputDirectory.ts
index 69c6c6f..03ab31f 100644
--- a/src/utils/getViteWatchOutputDirectory.ts
+++ b/src/utils/getViteWatchOutputDirectory.ts
@@ -1,22 +1,22 @@
-import path from "path";
-
-import { DIST_DIRECTORY_NAME, PROJECT_DIRECTORY } from "../constants";
-import getWidgetPackageJson from "./getWidgetPackageJson";
-
-const getViteWatchOutputDirectory = async (): Promise => {
- const packageJson = await getWidgetPackageJson();
- const packagePath = packageJson.packagePath;
- const widgetName = packageJson.widgetName;
- const packageDirectories = packagePath.split('.');
- const outputDir = path.join(
- PROJECT_DIRECTORY,
- packageJson.config.projectPath,
- 'deployment/web/widgets',
- ...packageDirectories,
- widgetName.toLowerCase()
- );
-
- return outputDir;
-};
-
-export default getViteWatchOutputDirectory;
\ No newline at end of file
+import path from "node:path";
+
+import { PROJECT_DIRECTORY } from "../constants";
+import getWidgetPackageJson from "./getWidgetPackageJson";
+
+const getViteWatchOutputDirectory = async (): Promise => {
+ const packageJson = await getWidgetPackageJson();
+ const packagePath = packageJson.packagePath;
+ const widgetName = packageJson.widgetName;
+ const packageDirectories = packagePath.split(".");
+ const outputDir = path.join(
+ PROJECT_DIRECTORY,
+ packageJson.config.projectPath,
+ "deployment/web/widgets",
+ ...packageDirectories,
+ widgetName.toLowerCase(),
+ );
+
+ return outputDir;
+};
+
+export default getViteWatchOutputDirectory;
diff --git a/src/utils/getWidgetName.ts b/src/utils/getWidgetName.ts
index 757a140..77cae90 100644
--- a/src/utils/getWidgetName.ts
+++ b/src/utils/getWidgetName.ts
@@ -1,13 +1,15 @@
-import getWidgetPackageJson from './getWidgetPackageJson';
-
-const getWidgetName = async (): Promise => {
- const widgetPackageJson = await getWidgetPackageJson();
-
- if (!widgetPackageJson.widgetName) {
- throw new Error('widget name is missing, please check your package.json file.');
- }
-
- return widgetPackageJson.widgetName;
-};
-
-export default getWidgetName;
\ No newline at end of file
+import getWidgetPackageJson from "./getWidgetPackageJson";
+
+const getWidgetName = async (): Promise => {
+ const widgetPackageJson = await getWidgetPackageJson();
+
+ if (!widgetPackageJson.widgetName) {
+ throw new Error(
+ "widget name is missing, please check your package.json file.",
+ );
+ }
+
+ return widgetPackageJson.widgetName;
+};
+
+export default getWidgetName;
diff --git a/src/utils/getWidgetPackageJson.ts b/src/utils/getWidgetPackageJson.ts
index 5034f40..41a7e99 100644
--- a/src/utils/getWidgetPackageJson.ts
+++ b/src/utils/getWidgetPackageJson.ts
@@ -1,19 +1,19 @@
-import fs from 'fs/promises';
-import path from 'path';
-
-import { PROJECT_DIRECTORY } from '../constants';
-import { WidgetPackageJson } from '../types';
-
-const getWidgetPackageJson = async (): Promise => {
- try {
- const packageJsonPath = path.join(PROJECT_DIRECTORY, 'package.json');
- const packageJsonFile = await fs.readFile(packageJsonPath, 'utf-8');
- const packageJsonData: WidgetPackageJson = JSON.parse(packageJsonFile);
-
- return packageJsonData;
- } catch (error) {
- throw new Error('package.json file is not exists.');
- }
-};
-
-export default getWidgetPackageJson;
\ No newline at end of file
+import fs from "node:fs/promises";
+import path from "node:path";
+
+import { PROJECT_DIRECTORY } from "../constants";
+import type { WidgetPackageJson } from "../types";
+
+const getWidgetPackageJson = async (): Promise => {
+ try {
+ const packageJsonPath = path.join(PROJECT_DIRECTORY, "package.json");
+ const packageJsonFile = await fs.readFile(packageJsonPath, "utf-8");
+ const packageJsonData: WidgetPackageJson = JSON.parse(packageJsonFile);
+
+ return packageJsonData;
+ } catch (_error) {
+ throw new Error("package.json file is not exists.");
+ }
+};
+
+export default getWidgetPackageJson;
diff --git a/src/utils/getWidgetVersion.ts b/src/utils/getWidgetVersion.ts
index 6bc2471..d971209 100644
--- a/src/utils/getWidgetVersion.ts
+++ b/src/utils/getWidgetVersion.ts
@@ -1,13 +1,15 @@
-import getWidgetPackageJson from './getWidgetPackageJson';
-
-const getWidgetVersion = async (): Promise => {
- const widgetPackageJson = await getWidgetPackageJson();
-
- if (!widgetPackageJson.version) {
- throw new Error('widget version is missing, please check your package.json file.');
- }
-
- return widgetPackageJson.version;
-};
-
-export default getWidgetVersion;
\ No newline at end of file
+import getWidgetPackageJson from "./getWidgetPackageJson";
+
+const getWidgetVersion = async (): Promise => {
+ const widgetPackageJson = await getWidgetPackageJson();
+
+ if (!widgetPackageJson.version) {
+ throw new Error(
+ "widget version is missing, please check your package.json file.",
+ );
+ }
+
+ return widgetPackageJson.version;
+};
+
+export default getWidgetVersion;
diff --git a/src/utils/pathIsExists.ts b/src/utils/pathIsExists.ts
index 1c26262..8e33753 100644
--- a/src/utils/pathIsExists.ts
+++ b/src/utils/pathIsExists.ts
@@ -1,13 +1,13 @@
-import fs from "fs/promises";
-
-const pathIsExists = async (directoryPath: string): Promise => {
- try {
- await fs.access(directoryPath, fs.constants.F_OK);
-
- return true;
- } catch (error) {
- return false;
- }
-};
-
-export default pathIsExists;
\ No newline at end of file
+import fs from "node:fs/promises";
+
+const pathIsExists = async (directoryPath: string): Promise => {
+ try {
+ await fs.access(directoryPath, fs.constants.F_OK);
+
+ return true;
+ } catch (_error) {
+ return false;
+ }
+};
+
+export default pathIsExists;
diff --git a/src/utils/showMessage.ts b/src/utils/showMessage.ts
index 519f476..85f7d64 100644
--- a/src/utils/showMessage.ts
+++ b/src/utils/showMessage.ts
@@ -1,7 +1,7 @@
-import { COLOR_NAME } from "../constants";
-
-const showMessage = (message: string) => {
- console.log(`${COLOR_NAME('[hyper-pwt]')} ${message}`);
-};
-
-export default showMessage;
\ No newline at end of file
+import { COLOR_NAME } from "../constants";
+
+const showMessage = (message: string) => {
+ console.log(`${COLOR_NAME("[hyper-pwt]")} ${message}`);
+};
+
+export default showMessage;
diff --git a/tools/copy-widget-schema.js b/tools/copy-widget-schema.js
index 9fafc2d..5b25f80 100644
--- a/tools/copy-widget-schema.js
+++ b/tools/copy-widget-schema.js
@@ -1,20 +1,26 @@
-const fs = require('fs');
-const path = require('path');
+const fs = require("node:fs");
+const path = require("node:path");
-const sourcePath = path.join(__dirname, '..', 'node_modules', 'mendix', 'custom_widget.xsd');
-const targetPath = path.join(__dirname, '..', 'custom_widget.xsd');
+const sourcePath = path.join(
+ __dirname,
+ "..",
+ "node_modules",
+ "mendix",
+ "custom_widget.xsd",
+);
+const targetPath = path.join(__dirname, "..", "custom_widget.xsd");
try {
if (fs.existsSync(sourcePath)) {
fs.copyFileSync(sourcePath, targetPath);
-
- console.log('Successfully copied custom_widget.xsd to project root');
+
+ console.log("Successfully copied custom_widget.xsd to project root");
} else {
- console.warn('custom_widget.xsd not found in node_modules/mendix');
- console.warn('Make sure mendix package is installed');
+ console.warn("custom_widget.xsd not found in node_modules/mendix");
+ console.warn("Make sure mendix package is installed");
}
} catch (error) {
- console.error('Failed to copy custom_widget.xsd:', error.message);
+ console.error("Failed to copy custom_widget.xsd:", error.message);
process.exit(1);
-}
\ No newline at end of file
+}
diff --git a/tsconfig.json b/tsconfig.json
index f59ef9e..6a77df9 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -8,5 +8,5 @@
"module": "preserve",
"moduleResolution": "bundler",
"target": "esnext"
- },
-}
\ No newline at end of file
+ }
+}