From 56dd2464135f8274b005cdae9a85df7bf435c7a1 Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Wed, 3 Sep 2025 19:40:55 +0900 Subject: [PATCH 1/2] feat: add biome --- .vscode/settings.json | 5 + benchmark/benchmarkWidget/.eslintrc.js | 2 +- benchmark/benchmarkWidget/prettier.config.js | 8 +- .../src/BenchmarkWidget.editorConfig.ts | 121 +- .../src/BenchmarkWidget.editorPreview.tsx | 14 +- .../benchmarkWidget/src/BenchmarkWidget.tsx | 9 +- .../src/components/CalendarWidget.tsx | 38 +- .../src/ui/BenchmarkWidget.css | 1 - benchmark/benchmarkWidget/tsconfig.json | 5 +- .../typings/BenchmarkWidgetProps.d.ts | 14 +- benchmark/package.json | 2 +- benchmark/src/benchmark.js | 1012 ++++++++++++---- benchmark/src/compare.js | 403 +++++-- biome.json | 41 + package.json | 3 +- pnpm-lock.yaml | 91 ++ rslib.config.ts | 148 +-- src/cli.ts | 84 +- src/commands/build/web/index.ts | 407 +++++-- src/commands/start/web/index.ts | 444 ++++--- .../typescript/tsconfig.base.json | 7 +- src/configurations/vite/index.ts | 424 ++++--- .../plugins/mendix-hotreload-react-plugin.ts | 83 +- .../mendix-patch-vite-client-plugin.ts | 85 +- src/constants/index.ts | 52 +- src/index.ts | 39 +- src/type-generator/generator.ts | 450 ++++--- src/type-generator/header.ts | 108 +- src/type-generator/index.ts | 79 +- src/type-generator/mendix-types.ts | 1039 +++++++++++------ src/type-generator/parser.ts | 582 ++++++--- src/type-generator/preview-types.ts | 582 +++++---- src/type-generator/system-props.ts | 272 +++-- src/type-generator/types.ts | 387 +++--- src/type-generator/utils.ts | 217 ++-- src/types.d.ts | 28 +- src/utils/getMendixProjectDirectory.ts | 34 +- src/utils/getMendixWidgetDirectory.ts | 27 +- src/utils/getViteOutputDirectory.ts | 48 +- src/utils/getViteUserConfiguration.ts | 42 +- src/utils/getViteWatchOutputDirectory.ts | 57 +- src/utils/getWidgetName.ts | 32 +- src/utils/getWidgetPackageJson.ts | 52 +- src/utils/getWidgetVersion.ts | 32 +- src/utils/pathIsExists.ts | 33 +- src/utils/showMessage.ts | 19 +- tools/copy-widget-schema.js | 52 +- tsconfig.json | 16 +- 48 files changed, 5271 insertions(+), 2459 deletions(-) create mode 100644 .vscode/settings.json create mode 100644 biome.json 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..22cdd11 100644 --- a/benchmark/src/benchmark.js +++ b/benchmark/src/benchmark.js @@ -1,364 +1,932 @@ -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'; - -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const rootDir = path.join(__dirname, '..'); -const widgetDir = path.join(rootDir, 'benchmarkWidget'); +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"; + +const __filename = + fileURLToPath( + import.meta.url, + ); +const __dirname = + path.dirname( + __filename, + ); +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'); + constructor( + options = {}, + ) { + this.verbose = + options.verbose || + process.argv.includes( + "--verbose", + ); + this.jsonOutput = + options.json || + process.argv.includes( + "--json", + ); this.results = { - standardBuild: {}, - hyperBuild: {}, - comparison: {} + standardBuild: + {}, + hyperBuild: + {}, + comparison: + {}, }; } - log(message, type = 'info') { - if (this.jsonOutput) return; - + 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}`); + + 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'); - - await fs.remove(distPath); - await fs.remove(tmpPath); - + 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); + const files = + await fs.readdir( + widgetDir, + ); for (const file of files) { - if (file.endsWith('.mpk')) { - await fs.remove(path.join(widgetDir, file)); + if ( + file.endsWith( + ".mpk", + ) + ) { + await fs.remove( + path.join( + widgetDir, + file, + ), + ); } } } - runBuild(command, buildType) { - this.log(`Running ${buildType} build...`); - - const startTime = Date.now(); - const startMemory = process.memoryUsage(); - + 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' - }); - - 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 output = + execSync( + `npm run ${command}`, + { + cwd: widgetDir, + 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", + ); + 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'); - - const analysis = { - distSize: 0, - fileCount: 0, - files: [], - mpkSize: 0 - }; + async analyzeBuildOutput( + buildType, + ) { + const distPath = + path.join( + widgetDir, + "dist", + ); + const distMpkPath = + path.join( + distPath, + "1.0.0", + ); + + const analysis = + { + distSize: 0, + fileCount: 0, + files: [], + mpkSize: 0, + }; // Analyze dist folder - if (await fs.pathExists(distPath)) { - const files = await this.getFilesRecursively(distPath); - analysis.fileCount = files.length; - + 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 - }); - analysis.distSize += stats.size; + const stats = + await fs.stat( + file, + ); + const relPath = + path.relative( + distPath, + file, + ); + analysis.files.push( + { + path: relPath, + size: stats.size, + }, + ); + analysis.distSize += + stats.size; } } // Find and analyze .mpk file - const widgetFiles = await fs.readdir(distMpkPath); - const mpkFile = widgetFiles.find(f => f.endsWith('.mpk')); - + const widgetFiles = + await fs.readdir( + distMpkPath, + ); + const mpkFile = + widgetFiles.find( + (f) => + f.endsWith( + ".mpk", + ), + ); + if (mpkFile) { - const mpkPath = path.join(distMpkPath, mpkFile); - const stats = await fs.stat(mpkPath); - analysis.mpkSize = stats.size; - analysis.mpkFile = mpkFile; + const mpkPath = + path.join( + distMpkPath, + mpkFile, + ); + const stats = + await fs.stat( + mpkPath, + ); + analysis.mpkSize = + stats.size; + analysis.mpkFile = + mpkFile; } return analysis; } - async getFilesRecursively(dir) { - const files = []; - const items = await fs.readdir(dir); - + 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)); + const fullPath = + path.join( + dir, + item, + ); + const stats = + await fs.stat( + fullPath, + ); + + if ( + stats.isDirectory() + ) { + files.push( + ...(await this.getFilesRecursively( + fullPath, + )), + ); } else { - files.push(fullPath); + files.push( + fullPath, + ); } } - + return files; } - async saveBuildArtifacts(buildType) { - const artifactsDir = path.join(rootDir, 'artifacts', buildType); - await fs.ensureDir(artifactsDir); - + async saveBuildArtifacts( + buildType, + ) { + const artifactsDir = + path.join( + rootDir, + "artifacts", + buildType, + ); + await fs.ensureDir( + artifactsDir, + ); + // Copy dist folder - const distPath = path.join(widgetDir, 'dist'); - if (await fs.pathExists(distPath)) { - await fs.copy(distPath, path.join(artifactsDir, 'dist')); + const distPath = + path.join( + widgetDir, + "dist", + ); + if ( + await fs.pathExists( + distPath, + ) + ) { + 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 widgetFiles = + await fs.readdir( + widgetDir, + ); + const mpkFile = + widgetFiles.find( + (f) => + f.endsWith( + ".mpk", + ), + ); + if (mpkFile) { await fs.copy( - path.join(widgetDir, mpkFile), - path.join(artifactsDir, mpkFile) + path.join( + widgetDir, + mpkFile, + ), + path.join( + artifactsDir, + mpkFile, + ), ); } } calculateComparison() { - const std = this.results.standardBuild; - const hyper = this.results.hyperBuild; - - if (!std.metrics || !hyper.metrics) { + 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) + difference: + hyper + .metrics + .buildTime - + std + .metrics + .buildTime, + 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) + difference: + hyper + .metrics + .memoryUsed - + std + .metrics + .memoryUsed, + 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) + difference: + hyper + .analysis + .distSize - + std + .analysis + .distSize, + 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) + difference: + hyper + .analysis + .mpkSize - + std + .analysis + .mpkSize, + 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, + }, }; } displayResults() { - if (this.jsonOutput) { - console.log(JSON.stringify(this.results, null, 2)); + if ( + this + .jsonOutput + ) { + console.log( + JSON.stringify( + this + .results, + null, + 2, + ), + ); 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'], - style: { - head: ['cyan'] - } - }); + const metricsTable = + new Table({ + head: [ + "Metric", + "Standard Build", + "Hyper Build", + "Difference", + ], + style: { + head: [ + "cyan", + ], + }, + }); - const std = this.results.standardBuild; - const hyper = this.results.hyperBuild; - const comp = this.results.comparison; + const std = + this.results + .standardBuild; + const hyper = + this.results + .hyperBuild; + const comp = + this.results + .comparison; - if (std.metrics && hyper.metrics && comp) { + 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', - this.formatBytes(std.analysis.distSize), - this.formatBytes(hyper.analysis.distSize), - this.formatDifference(comp.distSize.difference, comp.distSize.percentage, 'bytes', true) + "Dist Size", + this.formatBytes( + std + .analysis + .distSize, + ), + this.formatBytes( + hyper + .analysis + .distSize, + ), + this.formatDifference( + comp + .distSize + .difference, + comp + .distSize + .percentage, + "bytes", + true, + ), ], [ - 'MPK Size', - this.formatBytes(std.analysis.mpkSize), - this.formatBytes(hyper.analysis.mpkSize), - this.formatDifference(comp.mpkSize.difference, comp.mpkSize.percentage, 'bytes', true) + "MPK Size", + this.formatBytes( + std + .analysis + .mpkSize, + ), + this.formatBytes( + hyper + .analysis + .mpkSize, + ), + this.formatDifference( + comp + .mpkSize + .difference, + comp + .mpkSize + .percentage, + "bytes", + true, + ), ], [ - 'File Count', - std.analysis.fileCount, - hyper.analysis.fileCount, - comp.fileCount.difference > 0 ? `+${comp.fileCount.difference}` : `${comp.fileCount.difference}` - ] + "File Count", + std + .analysis + .fileCount, + hyper + .analysis + .fileCount, + comp + .fileCount + .difference > + 0 + ? `+${comp.fileCount.difference}` + : `${comp.fileCount.difference}`, + ], ); - console.log(metricsTable.toString()); + console.log( + metricsTable.toString(), + ); // 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( + "\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`, + ), + ); } 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`)); - } else if (comp.mpkSize.difference > 0) { - console.log(chalk.yellow(`⚠ Hyper build produces ${comp.mpkSize.percentage}% larger package`)); + + if ( + sizeImproved + ) { + 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`, + ), + ); } else { - console.log(chalk.blue(`• Package size is identical`)); + console.log( + chalk.blue( + `• Package size is identical`, + ), + ); } } } - 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 color = diff > 0 ? chalk.red : chalk.green; - - return color(`${sign}${value} (${sign}${Math.abs(percentage)}%)`); + 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 color = + diff > 0 + ? chalk.red + : chalk.green; + + return color( + `${sign}${value} (${sign}${Math.abs(percentage)}%)`, + ); } - formatBytes(bytes) { - if (bytes < 1024) return `${bytes}B`; - if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)}KB`; + formatBytes( + bytes, + ) { + if ( + bytes < 1024 + ) + return `${bytes}B`; + if ( + bytes < + 1024 * 1024 + ) + return `${(bytes / 1024).toFixed(2)}KB`; return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } 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'); - - if (standardMetrics.success) { - const standardAnalysis = await this.analyzeBuildOutput('standard'); - await this.saveBuildArtifacts('standard'); - - this.results.standardBuild = { - metrics: standardMetrics, - analysis: standardAnalysis - }; + const standardMetrics = + this.runBuild( + "build", + "Standard", + ); + + if ( + standardMetrics.success + ) { + const standardAnalysis = + await this.analyzeBuildOutput( + "standard", + ); + await this.saveBuildArtifacts( + "standard", + ); + + this.results.standardBuild = + { + metrics: + standardMetrics, + 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'); - - if (hyperMetrics.success) { - const hyperAnalysis = await this.analyzeBuildOutput('hyper'); - await this.saveBuildArtifacts('hyper'); - - this.results.hyperBuild = { - metrics: hyperMetrics, - analysis: hyperAnalysis - }; + const hyperMetrics = + this.runBuild( + "build:hyper", + "Hyper", + ); + + if ( + hyperMetrics.success + ) { + const hyperAnalysis = + await this.analyzeBuildOutput( + "hyper", + ); + await this.saveBuildArtifacts( + "hyper", + ); + + this.results.hyperBuild = + { + metrics: + hyperMetrics, + analysis: + hyperAnalysis, + }; } else { - throw new Error('Hyper build failed'); + throw new Error( + "Hyper build failed", + ); } // Calculate comparison - this.results.comparison = this.calculateComparison(); + 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'); - if (this.verbose) { - console.error(error); + this.log( + `Benchmark failed: ${error.message}`, + "error", + ); + if ( + this.verbose + ) { + console.error( + error, + ); } - process.exit(1); + process.exit( + 1, + ); } } } // Run benchmark -const benchmark = new WidgetBuildBenchmark(); -benchmark.run(); \ No newline at end of file +const benchmark = + new WidgetBuildBenchmark(); +benchmark.run(); diff --git a/benchmark/src/compare.js b/benchmark/src/compare.js index ca08c36..bee6578 100644 --- a/benchmark/src/compare.js +++ b/benchmark/src/compare.js @@ -1,105 +1,271 @@ -import fs from 'fs-extra'; -import path from 'path'; -import { fileURLToPath } from 'url'; -import chalk from 'chalk'; -import Table from 'cli-table3'; +import fs from "fs-extra"; +import path from "path"; +import { fileURLToPath } from "url"; +import chalk from "chalk"; +import Table from "cli-table3"; -const __filename = fileURLToPath(import.meta.url); -const __dirname = path.dirname(__filename); -const rootDir = path.join(__dirname, '..'); +const __filename = + fileURLToPath( + import.meta.url, + ); +const __dirname = + path.dirname( + __filename, + ); +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'); + 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.')); + if ( + !(await fs.pathExists( + standardDir, + )) || + !(await fs.pathExists( + hyperDir, + )) + ) { + console.error( + chalk.red( + "Build artifacts not found. Please run benchmark first.", + ), + ); return; } - const standardFiles = await this.getFileMap(standardDir); - const hyperFiles = await this.getFileMap(hyperDir); + const standardFiles = + await this.getFileMap( + standardDir, + ); + const hyperFiles = + await this.getFileMap( + hyperDir, + ); - const table = new Table({ - head: ['File', 'Standard Size', 'Hyper Size', 'Difference'], - style: { head: ['cyan'] } - }); + const table = + new Table({ + 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; for (const file of allFiles) { - const stdSize = standardFiles[file] || 0; - const hyperSize = hyperFiles[file] || 0; - const diff = hyperSize - stdSize; - - totalStandard += stdSize; - totalHyper += hyperSize; - - if (stdSize === 0) { + 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') + "-", + this.formatBytes( + hyperSize, + ), + chalk.yellow( + "New file", + ), ]); - } else if (hyperSize === 0) { + } else if ( + hyperSize === + 0 + ) { table.push([ file, - this.formatBytes(stdSize), - '-', - chalk.red('Removed') + this.formatBytes( + stdSize, + ), + "-", + 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 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", + ); + table.push([ file, - this.formatBytes(stdSize), - this.formatBytes(hyperSize), - diffStr + this.formatBytes( + stdSize, + ), + this.formatBytes( + hyperSize, + ), + diffStr, ]); } } // Add total row table.push([ - chalk.bold('TOTAL'), - chalk.bold(this.formatBytes(totalStandard)), - chalk.bold(this.formatBytes(totalHyper)), - chalk.bold(this.formatDifference(totalHyper - totalStandard, totalStandard)) + chalk.bold( + "TOTAL", + ), + chalk.bold( + this.formatBytes( + totalStandard, + ), + ), + chalk.bold( + this.formatBytes( + totalHyper, + ), + ), + chalk.bold( + this.formatDifference( + totalHyper - + totalStandard, + totalStandard, + ), + ), ]); - console.log('\n' + chalk.bold.cyan('=== File Size Comparison ===\n')); - console.log(table.toString()); + 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')); + async compareFileContents( + standardFiles, + hyperFiles, + standardDir, + hyperDir, + ) { + 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'); - - if (stdContent === hyperContent) { + if ( + hyperFiles[ + file + ] + ) { + 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 { differentCount++; @@ -107,49 +273,118 @@ class BuildComparator { } } - console.log('\n' + chalk.bold('Content Analysis:')); - console.log(`• ${identicalCount} files have identical content`); - console.log(`• ${differentCount} files have different content`); + 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 = '') { - const files = {}; - const items = await fs.readdir(dir); + async getFileMap( + dir, + basePath = "", + ) { + const files = + {}; + const items = + await fs.readdir( + dir, + ); for (const item of items) { - const fullPath = path.join(dir, item); - const relativePath = path.join(basePath, item); - const stats = await fs.stat(fullPath); + const fullPath = + path.join( + dir, + item, + ); + const relativePath = + path.join( + basePath, + item, + ); + const stats = + await fs.stat( + fullPath, + ); - if (stats.isDirectory()) { - const subFiles = await this.getFileMap(fullPath, relativePath); - Object.assign(files, subFiles); + if ( + stats.isDirectory() + ) { + const subFiles = + await this.getFileMap( + fullPath, + relativePath, + ); + Object.assign( + files, + subFiles, + ); } else { - files[relativePath] = stats.size; + files[ + relativePath + ] = + stats.size; } } return files; } - formatBytes(bytes) { - const absBytes = Math.abs(bytes); - if (absBytes < 1024) return `${bytes}B`; - if (absBytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)}KB`; + formatBytes( + bytes, + ) { + const absBytes = + Math.abs( + bytes, + ); + if ( + absBytes < + 1024 + ) + return `${bytes}B`; + if ( + absBytes < + 1024 * 1024 + ) + return `${(bytes / 1024).toFixed(2)}KB`; return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } - formatDifference(diff, original) { - const percentage = ((diff / original) * 100).toFixed(1); + formatDifference( + diff, + original, + ) { + const percentage = + ( + (diff / + original) * + 100 + ).toFixed(1); if (diff > 0) { - return chalk.red(`+${this.formatBytes(diff)} (+${percentage}%)`); - } else if (diff < 0) { - return chalk.green(`${this.formatBytes(diff)} (${percentage}%)`); + return chalk.red( + `+${this.formatBytes(diff)} (+${percentage}%)`, + ); + } 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 +const comparator = + new BuildComparator(); +comparator.compareFiles(); diff --git a/biome.json b/biome.json new file mode 100644 index 0000000..f2a65db --- /dev/null +++ b/biome.json @@ -0,0 +1,41 @@ +{ + "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", + "vcs": { + "enabled": false, + "clientKind": "git", + "useIgnoreFile": false + }, + "files": { + "ignoreUnknown": false, + "includes": [ + "**", + "!dist/**/*", + "!/benchmark/artifacts/**/*", + "!/benchmark/benchmarkWidget/**/*" + ] + }, + "formatter": { + "enabled": true, + "indentStyle": "space", + "lineWidth": 20 + }, + "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..f501217 100644 --- a/package.json +++ b/package.json @@ -35,8 +35,9 @@ ], "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", "type-fest": "4.41.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 872c695..3439358 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -33,6 +33,9 @@ importers: 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) @@ -193,6 +196,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==} @@ -1536,6 +1592,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 diff --git a/rslib.config.ts b/rslib.config.ts index 70632f4..900ba61 100644 --- a/rslib.config.ts +++ b/rslib.config.ts @@ -1,68 +1,80 @@ -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..b79f5ba 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -1,30 +1,54 @@ -#!/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..507c515 100644 --- a/src/commands/build/web/index.ts +++ b/src/commands/build/web/index.ts @@ -1,121 +1,286 @@ -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 "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; diff --git a/src/commands/start/web/index.ts b/src/commands/start/web/index.ts index c825766..13ffd5f 100644 --- a/src/commands/start/web/index.ts +++ b/src/commands/start/web/index.ts @@ -1,136 +1,308 @@ -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 "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; diff --git a/src/configurations/typescript/tsconfig.base.json b/src/configurations/typescript/tsconfig.base.json index f02bb19..5d0e2dc 100644 --- a/src/configurations/typescript/tsconfig.base.json +++ b/src/configurations/typescript/tsconfig.base.json @@ -4,7 +4,10 @@ "sourceMap": true, "module": "esnext", "target": "es6", - "lib": ["esnext", "dom"], + "lib": [ + "esnext", + "dom" + ], "moduleResolution": "bundler", "declaration": false, "noLib": false, @@ -24,4 +27,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..416899e 100644 --- a/src/configurations/vite/index.ts +++ b/src/configurations/vite/index.ts @@ -1,153 +1,271 @@ -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 { 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, + }; + }; diff --git a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts index 91e6880..1b2f701 100644 --- a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts +++ b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts @@ -4,31 +4,64 @@ import { Plugin } from "vite"; // @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 +104,10 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if (id === 'mendix:react-dom') { + if ( + id === + "mendix:react-dom" + ) { return ` const ReactDOM = window.ReactDOM; @@ -91,7 +127,10 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if (id === 'mendix:react-dom/client') { + if ( + id === + "mendix:react-dom/client" + ) { return ` const ReactDOMClient = window.ReactDOMClient; @@ -102,7 +141,10 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if (id === 'mendix:react/jsx-runtime') { + if ( + id === + "mendix:react/jsx-runtime" + ) { return ` const ReactJSXRuntime = window.ReactJSXRuntime; @@ -114,7 +156,10 @@ 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 +169,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..3c91013 100644 --- a/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts +++ b/src/configurations/vite/plugins/mendix-patch-vite-client-plugin.ts @@ -2,22 +2,45 @@ import { Plugin } from "vite"; export function mendixPatchViteClientPlugin(): Plugin { return { - name: 'mendix-patch-vite-client', - enforce: 'pre', - apply: 'serve', - configureServer(server) { - server.middlewares.use(async (req, res, next) => { - 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*\)/; - const m = code.match(rePageReload); - - if (m) { - const delay = m[1]; - const injectScript = ` + name: "mendix-patch-vite-client", + enforce: "pre", + apply: "serve", + configureServer( + server, + ) { + server.middlewares.use( + async ( + req, + res, + next, + ) => { + 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*\)/; + const m = + code.match( + rePageReload, + ); + + if (m) { + const delay = + m[1]; + const injectScript = ` const __mx_debounceReload = (time) => { let timer; return () => { @@ -41,16 +64,26 @@ 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.end(code); - return; - } + res.setHeader( + "Content-Type", + "application/javascript; charset=utf-8", + ); + res.end( + code, + ); + return; + } - next(); - }); - } + next(); + }, + ); + }, }; -} \ No newline at end of file +} diff --git a/src/constants/index.ts b/src/constants/index.ts index e871067..b044acb 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,18 +1,34 @@ -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 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; diff --git a/src/index.ts b/src/index.ts index d13c5e8..0918eb4 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,13 +1,26 @@ -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 { UserConfig } from "vite"; +import reactPlugin from "@vitejs/plugin-react"; + +export type PWTConfig = + UserConfig & { + reactPluginOptions?: Parameters< + typeof reactPlugin + >[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..1d0295e 100644 --- a/src/type-generator/generator.ts +++ b/src/type-generator/generator.ts @@ -1,146 +1,304 @@ -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 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; +} diff --git a/src/type-generator/header.ts b/src/type-generator/header.ts index 0ff29ba..23428e3 100644 --- a/src/type-generator/header.ts +++ b/src/type-generator/header.ts @@ -1,37 +1,71 @@ -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 "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. + */ + +`; +} diff --git a/src/type-generator/index.ts b/src/type-generator/index.ts index d9cc96c..57cfbc1 100644 --- a/src/type-generator/index.ts +++ b/src/type-generator/index.ts @@ -1,25 +1,54 @@ -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 "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, + ); +} diff --git a/src/type-generator/mendix-types.ts b/src/type-generator/mendix-types.ts index 624d9b9..75f5ead 100644 --- a/src/type-generator/mendix-types.ts +++ b/src/type-generator/mendix-types.ts @@ -1,359 +1,680 @@ -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 { + 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; +} diff --git a/src/type-generator/parser.ts b/src/type-generator/parser.ts index 3b8bc77..b79e002 100644 --- a/src/type-generator/parser.ts +++ b/src/type-generator/parser.ts @@ -1,194 +1,388 @@ -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 { + 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, + }), + ); +} diff --git a/src/type-generator/preview-types.ts b/src/type-generator/preview-types.ts index 9d61759..1ea9634 100644 --- a/src/type-generator/preview-types.ts +++ b/src/type-generator/preview-types.ts @@ -1,221 +1,361 @@ -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 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 + ); +} diff --git a/src/type-generator/system-props.ts b/src/type-generator/system-props.ts index 8b20b02..87a3e38 100644 --- a/src/type-generator/system-props.ts +++ b/src/type-generator/system-props.ts @@ -1,87 +1,185 @@ -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 { 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 + ); +} diff --git a/src/type-generator/types.ts b/src/type-generator/types.ts index 0398013..c8ed077 100644 --- a/src/type-generator/types.ts +++ b/src/type-generator/types.ts @@ -1,174 +1,213 @@ -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..f75cc70 100644 --- a/src/type-generator/utils.ts +++ b/src/type-generator/utils.ts @@ -1,92 +1,145 @@ -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; -} - -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'; +import type { + AttributeType, + Property, +} from "./types"; +import type { GenerateTargetPlatform } from "./mendix-types"; +import { mapPropertyToMendixType } from "./mendix-types"; - case 'DateTime': - return 'Date | string'; +export function mapPropertyTypeToTS( + property: Property, + target?: GenerateTargetPlatform, +): string { + const mapping = + mapPropertyToMendixType( + property, + target, + ); - case 'Binary': - return 'Blob | string'; + 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 "DateTime": + return "Date | 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'; + +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"; 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< + T, +>( + 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..2b89065 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,13 +1,15 @@ -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..e96df5d 100644 --- a/src/utils/getMendixProjectDirectory.ts +++ b/src/utils/getMendixProjectDirectory.ts @@ -1,13 +1,21 @@ -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 "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..8299e26 100644 --- a/src/utils/getMendixWidgetDirectory.ts +++ b/src/utils/getMendixWidgetDirectory.ts @@ -1,11 +1,16 @@ -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 "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..3cfac12 100644 --- a/src/utils/getViteOutputDirectory.ts +++ b/src/utils/getViteOutputDirectory.ts @@ -1,16 +1,32 @@ -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 "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..b157cf7 100644 --- a/src/utils/getViteUserConfiguration.ts +++ b/src/utils/getViteUserConfiguration.ts @@ -1,17 +1,35 @@ -import { PWTConfig, PWTConfigFn, PWTConfigFnPromise } from ".."; +import { + PWTConfig, + PWTConfigFn, + PWTConfigFnPromise, +} from ".."; -const getViteUserConfiguration = async (path: string): Promise => { - const getUserConfig = await import(`file://${path}`); - const getUserConfigFn: PWTConfigFn | PWTConfigFnPromise = getUserConfig.default; - const userConfig = getUserConfigFn(); +const getViteUserConfiguration = + async ( + path: string, + ): Promise => { + const getUserConfig = + await import( + `file://${path}` + ); + const getUserConfigFn: + | PWTConfigFn + | PWTConfigFnPromise = + getUserConfig.default; + const userConfig = + getUserConfigFn(); - if (userConfig instanceof Promise) { - const userConfigValue = await userConfig; + if ( + userConfig instanceof + Promise + ) { + const userConfigValue = + await userConfig; - return userConfigValue; - } + return userConfigValue; + } - return userConfig; -}; + 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..f6effe5 100644 --- a/src/utils/getViteWatchOutputDirectory.ts +++ b/src/utils/getViteWatchOutputDirectory.ts @@ -1,22 +1,35 @@ -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 "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; diff --git a/src/utils/getWidgetName.ts b/src/utils/getWidgetName.ts index 757a140..05ad9a0 100644 --- a/src/utils/getWidgetName.ts +++ b/src/utils/getWidgetName.ts @@ -1,13 +1,19 @@ -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..b7d9571 100644 --- a/src/utils/getWidgetPackageJson.ts +++ b/src/utils/getWidgetPackageJson.ts @@ -1,19 +1,33 @@ -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 "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; diff --git a/src/utils/getWidgetVersion.ts b/src/utils/getWidgetVersion.ts index 6bc2471..a490843 100644 --- a/src/utils/getWidgetVersion.ts +++ b/src/utils/getWidgetVersion.ts @@ -1,13 +1,19 @@ -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..31f63e1 100644 --- a/src/utils/pathIsExists.ts +++ b/src/utils/pathIsExists.ts @@ -1,13 +1,20 @@ -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 "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..ef1ac55 100644 --- a/src/utils/showMessage.ts +++ b/src/utils/showMessage.ts @@ -1,7 +1,12 @@ -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..b0f4417 100644 --- a/tools/copy-widget-schema.js +++ b/tools/copy-widget-schema.js @@ -1,20 +1,48 @@ -const fs = require('fs'); -const path = require('path'); +const fs = require("fs"); +const path = require("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'); + if ( + fs.existsSync( + sourcePath, + ) + ) { + fs.copyFileSync( + sourcePath, + targetPath, + ); + + 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..c80bed3 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,12 +1,18 @@ { - "include": ["src/**/*"], - "exclude": ["node_modules"], + "include": [ + "src/**/*" + ], + "exclude": [ + "node_modules" + ], "compilerOptions": { "resolveJsonModule": true, "esModuleInterop": true, - "lib": ["ESNext"], + "lib": [ + "ESNext" + ], "module": "preserve", "moduleResolution": "bundler", "target": "esnext" - }, -} \ No newline at end of file + } +} From 69c289c35934a3e2ad07c9d4b4532e71a915e0fb Mon Sep 17 00:00:00 2001 From: Chan Kang Date: Wed, 3 Sep 2025 20:45:47 +0900 Subject: [PATCH 2/2] fix: fix biome lint --- .husky/pre-commit | 1 + benchmark/src/benchmark.js | 933 ++++-------------- benchmark/src/compare.js | 379 ++----- biome.json | 12 +- package.json | 8 +- pnpm-lock.yaml | 29 +- rslib.config.ts | 118 +-- src/cli.ts | 46 +- src/commands/build/web/index.ts | 393 +++----- src/commands/start/web/index.ts | 425 +++----- .../typescript/tsconfig.base.json | 5 +- src/configurations/vite/index.ts | 390 +++----- .../plugins/mendix-hotreload-react-plugin.ts | 52 +- .../mendix-patch-vite-client-plugin.ts | 79 +- src/constants/index.ts | 40 +- src/index.ts | 27 +- src/type-generator/generator.ts | 264 ++--- src/type-generator/header.ts | 62 +- src/type-generator/index.ts | 38 +- src/type-generator/mendix-types.ts | 605 +++--------- src/type-generator/parser.ts | 425 +++----- src/type-generator/preview-types.ts | 290 ++---- src/type-generator/system-props.ts | 172 +--- src/type-generator/types.ts | 59 +- src/type-generator/utils.ts | 87 +- src/types.d.ts | 22 +- src/utils/getMendixProjectDirectory.ts | 20 +- src/utils/getMendixWidgetDirectory.ts | 15 +- src/utils/getViteOutputDirectory.ts | 41 +- src/utils/getViteUserConfiguration.ts | 41 +- src/utils/getViteWatchOutputDirectory.ts | 45 +- src/utils/getWidgetName.ts | 22 +- src/utils/getWidgetPackageJson.ts | 40 +- src/utils/getWidgetVersion.ts | 22 +- src/utils/pathIsExists.ts | 25 +- src/utils/showMessage.ts | 11 +- tools/copy-widget-schema.js | 54 +- tsconfig.json | 12 +- 38 files changed, 1517 insertions(+), 3792 deletions(-) create mode 100644 .husky/pre-commit 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/benchmark/src/benchmark.js b/benchmark/src/benchmark.js index 22cdd11..4cf0401 100644 --- a/benchmark/src/benchmark.js +++ b/benchmark/src/benchmark.js @@ -1,580 +1,251 @@ -import { execSync } from "child_process"; -import fs from "fs-extra"; -import path from "path"; -import { fileURLToPath } from "url"; +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 __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +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", - ); + constructor(options = {}) { + this.verbose = options.verbose || process.argv.includes("--verbose"); + this.jsonOutput = options.json || process.argv.includes("--json"); this.results = { - standardBuild: - {}, - hyperBuild: - {}, - comparison: - {}, + standardBuild: {}, + hyperBuild: {}, + comparison: {}, }; } - log( - message, - type = "info", - ) { - if ( - this - .jsonOutput - ) - return; + 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}`, - ); + 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, - ); + await fs.remove(distPath); + await fs.remove(tmpPath); // Clean any .mpk files - const files = - await fs.readdir( - widgetDir, - ); + const files = await fs.readdir(widgetDir); for (const file of files) { - if ( - file.endsWith( - ".mpk", - ) - ) { - await fs.remove( - path.join( - widgetDir, - file, - ), - ); + if (file.endsWith(".mpk")) { + await fs.remove(path.join(widgetDir, file)); } } } - runBuild( - command, - buildType, - ) { - this.log( - `Running ${buildType} build...`, - ); + runBuild(command, buildType) { + this.log(`Running ${buildType} build...`); - const startTime = - Date.now(); - const startMemory = - process.memoryUsage(); + const startTime = Date.now(); + const startMemory = process.memoryUsage(); try { - const output = - execSync( - `npm run ${command}`, - { - cwd: widgetDir, - stdio: - this - .verbose - ? "inherit" - : "pipe", - encoding: - "utf8", - }, - ); + const output = execSync(`npm run ${command}`, { + cwd: widgetDir, + stdio: this.verbose ? "inherit" : "pipe", + encoding: "utf8", + }); - const endTime = - Date.now(); - const endMemory = - process.memoryUsage(); + const endTime = Date.now(); + const endMemory = process.memoryUsage(); - const buildTime = - endTime - - startTime; + const buildTime = endTime - startTime; const memoryUsed = - (endMemory.heapUsed - - startMemory.heapUsed) / - 1024 / - 1024; - - this.log( - `${buildType} build completed in ${buildTime}ms`, - "success", - ); + (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, - }; + const analysis = { + distSize: 0, + fileCount: 0, + files: [], + mpkSize: 0, + }; // Analyze dist folder - if ( - await fs.pathExists( - distPath, - ) - ) { - const files = - await this.getFilesRecursively( - distPath, - ); - analysis.fileCount = - files.length; + 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, - }, - ); - analysis.distSize += - stats.size; + const stats = await fs.stat(file); + const relPath = path.relative(distPath, file); + analysis.files.push({ + path: relPath, + size: stats.size, + }); + analysis.distSize += stats.size; } } // Find and analyze .mpk file - const widgetFiles = - await fs.readdir( - distMpkPath, - ); - const mpkFile = - widgetFiles.find( - (f) => - f.endsWith( - ".mpk", - ), - ); + const widgetFiles = await fs.readdir(distMpkPath); + const mpkFile = widgetFiles.find((f) => f.endsWith(".mpk")); if (mpkFile) { - const mpkPath = - path.join( - distMpkPath, - mpkFile, - ); - const stats = - await fs.stat( - mpkPath, - ); - analysis.mpkSize = - stats.size; - analysis.mpkFile = - mpkFile; + const mpkPath = path.join(distMpkPath, mpkFile); + const stats = await fs.stat(mpkPath); + analysis.mpkSize = stats.size; + analysis.mpkFile = mpkFile; } return analysis; } - async getFilesRecursively( - dir, - ) { - const files = - []; - const items = - await fs.readdir( - dir, - ); + 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, - ); + const fullPath = path.join(dir, item); + const stats = await fs.stat(fullPath); - if ( - stats.isDirectory() - ) { - files.push( - ...(await this.getFilesRecursively( - fullPath, - )), - ); + if (stats.isDirectory()) { + files.push(...(await this.getFilesRecursively(fullPath))); } else { - files.push( - fullPath, - ); + files.push(fullPath); } } return files; } - async saveBuildArtifacts( - buildType, - ) { - const artifactsDir = - path.join( - rootDir, - "artifacts", - buildType, - ); - await fs.ensureDir( - artifactsDir, - ); + async saveBuildArtifacts(buildType) { + const artifactsDir = path.join(rootDir, "artifacts", buildType); + await fs.ensureDir(artifactsDir); // Copy dist folder - const distPath = - path.join( - widgetDir, - "dist", - ); - if ( - await fs.pathExists( - distPath, - ) - ) { - await fs.copy( - distPath, - path.join( - artifactsDir, - "dist", - ), - ); + const distPath = path.join(widgetDir, "dist"); + if (await fs.pathExists(distPath)) { + 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 widgetFiles = await fs.readdir(widgetDir); + const mpkFile = widgetFiles.find((f) => f.endsWith(".mpk")); if (mpkFile) { await fs.copy( - path.join( - widgetDir, - mpkFile, - ), - path.join( - artifactsDir, - mpkFile, - ), + path.join(widgetDir, mpkFile), + path.join(artifactsDir, mpkFile), ); } } calculateComparison() { - const std = - this.results - .standardBuild; - const hyper = - this.results - .hyperBuild; - - if ( - !std.metrics || - !hyper.metrics - ) { + 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, - ), + difference: hyper.metrics.buildTime - std.metrics.buildTime, + 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, - ), + difference: hyper.metrics.memoryUsed - std.metrics.memoryUsed, + 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, - ), + difference: hyper.analysis.distSize - std.analysis.distSize, + 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, - ), + difference: hyper.analysis.mpkSize - std.analysis.mpkSize, + 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, }, }; } displayResults() { - if ( - this - .jsonOutput - ) { - console.log( - JSON.stringify( - this - .results, - null, - 2, - ), - ); + if (this.jsonOutput) { + console.log(JSON.stringify(this.results, null, 2)); 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", - ], - style: { - head: [ - "cyan", - ], - }, - }); + const metricsTable = new Table({ + head: ["Metric", "Standard Build", "Hyper Build", "Difference"], + style: { + head: ["cyan"], + }, + }); + + const std = this.results.standardBuild; + const hyper = this.results.hyperBuild; + const comp = this.results.comparison; - const std = - this.results - .standardBuild; - const hyper = - this.results - .hyperBuild; - const comp = - this.results - .comparison; - - if ( - std.metrics && - hyper.metrics && - comp - ) { + if (std.metrics && hyper.metrics && comp) { metricsTable.push( [ "Build Time", `${std.metrics.buildTime}ms`, `${hyper.metrics.buildTime}ms`, this.formatDifference( - comp - .buildTime - .difference, - comp - .buildTime - .percentage, + comp.buildTime.difference, + comp.buildTime.percentage, "ms", ), ], @@ -583,103 +254,52 @@ class WidgetBuildBenchmark { `${std.metrics.memoryUsed.toFixed(2)}MB`, `${hyper.metrics.memoryUsed.toFixed(2)}MB`, this.formatDifference( - comp - .memoryUsage - .difference, - comp - .memoryUsage - .percentage, + comp.memoryUsage.difference, + comp.memoryUsage.percentage, "MB", ), ], [ "Dist Size", - this.formatBytes( - std - .analysis - .distSize, - ), - this.formatBytes( - hyper - .analysis - .distSize, - ), + this.formatBytes(std.analysis.distSize), + this.formatBytes(hyper.analysis.distSize), this.formatDifference( - comp - .distSize - .difference, - comp - .distSize - .percentage, + comp.distSize.difference, + comp.distSize.percentage, "bytes", true, ), ], [ "MPK Size", - this.formatBytes( - std - .analysis - .mpkSize, - ), - this.formatBytes( - hyper - .analysis - .mpkSize, - ), + this.formatBytes(std.analysis.mpkSize), + this.formatBytes(hyper.analysis.mpkSize), this.formatDifference( - comp - .mpkSize - .difference, - comp - .mpkSize - .percentage, + comp.mpkSize.difference, + comp.mpkSize.percentage, "bytes", true, ), ], [ "File Count", - std - .analysis - .fileCount, - hyper - .analysis - .fileCount, - comp - .fileCount - .difference > - 0 + std.analysis.fileCount, + hyper.analysis.fileCount, + comp.fileCount.difference > 0 ? `+${comp.fileCount.difference}` : `${comp.fileCount.difference}`, ], ); - console.log( - metricsTable.toString(), - ); + 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; - const buildTimeImproved = - comp - .buildTime - .difference < - 0; - const sizeImproved = - comp.mpkSize - .difference < - 0; - - if ( - buildTimeImproved - ) { + if (buildTimeImproved) { console.log( chalk.green( `✓ Hyper build is ${Math.abs(comp.buildTime.percentage)}% faster`, @@ -687,246 +307,119 @@ class WidgetBuildBenchmark { ); } else { console.log( - chalk.yellow( - `⚠ Hyper build is ${comp.buildTime.percentage}% slower`, - ), + chalk.yellow(`⚠ Hyper build is ${comp.buildTime.percentage}% slower`), ); } - if ( - sizeImproved - ) { + if (sizeImproved) { console.log( chalk.green( `✓ Hyper build produces ${Math.abs(comp.mpkSize.percentage)}% smaller package`, ), ); - } else if ( - comp.mpkSize - .difference > - 0 - ) { + } else if (comp.mpkSize.difference > 0) { console.log( chalk.yellow( `⚠ Hyper build produces ${comp.mpkSize.percentage}% larger package`, ), ); } else { - console.log( - chalk.blue( - `• Package size is identical`, - ), - ); + console.log(chalk.blue(`• Package size is identical`)); } } } - 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 color = - diff > 0 - ? chalk.red - : chalk.green; - - return color( - `${sign}${value} (${sign}${Math.abs(percentage)}%)`, - ); + 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 color = diff > 0 ? chalk.red : chalk.green; + + return color(`${sign}${value} (${sign}${Math.abs(percentage)}%)`); } - formatBytes( - bytes, - ) { - if ( - bytes < 1024 - ) - return `${bytes}B`; - if ( - bytes < - 1024 * 1024 - ) - return `${(bytes / 1024).toFixed(2)}KB`; + formatBytes(bytes) { + if (bytes < 1024) return `${bytes}B`; + if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)}KB`; return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } 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", - ); + if (standardMetrics.success) { + const standardAnalysis = await this.analyzeBuildOutput("standard"); + await this.saveBuildArtifacts("standard"); - this.results.standardBuild = - { - metrics: - standardMetrics, - analysis: - standardAnalysis, - }; + this.results.standardBuild = { + metrics: standardMetrics, + 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", - ); + if (hyperMetrics.success) { + const hyperAnalysis = await this.analyzeBuildOutput("hyper"); + await this.saveBuildArtifacts("hyper"); - this.results.hyperBuild = - { - metrics: - hyperMetrics, - analysis: - hyperAnalysis, - }; + this.results.hyperBuild = { + metrics: hyperMetrics, + analysis: hyperAnalysis, + }; } else { - throw new Error( - "Hyper build failed", - ); + throw new Error("Hyper build failed"); } // Calculate comparison - this.results.comparison = - this.calculateComparison(); + 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", - ); - if ( - this.verbose - ) { - console.error( - error, - ); + this.log(`Benchmark failed: ${error.message}`, "error"); + if (this.verbose) { + console.error(error); } - process.exit( - 1, - ); + process.exit(1); } } } // Run benchmark -const benchmark = - new WidgetBuildBenchmark(); +const benchmark = new WidgetBuildBenchmark(); benchmark.run(); diff --git a/benchmark/src/compare.js b/benchmark/src/compare.js index bee6578..e096781 100644 --- a/benchmark/src/compare.js +++ b/benchmark/src/compare.js @@ -1,178 +1,85 @@ -import fs from "fs-extra"; -import path from "path"; -import { fileURLToPath } from "url"; +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 __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +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", - ); + 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, - )) + !(await fs.pathExists(standardDir)) || + !(await fs.pathExists(hyperDir)) ) { console.error( - chalk.red( - "Build artifacts not found. Please run benchmark first.", - ), + chalk.red("Build artifacts not found. Please run benchmark first."), ); return; } - const standardFiles = - await this.getFileMap( - standardDir, - ); - const hyperFiles = - await this.getFileMap( - hyperDir, - ); + const standardFiles = await this.getFileMap(standardDir); + const hyperFiles = await this.getFileMap(hyperDir); - const table = - new Table({ - head: [ - "File", - "Standard Size", - "Hyper Size", - "Difference", - ], - style: { - head: [ - "cyan", - ], - }, - }); + const table = new Table({ + 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; for (const file of allFiles) { - const stdSize = - standardFiles[ - file - ] || 0; - const hyperSize = - hyperFiles[ - file - ] || 0; - const diff = - hyperSize - - stdSize; + const stdSize = standardFiles[file] || 0; + const hyperSize = hyperFiles[file] || 0; + const diff = hyperSize - stdSize; - totalStandard += - stdSize; - totalHyper += - hyperSize; + totalStandard += stdSize; + totalHyper += hyperSize; - if ( - stdSize === - 0 - ) { + if (stdSize === 0) { table.push([ file, "-", - this.formatBytes( - hyperSize, - ), - chalk.yellow( - "New file", - ), + this.formatBytes(hyperSize), + chalk.yellow("New file"), ]); - } else if ( - hyperSize === - 0 - ) { + } else if (hyperSize === 0) { table.push([ file, - this.formatBytes( - stdSize, - ), + this.formatBytes(stdSize), "-", - chalk.red( - "Removed", - ), + chalk.red("Removed"), ]); } else { - const percentage = - ( - (diff / - stdSize) * - 100 - ).toFixed( - 1, - ); + 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", - ); + ? 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, - ), + this.formatBytes(stdSize), + this.formatBytes(hyperSize), diffStr, ]); } @@ -180,37 +87,16 @@ class BuildComparator { // Add total row table.push([ + chalk.bold("TOTAL"), + chalk.bold(this.formatBytes(totalStandard)), + chalk.bold(this.formatBytes(totalHyper)), chalk.bold( - "TOTAL", - ), - chalk.bold( - this.formatBytes( - totalStandard, - ), - ), - chalk.bold( - this.formatBytes( - totalHyper, - ), - ), - chalk.bold( - this.formatDifference( - totalHyper - - totalStandard, - totalStandard, - ), + this.formatDifference(totalHyper - totalStandard, totalStandard), ), ]); - console.log( - "\n" + - chalk.bold.cyan( - "=== File Size Comparison ===\n", - ), - ); - console.log( - table.toString(), - ); + console.log(`\n${chalk.bold.cyan("=== File Size Comparison ===\n")}`); + console.log(table.toString()); // Check for content differences await this.compareFileContents( @@ -221,51 +107,23 @@ class BuildComparator { ); } - async compareFileContents( - standardFiles, - hyperFiles, - standardDir, - hyperDir, - ) { - const jsFiles = - Object.keys( - standardFiles, - ).filter( - (f) => - f.endsWith( - ".js", - ), - ); + async compareFileContents(standardFiles, hyperFiles, standardDir, hyperDir) { + 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", - ); + if (hyperFiles[file]) { + const stdContent = await fs.readFile( + path.join(standardDir, file), + "utf8", + ); + const hyperContent = await fs.readFile( + path.join(hyperDir, file), + "utf8", + ); - if ( - stdContent === - hyperContent - ) { + if (stdContent === hyperContent) { identicalCount++; } else { differentCount++; @@ -273,118 +131,49 @@ class BuildComparator { } } - console.log( - "\n" + - chalk.bold( - "Content Analysis:", - ), - ); - console.log( - `• ${identicalCount} files have identical content`, - ); - console.log( - `• ${differentCount} files have different content`, - ); + 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 = "", - ) { - const files = - {}; - const items = - await fs.readdir( - dir, - ); + async getFileMap(dir, basePath = "") { + const files = {}; + const items = await fs.readdir(dir); for (const item of items) { - const fullPath = - path.join( - dir, - item, - ); - const relativePath = - path.join( - basePath, - item, - ); - const stats = - await fs.stat( - fullPath, - ); + const fullPath = path.join(dir, item); + const relativePath = path.join(basePath, item); + const stats = await fs.stat(fullPath); - if ( - stats.isDirectory() - ) { - const subFiles = - await this.getFileMap( - fullPath, - relativePath, - ); - Object.assign( - files, - subFiles, - ); + if (stats.isDirectory()) { + const subFiles = await this.getFileMap(fullPath, relativePath); + Object.assign(files, subFiles); } else { - files[ - relativePath - ] = - stats.size; + files[relativePath] = stats.size; } } return files; } - formatBytes( - bytes, - ) { - const absBytes = - Math.abs( - bytes, - ); - if ( - absBytes < - 1024 - ) - return `${bytes}B`; - if ( - absBytes < - 1024 * 1024 - ) - return `${(bytes / 1024).toFixed(2)}KB`; + formatBytes(bytes) { + const absBytes = Math.abs(bytes); + if (absBytes < 1024) return `${bytes}B`; + if (absBytes < 1024 * 1024) return `${(bytes / 1024).toFixed(2)}KB`; return `${(bytes / (1024 * 1024)).toFixed(2)}MB`; } - formatDifference( - diff, - original, - ) { - const percentage = - ( - (diff / - original) * - 100 - ).toFixed(1); + formatDifference(diff, original) { + const percentage = ((diff / original) * 100).toFixed(1); if (diff > 0) { - return chalk.red( - `+${this.formatBytes(diff)} (+${percentage}%)`, - ); - } else if ( - diff < 0 - ) { - return chalk.green( - `${this.formatBytes(diff)} (${percentage}%)`, - ); + return chalk.red(`+${this.formatBytes(diff)} (+${percentage}%)`); + } 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(); +const comparator = new BuildComparator(); comparator.compareFiles(); diff --git a/biome.json b/biome.json index f2a65db..47cbfbd 100644 --- a/biome.json +++ b/biome.json @@ -1,23 +1,23 @@ { "$schema": "https://biomejs.dev/schemas/2.2.2/schema.json", "vcs": { - "enabled": false, - "clientKind": "git", - "useIgnoreFile": false + "enabled": true, + "clientKind": "git" }, "files": { "ignoreUnknown": false, "includes": [ "**", "!dist/**/*", - "!/benchmark/artifacts/**/*", - "!/benchmark/benchmarkWidget/**/*" + "!**/benchmark/artifacts", + "!**/benchmark/benchmarkWidget" ] }, "formatter": { "enabled": true, "indentStyle": "space", - "lineWidth": 20 + "indentWidth": 2, + "lineWidth": 80 }, "linter": { "enabled": true, diff --git a/package.json b/package.json index f501217..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", @@ -40,6 +45,7 @@ "@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 3439358..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,7 +28,7 @@ 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 @@ -42,6 +42,9 @@ importers: '@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 @@ -983,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==} @@ -1427,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==} @@ -2019,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) @@ -2027,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 @@ -2248,6 +2261,8 @@ snapshots: graceful-fs@4.2.11: {} + husky@9.1.7: {} + ieee754@1.2.1: {} inherits@2.0.4: {} @@ -2460,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 @@ -2473,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: @@ -2657,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 900ba61..b07ef5f 100644 --- a/rslib.config.ts +++ b/rslib.config.ts @@ -1,80 +1,68 @@ import { defineConfig } from "@rslib/core"; -export default defineConfig( - { - lib: [ - { - format: - "cjs", - bundle: true, - dts: true, - source: { - entry: { - cli: "src/cli.ts", - }, +export default defineConfig({ + lib: [ + { + format: "cjs", + bundle: true, + dts: true, + source: { + entry: { + cli: "src/cli.ts", }, - output: { - filename: - { - js: "[name].js", - }, + }, + output: { + filename: { + js: "[name].js", }, }, - { - format: - "esm", - bundle: true, - dts: true, - source: { - entry: { - index: - "src/index.ts", - }, + }, + { + format: "esm", + bundle: true, + dts: true, + source: { + entry: { + index: "src/index.ts", }, - output: { - filename: - { - js: "[name].mjs", - }, + }, + output: { + filename: { + js: "[name].mjs", }, }, - { - format: - "cjs", - bundle: true, - dts: false, - source: { - entry: { - index: - "src/index.ts", - }, + }, + { + format: "cjs", + bundle: true, + dts: false, + source: { + entry: { + index: "src/index.ts", }, - output: { - filename: - { - js: "[name].cjs", - }, + }, + 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, - }, - }, + }, + ], + 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 b79f5ba..0454ef9 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -13,42 +13,22 @@ program.version( ); program - .command( - "build:web", - ) - .summary( - "build web widget", - ) - .action( - async () => { - await buildWebCommand(); - }, - ); + .command("build:web") + .summary("build web widget") + .action(async () => { + await buildWebCommand(); + }); program - .command( - "release:web", - ) - .summary( - "release web widget", - ) - .action( - async () => { - await buildWebCommand( - true, - ); - }, - ); + .command("release:web") + .summary("release web widget") + .action(async () => { + await buildWebCommand(true); + }); program - .command( - "start:web", - ) - .summary( - "start web widget live reload", - ) - .action( - startWebCommand, - ); + .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 507c515..e799860 100644 --- a/src/commands/build/web/index.ts +++ b/src/commands/build/web/index.ts @@ -1,12 +1,12 @@ -import fs from "fs/promises"; -import path from "path"; -import { - InlineConfig, - UserConfig, - build as viteBuild, -} from "vite"; +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, @@ -15,272 +15,149 @@ import { 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 { generateTypesFromFile } from "../../../type-generator"; 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", - ); +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 widgetVersion = - await getWidgetVersion(); - const outputDir = - path.join( - distDir, - widgetVersion, - ); +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( - outputDir, - ); - await fs.mkdir( - WEB_OUTPUT_DIRECTORY, - { - recursive: true, - }, - ); + await fs.mkdir(typingsPath); - const customViteConfigPath = - path.join( - PROJECT_DIRECTORY, - VITE_CONFIGURATION_FILENAME, - ); - const viteConfigIsExists = - await pathIsExists( - customViteConfigPath, - ); - let resultViteConfig: UserConfig; + const newTypingsFilePath = path.join( + typingsPath, + `${widgetName}Props.d.ts`, + ); + const typingContents = await generateTypesFromFile( + originWidgetXmlPath, + "web", + ); - if ( - viteConfigIsExists - ) { - const userConfig = - await getViteUserConfiguration( - customViteConfigPath, - ); + await fs.writeFile(newTypingsFilePath, typingContents); - resultViteConfig = - await getViteDefaultConfig( - false, - userConfig, - ); - } else { - resultViteConfig = - await getViteDefaultConfig( - false, - ); - } + showMessage("Remove previous builds"); - 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`, - ); + const distDir = path.join(PROJECT_DIRECTORY, DIST_DIRECTORY_NAME); + const distIsExists = await pathIsExists(distDir); - await fs.copyFile( - originPackageXmlPath, - destPackageXmlPath, - ); - await fs.copyFile( - originWidgetXmlPath, - destWidgetXmlPath, - ); + if (distIsExists) { + await fs.rm(distDir, { + recursive: true, + force: true, + }); + } - showMessage( - "Start build", - ); + await fs.mkdir(distDir); - 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", - }, - ]; + showMessage("Copy resources"); - await Promise.all( - viteBuildConfigs.map( - async ( - config, - ) => { - await viteBuild( - config, - ); - }, - ), - ); + const widgetVersion = await getWidgetVersion(); + const outputDir = path.join(distDir, widgetVersion); - showMessage( - "Generate mpk file", - ); + await fs.mkdir(outputDir); + await fs.mkdir(WEB_OUTPUT_DIRECTORY, { + recursive: true, + }); - 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, - ); + const customViteConfigPath = path.join( + PROJECT_DIRECTORY, + VITE_CONFIGURATION_FILENAME, + ); + const viteConfigIsExists = await pathIsExists(customViteConfigPath); + let resultViteConfig: UserConfig; - await zip( - WEB_OUTPUT_DIRECTORY, - mpkFileDestPath, - ); - await fs.copyFile( - mpkFileDestPath, - mendixMpkFileDestPath, - ); + if (viteConfigIsExists) { + const userConfig = await getViteUserConfiguration(customViteConfigPath); - showMessage( - `${COLOR_GREEN("Build complete.")}`, - ); - } catch (error) { - showMessage( - `${COLOR_ERROR("Build failed.")}\nError occurred: ${COLOR_ERROR((error as Error).stack)}`, - ); + 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 13ffd5f..d850925 100644 --- a/src/commands/start/web/index.ts +++ b/src/commands/start/web/index.ts @@ -1,11 +1,10 @@ -import fs from "fs/promises"; -import path from "path"; -import { - UserConfig, - createServer, -} from "vite"; -import { PluginOption } from "vite"; - +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, @@ -13,296 +12,156 @@ import { PROJECT_DIRECTORY, VITE_CONFIGURATION_FILENAME, } from "../../../constants"; -import showMessage from "../../../utils/showMessage"; +import { generateTypesFromFile } from "../../../type-generator"; +import getViteUserConfiguration from "../../../utils/getViteUserConfiguration"; 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", - ); +import pathIsExists from "../../../utils/pathIsExists"; +import showMessage from "../../../utils/showMessage"; - await fs.writeFile( - newTypingsFilePath, - typingContents, +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(); -const startWebCommand = - async () => { - try { - showMessage( - "Start widget server", - ); + if (viteConfigIsExists) { + const userConfig = await getViteUserConfiguration(customViteConfigPath); - 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); + } - resultViteConfig = - await getViteDefaultConfig( - false, - userConfig, - ); - } else { - resultViteConfig = - await getViteDefaultConfig( - false, - ); - } + const viteCachePath = path.join(PROJECT_DIRECTORY, "node_modules/.vite"); + const viteCachePathExists = await pathIsExists(viteCachePath); - const viteCachePath = - path.join( - PROJECT_DIRECTORY, - "node_modules/.vite", - ); - const viteCachePathExists = - await pathIsExists( - viteCachePath, - ); + if (viteCachePathExists) { + await fs.rm(viteCachePath, { + recursive: true, + force: true, + }); + } - 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, + }, }, - ); - } - - 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(); - } - }, - ); - }, - }, - ], + 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", - ); + await viteServer.listen(); - 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, - ); + showMessage("Generate hot reload widget"); - const distDir = - await getViteWatchOutputDirectory(); - const distIsExists = - await pathIsExists( - distDir, - ); - const hotReloadWidgetPath = - path.join( - distDir, - `${widgetName}.mjs`, - ); - const dummyCssPath = - path.join( - distDir, - `${widgetName}.css`, - ); + 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, + }); + } - 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, ""); - 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)}`, - ); - } - }; + 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 5d0e2dc..93a0799 100644 --- a/src/configurations/typescript/tsconfig.base.json +++ b/src/configurations/typescript/tsconfig.base.json @@ -4,10 +4,7 @@ "sourceMap": true, "module": "esnext", "target": "es6", - "lib": [ - "esnext", - "dom" - ], + "lib": ["esnext", "dom"], "moduleResolution": "bundler", "declaration": false, "noLib": false, diff --git a/src/configurations/vite/index.ts b/src/configurations/vite/index.ts index 416899e..479e77d 100644 --- a/src/configurations/vite/index.ts +++ b/src/configurations/vite/index.ts @@ -1,271 +1,165 @@ -import { UserConfig } from "vite"; +import path from "node:path"; 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 type { UserConfig } from "vite"; +import type { PWTConfig } from "../.."; +import { PROJECT_DIRECTORY, WEB_OUTPUT_DIRECTORY } from "../../constants"; import getViteOutputDirectory from "../../utils/getViteOutputDirectory"; -import { PWTConfig } from "../.."; +import getWidgetName from "../../utils/getWidgetName"; -export const getEditorConfigDefaultConfig = - async ( - isProduction: boolean, - ): Promise => { - const widgetName = - await getWidgetName(); +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", - ], + 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(); +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", - ], + 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`; }, - 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", - }, - }, + 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(); +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`; - } + 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`; - } + if (format === "es") { + return `${widgetName}.mjs`; + } - return entry; - }, - cssFileName: - widgetName, + return entry; }, - 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", - }, + 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, - }; + }, + ...userCustomConfig, }; +}; diff --git a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts index 1b2f701..7d3691c 100644 --- a/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts +++ b/src/configurations/vite/plugins/mendix-hotreload-react-plugin.ts @@ -1,4 +1,4 @@ -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. @@ -7,50 +7,35 @@ export function mendixHotreloadReactPlugin(): Plugin { name: "mendix-hotreload-react-18.2.0", enforce: "pre", resolveId(id) { - if ( - id === - "react" - ) { + if (id === "react") { return { id: "mendix:react", external: true, }; } - if ( - id === - "react-dom" - ) { + if (id === "react-dom") { return { id: "mendix:react-dom", external: true, }; } - if ( - id === - "react-dom/client" - ) { + if (id === "react-dom/client") { return { id: "mendix:react-dom/client", external: true, }; } - if ( - id === - "react/jsx-runtime" - ) { + if (id === "react/jsx-runtime") { return { id: "mendix:react/jsx-runtime", external: true, }; } - if ( - id === - "react/jsx-dev-runtime" - ) { + if (id === "react/jsx-dev-runtime") { return { id: "mendix:react/jsx-dev-runtime", external: true, @@ -58,10 +43,7 @@ export function mendixHotreloadReactPlugin(): Plugin { } }, load(id) { - if ( - id === - "mendix:react" - ) { + if (id === "mendix:react") { return ` const React = window.React; @@ -104,10 +86,7 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if ( - id === - "mendix:react-dom" - ) { + if (id === "mendix:react-dom") { return ` const ReactDOM = window.ReactDOM; @@ -127,10 +106,7 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if ( - id === - "mendix:react-dom/client" - ) { + if (id === "mendix:react-dom/client") { return ` const ReactDOMClient = window.ReactDOMClient; @@ -141,10 +117,7 @@ export function mendixHotreloadReactPlugin(): Plugin { `; } - if ( - id === - "mendix:react/jsx-runtime" - ) { + if (id === "mendix:react/jsx-runtime") { return ` const ReactJSXRuntime = window.ReactJSXRuntime; @@ -156,10 +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; 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 3c91013..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,46 +1,25 @@ -import { Plugin } from "vite"; +import type { Plugin } from "vite"; export function mendixPatchViteClientPlugin(): Plugin { return { name: "mendix-patch-vite-client", enforce: "pre", apply: "serve", - configureServer( - server, - ) { - server.middlewares.use( - async ( - req, - res, - next, - ) => { - const url = - req.url || - ""; + configureServer(server) { + server.middlewares.use(async (req, res, next) => { + 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*\)/; - const m = - code.match( - rePageReload, - ); + 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) { - const delay = - m[1]; - const injectScript = ` + if (m) { + const delay = m[1]; + const injectScript = ` const __mx_debounceReload = (time) => { let timer; return () => { @@ -64,26 +43,22 @@ const __mx_debounceReload = (time) => { }; `; - code = - code.replace( - rePageReload, - `${injectScript}\nconst pageReload = __mx_debounceReload(${delay})`, - ); - } - - res.setHeader( - "Content-Type", - "application/javascript; charset=utf-8", - ); - res.end( - code, + code = code.replace( + rePageReload, + `${injectScript}\nconst pageReload = __mx_debounceReload(${delay})`, ); - return; } - next(); - }, - ); + res.setHeader( + "Content-Type", + "application/javascript; charset=utf-8", + ); + res.end(code); + return; + } + + next(); + }); }, }; } diff --git a/src/constants/index.ts b/src/constants/index.ts index b044acb..7fe2d98 100644 --- a/src/constants/index.ts +++ b/src/constants/index.ts @@ -1,34 +1,24 @@ +import path from "node:path"; import chalk from "chalk"; -import path from "path"; -export const PROJECT_DIRECTORY = - process.cwd(); +export const PROJECT_DIRECTORY = process.cwd(); -export const CLI_DIRECTORY = - path.join( - PROJECT_DIRECTORY, - "node_modules/@repixelcorp/hyper-pwt", - ); +export const CLI_DIRECTORY = path.join( + PROJECT_DIRECTORY, + "node_modules/@repixelcorp/hyper-pwt", +); -export const DIST_DIRECTORY_NAME = - "dist"; +export const DIST_DIRECTORY_NAME = "dist"; -export const WEB_OUTPUT_DIRECTORY = - path.join( - PROJECT_DIRECTORY, - `/${DIST_DIRECTORY_NAME}/tmp/widgets`, - ); +export const WEB_OUTPUT_DIRECTORY = path.join( + PROJECT_DIRECTORY, + `/${DIST_DIRECTORY_NAME}/tmp/widgets`, +); -export const VITE_CONFIGURATION_FILENAME = - "vite.config.mjs"; +export const VITE_CONFIGURATION_FILENAME = "vite.config.mjs"; -export const COLOR_NAME = - chalk.bold - .blueBright; +export const COLOR_NAME = chalk.bold.blueBright; -export const COLOR_ERROR = - chalk.bold.red; +export const COLOR_ERROR = chalk.bold.red; -export const COLOR_GREEN = - chalk.bold - .greenBright; +export const COLOR_GREEN = chalk.bold.greenBright; diff --git a/src/index.ts b/src/index.ts index 0918eb4..f3f9cfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,26 +1,15 @@ +import type reactPlugin from "@vitejs/plugin-react"; import type { UserConfig } from "vite"; -import reactPlugin from "@vitejs/plugin-react"; -export type PWTConfig = - UserConfig & { - reactPluginOptions?: Parameters< - typeof reactPlugin - >[0]; - }; +export type PWTConfig = UserConfig & { + reactPluginOptions?: Parameters[0]; +}; -export type PWTConfigFnPromise = - () => Promise; -export type PWTConfigFn = - () => - | PWTConfig - | Promise; +export type PWTConfigFnPromise = () => Promise; +export type PWTConfigFn = () => PWTConfig | Promise; export function definePWTConfig( - config: - | PWTConfigFn - | PWTConfigFnPromise, -): - | PWTConfigFn - | PWTConfigFnPromise { + config: PWTConfigFn | PWTConfigFnPromise, +): PWTConfigFn | PWTConfigFnPromise { return config; } diff --git a/src/type-generator/generator.ts b/src/type-generator/generator.ts index 1d0295e..0b34c7a 100644 --- a/src/type-generator/generator.ts +++ b/src/type-generator/generator.ts @@ -1,126 +1,71 @@ +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 { - WidgetDefinition, Property, PropertyGroup, SystemProperty, + WidgetDefinition, } from "./types"; -import type { GenerateTargetPlatform } from "./mendix-types"; import { + formatDescription, 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[]; + 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 += 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 += generateJSDoc(widget); output += `export interface ${interfaceName} {\n`; - const systemPropsLines = - generateSystemProps( - { - hasLabel, - platform: - target, - }, - ); + const systemPropsLines = generateSystemProps({ + hasLabel, + platform: target, + }); for (const line of systemPropsLines) { output += ` ${line}\n`; } - if ( - systemPropsLines.length > - 0 && - widgetProperties.length > - 0 - ) { + if (systemPropsLines.length > 0 && widgetProperties.length > 0) { output += `\n // Widget properties\n`; } for (const property of widgetProperties) { - output += - generatePropertyDefinition( - property, - target, - ); + output += generatePropertyDefinition(property, target); } output += "}\n"; @@ -128,38 +73,20 @@ export function generateTypeDefinition( return output; } -function generateInterfaceName( - widgetName: string, -): string { +function generateInterfaceName(widgetName: string): string { return `${pascalCase(widgetName)}ContainerProps`; } export function extractAllProperties( - properties: - | PropertyGroup[] - | Property[], -): ( - | Property - | SystemProperty -)[] { - const result: ( - | Property - | SystemProperty - )[] = []; + properties: PropertyGroup[] | Property[], +): (Property | SystemProperty)[] { + const result: (Property | SystemProperty)[] = []; for (const item of properties) { - if ( - isPropertyGroup( - item, - ) - ) { - result.push( - ...item.properties, - ); + if (isPropertyGroup(item)) { + result.push(...item.properties); } else { - result.push( - item, - ); + result.push(item); } } @@ -167,56 +94,30 @@ export function extractAllProperties( } function isPropertyGroup( - item: - | PropertyGroup - | Property - | SystemProperty, + item: PropertyGroup | Property | SystemProperty, ): item is PropertyGroup { - return ( - "caption" in - item && - "properties" in - item - ); + return "caption" in item && "properties" in item; } function isSystemProperty( - item: - | Property - | SystemProperty, + item: Property | SystemProperty, ): item is SystemProperty { - return ( - !( - "type" in item - ) && - "key" in item - ); + return !("type" in item) && "key" in item; } -function generateJSDoc( - widget: WidgetDefinition, -): string { - let jsDoc = - "/**\n"; +function generateJSDoc(widget: WidgetDefinition): string { + let jsDoc = "/**\n"; jsDoc += ` * Props for ${widget.name}\n`; - if ( - widget.description - ) { + if (widget.description) { jsDoc += ` * ${formatDescription(widget.description)}\n`; } - if ( - widget.needsEntityContext - ) { + if (widget.needsEntityContext) { jsDoc += ` * @needsEntityContext true\n`; } - if ( - widget.supportedPlatform && - widget.supportedPlatform !== - "Web" - ) { + if (widget.supportedPlatform && widget.supportedPlatform !== "Web") { jsDoc += ` * @platform ${widget.supportedPlatform}\n`; } @@ -228,75 +129,36 @@ function generatePropertyDefinition( property: Property, target: GenerateTargetPlatform, ): string { - const indent = - " "; + const indent = " "; let output = ""; - if ( - property.description - ) { + if (property.description) { output += `${indent}/**\n`; output += `${indent} * ${formatDescription(property.description)}\n`; - if ( - property.caption && - property.caption !== - property.description - ) { + if (property.caption && property.caption !== property.description) { output += `${indent} * @caption ${property.caption}\n`; } - if ( - property.defaultValue !== - undefined && - property.defaultValue !== - "" - ) { + if (property.defaultValue !== undefined && property.defaultValue !== "") { output += `${indent} * @default ${property.defaultValue}\n`; } - if ( - property.type === - "attribute" && - property.attributeTypes - ) { + 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( - ", ", - ); + 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, - ); + const propertyKey = sanitizePropertyKey(property.key); + const optional = property.required === false ? "?" : ""; + const propertyType = mapPropertyTypeToTS(property, target); output += `${indent}${propertyKey}${optional}: ${propertyType};\n`; diff --git a/src/type-generator/header.ts b/src/type-generator/header.ts index 23428e3..1237084 100644 --- a/src/type-generator/header.ts +++ b/src/type-generator/header.ts @@ -1,53 +1,26 @@ -import { readFileSync } from "fs"; -import { join } from "path"; +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" + 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", - ), - ); + 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" - ); + if (packageJson.name === "@repixelcorp/hyper-pwt") { + return packageJson.version || "unknown"; } } catch { return "unknown"; @@ -56,8 +29,7 @@ export function getPackageVersion(): string { } export function generateHeaderComment(): string { - const version = - getPackageVersion(); + const version = getPackageVersion(); return `/** * This file was automatically generated by @repixelcorp/hyper-pwt v${version} diff --git a/src/type-generator/index.ts b/src/type-generator/index.ts index 57cfbc1..ff8ba20 100644 --- a/src/type-generator/index.ts +++ b/src/type-generator/index.ts @@ -1,38 +1,27 @@ -import { readFile } from "fs/promises"; -import { parseWidgetXML } from "./parser"; +import { readFile } from "node:fs/promises"; import { generateTypeDefinition } from "./generator"; -import { generatePreviewTypeDefinition } from "./preview-types"; import type { GenerateTargetPlatform } from "./mendix-types"; +import { parseWidgetXML } from "./parser"; +import { generatePreviewTypeDefinition } from "./preview-types"; -export { parseWidgetXML } from "./parser"; export { generateTypeDefinition } from "./generator"; +export { parseWidgetXML } from "./parser"; export { generatePreviewTypeDefinition } from "./preview-types"; export type { - WidgetDefinition, Property, PropertyGroup, PropertyType, + WidgetDefinition, } from "./types"; export function generateTypes( xmlContent: string, target: GenerateTargetPlatform, ): string { - const widget = - parseWidgetXML( - xmlContent, - ); - let output = - generateTypeDefinition( - widget, - target, - ); + const widget = parseWidgetXML(xmlContent); + let output = generateTypeDefinition(widget, target); - output += - "\n" + - generatePreviewTypeDefinition( - widget, - ); + output += `\n${generatePreviewTypeDefinition(widget)}`; return output; } @@ -41,14 +30,7 @@ export async function generateTypesFromFile( filePath: string, target: GenerateTargetPlatform, ): Promise { - const xmlContent = - await readFile( - filePath, - "utf-8", - ); + const xmlContent = await readFile(filePath, "utf-8"); - return generateTypes( - xmlContent, - target, - ); + return generateTypes(xmlContent, target); } diff --git a/src/type-generator/mendix-types.ts b/src/type-generator/mendix-types.ts index 75f5ead..76ae1bc 100644 --- a/src/type-generator/mendix-types.ts +++ b/src/type-generator/mendix-types.ts @@ -1,334 +1,184 @@ -import type { - Property, - AttributeType, -} from "./types"; +import type { AttributeType, Property } from "./types"; export interface MendixTypeMapping { type: string; imports: Set; } -export type GenerateTargetPlatform = - | "web" - | "native"; +export type GenerateTargetPlatform = "web" | "native"; export function getMendixImports( properties: Property[], target: GenerateTargetPlatform, ): string[] { - const imports = - new Set(); + const imports = new Set(); for (const property of properties) { - const mapping = - mapPropertyToMendixType( - property, - target, - ); - - mapping.imports.forEach( - (imp) => - imports.add( - imp, - ), - ); + const mapping = mapPropertyToMendixType(property, target); + + mapping.imports.forEach((imp) => { + imports.add(imp); + }); } - return Array.from( - imports, - ).sort(); + return Array.from(imports).sort(); } export function mapPropertyToMendixType( property: Property, platform: GenerateTargetPlatform = "web", ): MendixTypeMapping { - const imports = - new Set(); + const imports = new Set(); let type: string; - switch ( - property.type - ) { + switch (property.type) { case "string": case "translatableString": - type = - "string"; + type = "string"; break; case "boolean": - type = - "boolean"; + type = "boolean"; break; case "integer": - type = - "number"; + type = "number"; break; case "decimal": - imports.add( - "Big", - ); + imports.add("Big"); type = "Big"; break; case "textTemplate": - imports.add( - "DynamicValue", - ); - if ( - property.dataSource - ) { - imports.add( - "ListExpressionValue", - ); - type = - "ListExpressionValue"; + imports.add("DynamicValue"); + if (property.dataSource) { + imports.add("ListExpressionValue"); + type = "ListExpressionValue"; } else { - type = - "DynamicValue"; + type = "DynamicValue"; } break; case "action": - if ( - property.dataSource - ) { - imports.add( - "ListActionValue", - ); - type = - "ListActionValue"; + if (property.dataSource) { + imports.add("ListActionValue"); + type = "ListActionValue"; } else { - imports.add( - "ActionValue", - ); - type = - "ActionValue"; + imports.add("ActionValue"); + type = "ActionValue"; } break; case "microflow": case "nanoflow": - imports.add( - "ActionValue", - ); - type = - "ActionValue"; + imports.add("ActionValue"); + type = "ActionValue"; break; case "attribute": - type = - mapAttributeToMendixType( - property, - imports, - ); + type = mapAttributeToMendixType(property, imports); break; case "expression": - type = - mapExpressionToMendixType( - property, - imports, - ); + type = mapExpressionToMendixType(property, imports); break; case "datasource": - imports.add( - "ListValue", - ); - type = - "ListValue"; + 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"; + 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"; + 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"; + 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"; + imports.add("WebImage"); + imports.add("NativeImage"); + type = "DynamicValue"; } break; case "file": - imports.add( - "DynamicValue", - ); - imports.add( - "FileValue", - ); - type = - "DynamicValue"; + imports.add("DynamicValue"); + imports.add("FileValue"); + type = "DynamicValue"; break; case "widgets": - if ( - property.dataSource - ) { - imports.add( - "ListWidgetValue", - ); - type = - "ListWidgetValue"; + if (property.dataSource) { + imports.add("ListWidgetValue"); + type = "ListWidgetValue"; } else { - imports.add( - "ReactNode", - ); - type = - "ReactNode"; + imports.add("ReactNode"); + type = "ReactNode"; } break; case "object": - if ( - property.properties && - property - .properties - .length > - 0 - ) { - type = - generateObjectInterface( - property, - ); + if (property.properties && property.properties.length > 0) { + type = generateObjectInterface(property); } else { - type = - "object"; + type = "object"; } break; case "entity": - imports.add( - "ObjectItem", - ); - type = - "ObjectItem"; + imports.add("ObjectItem"); + type = "ObjectItem"; break; case "entityConstraint": - type = - "string"; + type = "string"; break; case "enumeration": - if ( - property.enumerationValues && - property - .enumerationValues - .length > - 0 - ) { - type = - property.enumerationValues - .map( - ( - ev, - ) => - `"${ev.key}"`, - ) - .join( - " | ", - ); + if (property.enumerationValues && property.enumerationValues.length > 0) { + type = property.enumerationValues + .map((ev) => `"${ev.key}"`) + .join(" | "); } else { - type = - "string"; + type = "string"; } break; case "association": - type = - mapAssociationToMendixType( - property, - imports, - ); + type = mapAssociationToMendixType(property, imports); break; case "selection": - type = - mapSelectionToMendixType( - property, - imports, - ); + type = mapSelectionToMendixType(property, imports); break; case "form": - type = - "string"; + type = "string"; break; default: type = "any"; } - if ( - property.isList && - ![ - "datasource", - "widgets", - ].includes( - property.type, - ) - ) { + if (property.isList && !["datasource", "widgets"].includes(property.type)) { type = `${type}[]`; } @@ -342,34 +192,18 @@ function mapAttributeToMendixType( property: Property, imports: Set, ): string { - const baseType = - getAttributeBaseType( - property.attributeTypes || - [], - ); - - if ( - baseType.includes( - "Big", - ) - ) { - imports.add( - "Big", - ); + const baseType = getAttributeBaseType(property.attributeTypes || []); + + if (baseType.includes("Big")) { + imports.add("Big"); } - if ( - property.dataSource - ) { - imports.add( - "ListAttributeValue", - ); + if (property.dataSource) { + imports.add("ListAttributeValue"); return `ListAttributeValue<${baseType}>`; } else { - imports.add( - "EditableValue", - ); + imports.add("EditableValue"); return `EditableValue<${baseType}>`; } @@ -379,51 +213,24 @@ function mapExpressionToMendixType( property: Property, imports: Set, ): string { - const baseType = - property.returnType - ? mapReturnTypeToTS( - property - .returnType - .type, - ) - : "string"; - - if ( - baseType.includes( - "Big", - ) - ) { - imports.add( - "Big", - ); + const baseType = property.returnType + ? mapReturnTypeToTS(property.returnType.type) + : "string"; + + if (baseType.includes("Big")) { + imports.add("Big"); } - if ( - property.dataSource - ) { - imports.add( - "ListExpressionValue", - ); + if (property.dataSource) { + imports.add("ListExpressionValue"); - const typeStr = - property - .returnType - ?.isList - ? `${baseType}[]` - : baseType; + const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType; return `ListExpressionValue<${typeStr}>`; } else { - imports.add( - "DynamicValue", - ); + imports.add("DynamicValue"); - const typeStr = - property - .returnType - ?.isList - ? `${baseType}[]` - : baseType; + const typeStr = property.returnType?.isList ? `${baseType}[]` : baseType; return `DynamicValue<${typeStr}>`; } @@ -433,66 +240,37 @@ function mapAssociationToMendixType( property: Property, imports: Set, ): string { - if ( - !property.associationTypes || - property - .associationTypes - .length === 0 - ) { - imports.add( - "ObjectItem", - ); + 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", - ); + const assocType = property.associationTypes[0]; + + if (assocType === "Reference") { + if (property.dataSource) { + imports.add("ListReferenceValue"); return "ListReferenceValue"; } else { - imports.add( - "ReferenceValue", - ); + imports.add("ReferenceValue"); return "ReferenceValue"; } - } else if ( - assocType === - "ReferenceSet" - ) { - if ( - property.dataSource - ) { - imports.add( - "ListReferenceSetValue", - ); + } else if (assocType === "ReferenceSet") { + if (property.dataSource) { + imports.add("ListReferenceSetValue"); return "ListReferenceSetValue"; } else { - imports.add( - "ReferenceSetValue", - ); + imports.add("ReferenceSetValue"); return "ReferenceSetValue"; } } - imports.add( - "ObjectItem", - ); + imports.add("ObjectItem"); return "ObjectItem"; } @@ -501,100 +279,59 @@ function mapSelectionToMendixType( property: Property, imports: Set, ): string { - if ( - !property.selectionTypes || - property - .selectionTypes - .length === 0 - ) { - imports.add( - "SelectionSingleValue", - ); + if (!property.selectionTypes || property.selectionTypes.length === 0) { + imports.add("SelectionSingleValue"); return "SelectionSingleValue"; } - const selectionType = - property - .selectionTypes[0]; + const selectionType = property.selectionTypes[0]; - if ( - selectionType === - "Multi" - ) { - imports.add( - "SelectionMultiValue", - ); + if (selectionType === "Multi") { + imports.add("SelectionMultiValue"); return "SelectionMultiValue"; } else { - imports.add( - "SelectionSingleValue", - ); + 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 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 - ) { +function mapReturnTypeToTS(returnType: string): string { + switch (returnType) { case "Void": return "void"; case "Boolean": @@ -617,62 +354,30 @@ function mapReturnTypeToTS( } } -function generateObjectInterface( - property: Property, -): string { +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", - ); +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 - ) { + if (reactImports.length > 0) { output += `import { ${reactImports.join(", ")} } from 'react';\n`; } - if ( - mendixImports.length > - 0 - ) { + if (mendixImports.length > 0) { output += `import { ${mendixImports.join(", ")} } from 'mendix';\n`; } - if ( - bigJsImports.length > - 0 - ) { + if (bigJsImports.length > 0) { output += `import { ${bigJsImports.join(", ")} } from 'big.js';\n`; } diff --git a/src/type-generator/parser.ts b/src/type-generator/parser.ts index b79e002..5e2f2e7 100644 --- a/src/type-generator/parser.ts +++ b/src/type-generator/parser.ts @@ -1,388 +1,205 @@ import { XMLParser } from "fast-xml-parser"; import type { - WidgetDefinition, - Property, - PropertyGroup, - SystemProperty, - AttributeType, AssociationType, - SelectionType, + AttributeType, EnumerationValue, - ParsedXMLWidget, + ParsedXMLAssociationType, + ParsedXMLAttributeType, + ParsedXMLEnumerationValue, ParsedXMLProperty, ParsedXMLPropertyGroup, - ParsedXMLSystemProperty, - ParsedXMLAttributeType, - ParsedXMLAssociationType, ParsedXMLSelectionType, - ParsedXMLEnumerationValue, + 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, +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: [], }; -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, - ); + 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, - ), - ); +function parseProperties(props): PropertyGroup[] | Property[] { + if (props.propertyGroup) { + const groups = ensureArray(props.propertyGroup); + + return groups.map((group) => parsePropertyGroup(group)); } - const properties: Property[] = - []; + const properties: Property[] = []; - if ( - props.property - ) { - const propsArray = - ensureArray( - props.property, - ); + if (props.property) { + const propsArray = ensureArray(props.property); for (const prop of propsArray) { - properties.push( - parseProperty( - prop, - ), - ); + properties.push(parseProperty(prop)); } } return properties; } -function parsePropertyGroup( - group: ParsedXMLPropertyGroup, -): PropertyGroup { - const properties: ( - | Property - | SystemProperty - )[] = []; - - if ( - group.property - ) { - const props = - ensureArray( - group.property, - ); +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, - ), - ); + properties.push(parseProperty(prop)); } } - if ( - group.systemProperty - ) { - const sysProps = - ensureArray( - group.systemProperty, - ); + if (group.systemProperty) { + const sysProps = ensureArray(group.systemProperty); for (const sysProp of sysProps) { - properties.push( - parseSystemProperty( - sysProp, - ), - ); + properties.push(parseSystemProperty(sysProp)); } } return { - caption: - group.caption, + 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", - }; +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.defaultValue !== undefined) { + property.defaultValue = prop.defaultValue; } - if ( - prop.onChange - ) { - property.onChange = - prop.onChange; + if (prop.onChange) { + property.onChange = prop.onChange; } - if ( - prop.dataSource - ) { - property.dataSource = - prop.dataSource; + if (prop.dataSource) { + property.dataSource = prop.dataSource; } - if ( - prop.attributeTypes - ) { - property.attributeTypes = - parseAttributeTypes( - prop.attributeTypes, - ); + if (prop.attributeTypes) { + property.attributeTypes = parseAttributeTypes(prop.attributeTypes); } - if ( - prop.associationTypes - ) { - property.associationTypes = - parseAssociationTypes( - prop.associationTypes, - ); + if (prop.associationTypes) { + property.associationTypes = parseAssociationTypes(prop.associationTypes); } - if ( - prop.selectionTypes - ) { - property.selectionTypes = - parseSelectionTypes( - prop.selectionTypes, - ); + if (prop.selectionTypes) { + property.selectionTypes = parseSelectionTypes(prop.selectionTypes); } - if ( - prop.enumerationValues - ) { - property.enumerationValues = - parseEnumerationValues( - prop.enumerationValues, - ); + 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.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", - }; + 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, - }; +function parseSystemProperty(sysProp: ParsedXMLSystemProperty): SystemProperty { + const systemProperty: SystemProperty = { + key: sysProp.key, + }; - if ( - sysProp.category - ) { - systemProperty.category = - sysProp.category; + if (sysProp.category) { + systemProperty.category = sysProp.category; } return systemProperty; } function parseAttributeTypes(attributeTypes: { - attributeType: - | ParsedXMLAttributeType - | ParsedXMLAttributeType[]; + attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[]; }): AttributeType[] { - const types = - ensureArray( - attributeTypes.attributeType, - ); - - return types.map( - (type) => - type.name, - ); + const types = ensureArray(attributeTypes.attributeType); + + return types.map((type) => type.name); } function parseAssociationTypes(associationTypes: { - associationType: - | ParsedXMLAssociationType - | ParsedXMLAssociationType[]; + associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[]; }): AssociationType[] { - const types = - ensureArray( - associationTypes.associationType, - ); - - return types.map( - (type) => - type.name, - ); + const types = ensureArray(associationTypes.associationType); + + return types.map((type) => type.name); } function parseSelectionTypes(selectionTypes: { - selectionType: - | ParsedXMLSelectionType - | ParsedXMLSelectionType[]; + selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[]; }): SelectionType[] { - const types = - ensureArray( - selectionTypes.selectionType, - ); - - return types.map( - (type) => - type.name, - ); + const types = ensureArray(selectionTypes.selectionType); + + return types.map((type) => type.name); } function parseEnumerationValues(enumerationValues: { - enumerationValue: - | ParsedXMLEnumerationValue - | ParsedXMLEnumerationValue[]; + enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[]; }): EnumerationValue[] { - const values = - ensureArray( - enumerationValues.enumerationValue, - ); - - return values.map( - (value) => ({ - key: value.key, - value: - value._ || - value.key, - }), - ); + 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 1ea9634..593b39f 100644 --- a/src/type-generator/preview-types.ts +++ b/src/type-generator/preview-types.ts @@ -1,87 +1,49 @@ +import { + extractSystemProperties, + generatePreviewSystemProps, + hasLabelProperty, +} from "./system-props"; import type { - WidgetDefinition, Property, PropertyGroup, SystemProperty, + WidgetDefinition, } from "./types"; -import { - pascalCase, - sanitizePropertyKey, - formatDescription, -} from "./utils"; -import { - extractSystemProperties, - hasLabelProperty, - generatePreviewSystemProps, -} from "./system-props"; +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[]; + 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 += 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, - ); + 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"; + output += ` ${line}\n`; } for (const property of widgetProperties) { - output += - generatePreviewPropertyDefinition( - property, - ); + output += generatePreviewPropertyDefinition(property); } output += "}\n"; @@ -90,243 +52,150 @@ export function generatePreviewTypeDefinition( } function generatePreviewImports(): string { - const imports: string[] = - []; + const imports: string[] = []; - imports.push( - "CSSProperties", - ); - imports.push( - "PreviewValue", - ); + imports.push("CSSProperties"); + imports.push("PreviewValue"); let output = ""; - if ( - imports.length > - 0 - ) { + if (imports.length > 0) { output += `import type { ${imports.join(", ")} } from 'react';\n\n`; } return output; } -function generatePreviewJSDoc( - widget: WidgetDefinition, -): string { - let jsDoc = - "/**\n"; +function generatePreviewJSDoc(widget: WidgetDefinition): string { + let jsDoc = "/**\n"; jsDoc += ` * Preview props for ${widget.name}\n`; - if ( - widget.description - ) { + if (widget.description) { jsDoc += ` * ${formatDescription(widget.description)}\n`; } - jsDoc += - " * @preview This interface is used in design mode\n"; + jsDoc += " * @preview This interface is used in design mode\n"; jsDoc += " */\n"; return jsDoc; } -function generatePreviewPropertyDefinition( - property: Property, -): string { - const indent = - " "; +function generatePreviewPropertyDefinition(property: Property): string { + const indent = " "; let output = ""; - if ( - property.description - ) { + if (property.description) { output += `${indent}/**\n`; output += `${indent} * ${formatDescription(property.description)}\n`; - if ( - property.caption && - property.caption !== - property.description - ) { + 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, - ); + 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; +function mapPropertyToPreviewType(property: Property): string { + const { type, isList, enumerationValues } = property; let baseType: string; switch (type) { case "string": case "translatableString": - baseType = - "string"; + baseType = "string"; break; case "boolean": - baseType = - "boolean"; + baseType = "boolean"; break; case "integer": case "decimal": - baseType = - "number"; + baseType = "number"; break; case "action": case "microflow": case "nanoflow": - baseType = - "{} | null"; + baseType = "{} | null"; break; case "attribute": case "expression": case "entityConstraint": - baseType = - "string"; + baseType = "string"; break; case "textTemplate": - baseType = - "string"; + baseType = "string"; break; case "datasource": - baseType = - "{ type: string } | { caption: string } | {}"; + baseType = "{ type: string } | { caption: string } | {}"; break; case "icon": case "image": case "file": - baseType = - "{ uri: string } | null"; + baseType = "{ uri: string } | null"; break; case "widgets": - baseType = - "PreviewValue | null"; + baseType = "PreviewValue | null"; break; case "enumeration": - if ( - enumerationValues && - enumerationValues.length > - 0 - ) { - baseType = - enumerationValues - .map( - ( - ev, - ) => - `"${ev.key}"`, - ) - .join( - " | ", - ); + if (enumerationValues && enumerationValues.length > 0) { + baseType = enumerationValues.map((ev) => `"${ev.key}"`).join(" | "); } else { - baseType = - "string"; + baseType = "string"; } break; case "object": - if ( - property.properties && - property - .properties - .length > - 0 - ) { + if (property.properties && property.properties.length > 0) { baseType = `${pascalCase(property.key)}PreviewType`; } else { - baseType = - "object"; + baseType = "object"; } break; case "entity": case "association": case "selection": - baseType = - "string"; + baseType = "string"; break; case "form": - baseType = - "string"; + baseType = "string"; break; default: - baseType = - "any"; + baseType = "any"; } - return isList && - type !== - "datasource" - ? `${baseType}[]` - : baseType; + return isList && type !== "datasource" ? `${baseType}[]` : baseType; } function extractAllProperties( - properties: - | PropertyGroup[] - | Property[], -): ( - | Property - | SystemProperty -)[] { - const result: ( - | Property - | SystemProperty - )[] = []; + properties: PropertyGroup[] | Property[], +): (Property | SystemProperty)[] { + const result: (Property | SystemProperty)[] = []; for (const item of properties) { - if ( - isPropertyGroup( - item, - ) - ) { - result.push( - ...item.properties, - ); + if (isPropertyGroup(item)) { + result.push(...item.properties); } else { - result.push( - item, - ); + result.push(item); } } @@ -334,28 +203,13 @@ function extractAllProperties( } function isPropertyGroup( - item: - | PropertyGroup - | Property - | SystemProperty, + item: PropertyGroup | Property | SystemProperty, ): item is PropertyGroup { - return ( - "caption" in - item && - "properties" in - item - ); + return "caption" in item && "properties" in item; } function isSystemProperty( - item: - | Property - | SystemProperty, + item: Property | SystemProperty, ): item is SystemProperty { - return ( - !( - "type" in item - ) && - "key" in item - ); + return !("type" in item) && "key" in item; } diff --git a/src/type-generator/system-props.ts b/src/type-generator/system-props.ts index 87a3e38..0c15689 100644 --- a/src/type-generator/system-props.ts +++ b/src/type-generator/system-props.ts @@ -1,9 +1,5 @@ -import { GenerateTargetPlatform } from "./mendix-types"; -import type { - SystemProperty, - Property, - PropertyGroup, -} from "./types"; +import type { GenerateTargetPlatform } from "./mendix-types"; +import type { Property, PropertyGroup, SystemProperty } from "./types"; export interface SystemPropsConfig { hasLabel?: boolean; @@ -11,93 +7,45 @@ export interface SystemPropsConfig { } export function extractSystemProperties( - properties: - | PropertyGroup[] - | Property[] - | ( - | Property - | SystemProperty - )[], + properties: PropertyGroup[] | Property[] | (Property | SystemProperty)[], ): SystemProperty[] { - const systemProps: SystemProperty[] = - []; + const systemProps: SystemProperty[] = []; for (const item of properties) { - if ( - isPropertyGroup( - item, - ) - ) { + if (isPropertyGroup(item)) { for (const prop of item.properties) { - if ( - isSystemProperty( - prop, - ) - ) { - systemProps.push( - prop, - ); + if (isSystemProperty(prop)) { + systemProps.push(prop); } } - } else if ( - isSystemProperty( - item, - ) - ) { - systemProps.push( - item, - ); + } else if (isSystemProperty(item)) { + systemProps.push(item); } } return systemProps; } -export function hasLabelProperty( - systemProperties: SystemProperty[], -): boolean { - return systemProperties.some( - (prop) => - prop.key === - "Label", - ); +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" - ) { +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("class?: string;"); + props.push("style?: CSSProperties;"); } - props.push( - "tabIndex?: number;", - ); + props.push("tabIndex?: number;"); } if (hasLabel) { - props.push( - "id?: string;", - ); + props.push("id?: string;"); } return props; @@ -106,80 +54,40 @@ export function generateSystemProps( export function getSystemPropsImports( config: SystemPropsConfig = {}, ): string[] { - const { - platform = "web", - } = config; - const imports: string[] = - []; - - if ( - platform !== - "native" - ) { - imports.push( - "CSSProperties", - ); + const { platform = "web" } = config; + const imports: string[] = []; + + if (platform !== "native") { + imports.push("CSSProperties"); } return imports; } -export function generatePreviewSystemProps( - hasLabel: boolean, -): string[] { - const props: string[] = - []; +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;", - ); + 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: PropertyGroup | Property | SystemProperty, ): item is PropertyGroup { - return ( - "caption" in - item && - "properties" in - item - ); + return "caption" in item && "properties" in item; } function isSystemProperty( - item: - | Property - | SystemProperty, + item: Property | SystemProperty, ): item is SystemProperty { - return ( - !( - "type" in item - ) && - "key" in item - ); + return !("type" in item) && "key" in item; } diff --git a/src/type-generator/types.ts b/src/type-generator/types.ts index c8ed077..a21d07b 100644 --- a/src/type-generator/types.ts +++ b/src/type-generator/types.ts @@ -5,21 +5,13 @@ export interface WidgetDefinition { needsEntityContext?: boolean; pluginWidget?: boolean; offlineCapable?: boolean; - supportedPlatform?: - | "All" - | "Native" - | "Web"; - properties: - | PropertyGroup[] - | Property[]; + supportedPlatform?: "All" | "Native" | "Web"; + properties: PropertyGroup[] | Property[]; } export interface PropertyGroup { caption: string; - properties: ( - | Property - | SystemProperty - )[]; + properties: (Property | SystemProperty)[]; } export interface Property { @@ -84,14 +76,9 @@ export type AttributeType = | "String" | "Decimal"; -export type AssociationType = - | "Reference" - | "ReferenceSet"; +export type AssociationType = "Reference" | "ReferenceSet"; -export type SelectionType = - | "None" - | "Single" - | "Multi"; +export type SelectionType = "None" | "Single" | "Multi"; export type SystemPropertyKey = | "Label" @@ -132,25 +119,15 @@ export interface ParsedXMLWidget { } export interface ParsedXMLProperties { - property?: - | ParsedXMLProperty - | ParsedXMLProperty[]; - propertyGroup?: - | ParsedXMLPropertyGroup - | ParsedXMLPropertyGroup[]; - systemProperty?: - | ParsedXMLSystemProperty - | ParsedXMLSystemProperty[]; + property?: ParsedXMLProperty | ParsedXMLProperty[]; + propertyGroup?: ParsedXMLPropertyGroup | ParsedXMLPropertyGroup[]; + systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[]; } export interface ParsedXMLPropertyGroup { caption: string; - property?: - | ParsedXMLProperty - | ParsedXMLProperty[]; - systemProperty?: - | ParsedXMLSystemProperty - | ParsedXMLSystemProperty[]; + property?: ParsedXMLProperty | ParsedXMLProperty[]; + systemProperty?: ParsedXMLSystemProperty | ParsedXMLSystemProperty[]; } export interface ParsedXMLProperty { @@ -164,24 +141,16 @@ export interface ParsedXMLProperty { caption: string; description: string; attributeTypes?: { - attributeType: - | ParsedXMLAttributeType - | ParsedXMLAttributeType[]; + attributeType: ParsedXMLAttributeType | ParsedXMLAttributeType[]; }; associationTypes?: { - associationType: - | ParsedXMLAssociationType - | ParsedXMLAssociationType[]; + associationType: ParsedXMLAssociationType | ParsedXMLAssociationType[]; }; selectionTypes?: { - selectionType: - | ParsedXMLSelectionType - | ParsedXMLSelectionType[]; + selectionType: ParsedXMLSelectionType | ParsedXMLSelectionType[]; }; enumerationValues?: { - enumerationValue: - | ParsedXMLEnumerationValue - | ParsedXMLEnumerationValue[]; + enumerationValue: ParsedXMLEnumerationValue | ParsedXMLEnumerationValue[]; }; properties?: ParsedXMLProperties; returnType?: { diff --git a/src/type-generator/utils.ts b/src/type-generator/utils.ts index f75cc70..59d5f6f 100644 --- a/src/type-generator/utils.ts +++ b/src/type-generator/utils.ts @@ -1,29 +1,18 @@ -import type { - AttributeType, - Property, -} from "./types"; 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, - ); + const mapping = mapPropertyToMendixType(property, target); return mapping.type; } -export function mapAttributeTypeToTS( - attributeType: AttributeType, -): string { - switch ( - attributeType - ) { +export function mapAttributeTypeToTS(attributeType: AttributeType): string { + switch (attributeType) { case "String": case "HashString": case "Enum": @@ -50,12 +39,8 @@ export function mapAttributeTypeToTS( } } -export function mapReturnTypeToTS( - returnType: string, -): string { - switch ( - returnType - ) { +export function mapReturnTypeToTS(returnType: string): string { + switch (returnType) { case "Void": return "void"; case "Boolean": @@ -78,68 +63,30 @@ export function mapReturnTypeToTS( } } -export function ensureArray< - T, ->( - value: - | T - | T[] - | undefined, -): T[] { - if (!value) - return []; - return Array.isArray( - value, - ) - ? value - : [value]; +export function ensureArray(value: T | T[] | undefined): T[] { + if (!value) return []; + return Array.isArray(value) ? value : [value]; } -export function pascalCase( - str: string, -): string { +export function pascalCase(str: string): string { return str - .split( - /[-_\s]+/, - ) - .map( - (word) => - word - .charAt(0) - .toUpperCase() + - word - .slice(1) - .toLowerCase(), - ) + .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, - ) - ) { +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 { +export function formatDescription(description: string): string { return description .trim() .split("\n") - .map((line) => - line.trim(), - ) - .filter( - (line) => - line.length > - 0, - ) + .map((line) => line.trim()) + .filter((line) => line.length > 0) .join(" "); } diff --git a/src/types.d.ts b/src/types.d.ts index 2b89065..e8a8653 100644 --- a/src/types.d.ts +++ b/src/types.d.ts @@ -1,15 +1,13 @@ import { PackageJson } from "type-fest"; -type WidgetConfig = - { - projectPath: string; - mendixHost: string; - developmentPort: number; - }; +type WidgetConfig = { + projectPath: string; + mendixHost: string; + developmentPort: number; +}; -export type WidgetPackageJson = - PackageJson & { - widgetName: string; - config: WidgetConfig; - packagePath: string; - }; +export type WidgetPackageJson = PackageJson & { + widgetName: string; + config: WidgetConfig; + packagePath: string; +}; diff --git a/src/utils/getMendixProjectDirectory.ts b/src/utils/getMendixProjectDirectory.ts index e96df5d..3fa3576 100644 --- a/src/utils/getMendixProjectDirectory.ts +++ b/src/utils/getMendixProjectDirectory.ts @@ -1,21 +1,13 @@ -import path from "path"; +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; +const getMendixProjectDiectory = async () => { + const packageJson = await getWidgetPackageJson(); + const widgetPath = PROJECT_DIRECTORY; - return path.join( - widgetPath, - packageJson - .config - .projectPath, - ); - }; + return path.join(widgetPath, packageJson.config.projectPath); +}; export default getMendixProjectDiectory; diff --git a/src/utils/getMendixWidgetDirectory.ts b/src/utils/getMendixWidgetDirectory.ts index 8299e26..ec8a742 100644 --- a/src/utils/getMendixWidgetDirectory.ts +++ b/src/utils/getMendixWidgetDirectory.ts @@ -1,16 +1,11 @@ -import path from "path"; +import path from "node:path"; import getMendixProjectDiectory from "./getMendixProjectDirectory"; -const getMendixWidgetDirectory = - async () => { - const mendixPath = - await getMendixProjectDiectory(); +const getMendixWidgetDirectory = async () => { + const mendixPath = await getMendixProjectDiectory(); - return path.join( - mendixPath, - "widgets", - ); - }; + return path.join(mendixPath, "widgets"); +}; export default getMendixWidgetDirectory; diff --git a/src/utils/getViteOutputDirectory.ts b/src/utils/getViteOutputDirectory.ts index 3cfac12..ef4c41e 100644 --- a/src/utils/getViteOutputDirectory.ts +++ b/src/utils/getViteOutputDirectory.ts @@ -1,32 +1,21 @@ -import path from "path"; +import path from "node:path"; -import { - DIST_DIRECTORY_NAME, - PROJECT_DIRECTORY, -} from "../constants"; +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(), - ); +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; - }; + return outputDir; +}; export default getViteOutputDirectory; diff --git a/src/utils/getViteUserConfiguration.ts b/src/utils/getViteUserConfiguration.ts index b157cf7..07df724 100644 --- a/src/utils/getViteUserConfiguration.ts +++ b/src/utils/getViteUserConfiguration.ts @@ -1,35 +1,18 @@ -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 userConfig = - getUserConfigFn(); +const getViteUserConfiguration = async (path: string): Promise => { + const getUserConfig = await import(`file://${path}`); + const getUserConfigFn: PWTConfigFn | PWTConfigFnPromise = + getUserConfig.default; + const userConfig = getUserConfigFn(); - if ( - userConfig instanceof - Promise - ) { - const userConfigValue = - await userConfig; + if (userConfig instanceof Promise) { + const userConfigValue = await userConfig; - return userConfigValue; - } + return userConfigValue; + } - return userConfig; - }; + return userConfig; +}; export default getViteUserConfiguration; diff --git a/src/utils/getViteWatchOutputDirectory.ts b/src/utils/getViteWatchOutputDirectory.ts index f6effe5..03ab31f 100644 --- a/src/utils/getViteWatchOutputDirectory.ts +++ b/src/utils/getViteWatchOutputDirectory.ts @@ -1,35 +1,22 @@ -import path from "path"; +import path from "node:path"; -import { - DIST_DIRECTORY_NAME, - PROJECT_DIRECTORY, -} from "../constants"; +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(), - ); +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; - }; + return outputDir; +}; export default getViteWatchOutputDirectory; diff --git a/src/utils/getWidgetName.ts b/src/utils/getWidgetName.ts index 05ad9a0..77cae90 100644 --- a/src/utils/getWidgetName.ts +++ b/src/utils/getWidgetName.ts @@ -1,19 +1,15 @@ import getWidgetPackageJson from "./getWidgetPackageJson"; -const getWidgetName = - async (): Promise => { - const widgetPackageJson = - await 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.", - ); - } + if (!widgetPackageJson.widgetName) { + throw new Error( + "widget name is missing, please check your package.json file.", + ); + } - return widgetPackageJson.widgetName; - }; + return widgetPackageJson.widgetName; +}; export default getWidgetName; diff --git a/src/utils/getWidgetPackageJson.ts b/src/utils/getWidgetPackageJson.ts index b7d9571..41a7e99 100644 --- a/src/utils/getWidgetPackageJson.ts +++ b/src/utils/getWidgetPackageJson.ts @@ -1,33 +1,19 @@ -import fs from "fs/promises"; -import path from "path"; +import fs from "node:fs/promises"; +import path from "node:path"; import { PROJECT_DIRECTORY } from "../constants"; -import { WidgetPackageJson } from "../types"; +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, - ); +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.", - ); - } - }; + 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 a490843..d971209 100644 --- a/src/utils/getWidgetVersion.ts +++ b/src/utils/getWidgetVersion.ts @@ -1,19 +1,15 @@ import getWidgetPackageJson from "./getWidgetPackageJson"; -const getWidgetVersion = - async (): Promise => { - const widgetPackageJson = - await 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.", - ); - } + if (!widgetPackageJson.version) { + throw new Error( + "widget version is missing, please check your package.json file.", + ); + } - return widgetPackageJson.version; - }; + return widgetPackageJson.version; +}; export default getWidgetVersion; diff --git a/src/utils/pathIsExists.ts b/src/utils/pathIsExists.ts index 31f63e1..8e33753 100644 --- a/src/utils/pathIsExists.ts +++ b/src/utils/pathIsExists.ts @@ -1,20 +1,13 @@ -import fs from "fs/promises"; +import fs from "node:fs/promises"; -const pathIsExists = - async ( - directoryPath: string, - ): Promise => { - try { - await fs.access( - directoryPath, - fs.constants - .F_OK, - ); +const pathIsExists = async (directoryPath: string): Promise => { + try { + await fs.access(directoryPath, fs.constants.F_OK); - return true; - } catch (error) { - return false; - } - }; + return true; + } catch (_error) { + return false; + } +}; export default pathIsExists; diff --git a/src/utils/showMessage.ts b/src/utils/showMessage.ts index ef1ac55..85f7d64 100644 --- a/src/utils/showMessage.ts +++ b/src/utils/showMessage.ts @@ -1,12 +1,7 @@ import { COLOR_NAME } from "../constants"; -const showMessage = - ( - message: string, - ) => { - console.log( - `${COLOR_NAME("[hyper-pwt]")} ${message}`, - ); - }; +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 b0f4417..5b25f80 100644 --- a/tools/copy-widget-schema.js +++ b/tools/copy-widget-schema.js @@ -1,48 +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, - ); + 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); } diff --git a/tsconfig.json b/tsconfig.json index c80bed3..6a77df9 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,16 +1,10 @@ { - "include": [ - "src/**/*" - ], - "exclude": [ - "node_modules" - ], + "include": ["src/**/*"], + "exclude": ["node_modules"], "compilerOptions": { "resolveJsonModule": true, "esModuleInterop": true, - "lib": [ - "ESNext" - ], + "lib": ["ESNext"], "module": "preserve", "moduleResolution": "bundler", "target": "esnext"