From 21f8d30188f5a1e0577d343ede7bfdaa06f5e6e0 Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Thu, 16 Apr 2026 15:58:16 +0200 Subject: [PATCH 1/4] chore: add release info code and cli tool to get releasable widgets/modules --- automation/utils/bin/rui-release-info.ts | 67 ++ automation/utils/package.json | 1 + automation/utils/src/index.ts | 2 + automation/utils/src/io/filesystem.ts | 43 ++ automation/utils/src/release-candidates.ts | 296 +++++++++ docs/release-process/release-info-tool.md | 166 +++++ docs/release-process/release-workflow.md | 732 +++++++++++++++++++++ 7 files changed, 1307 insertions(+) create mode 100755 automation/utils/bin/rui-release-info.ts create mode 100644 automation/utils/src/io/filesystem.ts create mode 100644 automation/utils/src/release-candidates.ts create mode 100644 docs/release-process/release-info-tool.md create mode 100644 docs/release-process/release-workflow.md diff --git a/automation/utils/bin/rui-release-info.ts b/automation/utils/bin/rui-release-info.ts new file mode 100755 index 0000000000..e9f604be94 --- /dev/null +++ b/automation/utils/bin/rui-release-info.ts @@ -0,0 +1,67 @@ +#!/usr/bin/env ts-node +import { loadReleaseCandidates, loadAllPackages } from "../src/release-candidates"; + +async function main(): Promise { + const args = process.argv.slice(2); + const command = args[0]; + + try { + if (command === "--candidates" || command === "-c") { + // List only packages with unreleased changes + const candidates = await loadReleaseCandidates(); + console.log(JSON.stringify(candidates, null, 2)); + } else if (command === "--all" || command === "-a") { + // List all packages (including those without changes) + const allPackages = await loadAllPackages(); + console.log(JSON.stringify(allPackages, null, 2)); + } else if (command === "--summary" || command === "-s") { + // Summary view + const candidates = await loadReleaseCandidates(); + const summary = { + totalCandidates: candidates.length, + widgets: candidates.filter(c => c.packageType === "widget").length, + modules: candidates.filter(c => c.packageType === "module").length, + packages: candidates.map(c => ({ + name: c.name, + packageType: c.packageType, + hasDependencies: c.hasDependencies, + version: c.currentVersion, + hasChanges: c.hasUnreleasedChanges, + dependentWidgetsWithChanges: c.hasDependencies + ? c.dependentWidgets!.filter(w => w.hasUnreleasedChanges).length + : undefined + })) + }; + console.log(JSON.stringify(summary, null, 2)); + } else if (command === "--help" || command === "-h" || !command) { + printHelp(); + } else { + console.error(`Unknown command: ${command}`); + console.error("Use --help for usage information"); + process.exit(1); + } + } catch (error) { + console.error("Error:", error instanceof Error ? error.message : String(error)); + process.exit(1); + } +} + +function printHelp(): void { + console.log(` +rui-release-info - Query release candidates in the monorepo + +Commands: + -c, --candidates List packages with unreleased changes (release candidates) + Returns detailed JSON with changelog entries + + -a, --all List all packages (including those without changes) + Useful for seeing complete package structure + + -s, --summary Show summary statistics and package list + Concise view with counts and basic info + + -h, --help Show this help message +`); +} + +main(); diff --git a/automation/utils/package.json b/automation/utils/package.json index 5f0945a7e3..475646d5a0 100644 --- a/automation/utils/package.json +++ b/automation/utils/package.json @@ -11,6 +11,7 @@ "rui-include-oss-in-artifact": "bin/rui-include-oss-in-artifact.ts", "rui-prepare-release": "bin/rui-prepare-release.ts", "rui-publish-marketplace": "bin/rui-publish-marketplace.ts", + "rui-release-info": "bin/rui-release-info.ts", "rui-update-changelog-module": "bin/rui-update-changelog-module.ts", "rui-update-changelog-widget": "bin/rui-update-changelog-widget.ts", "rui-verify-package-format": "bin/rui-verify-package-format.ts" diff --git a/automation/utils/src/index.ts b/automation/utils/src/index.ts index 8e405bb4e8..a8cc059505 100644 --- a/automation/utils/src/index.ts +++ b/automation/utils/src/index.ts @@ -6,3 +6,5 @@ export * from "./mpk"; export * from "./changelog-parser"; export * from "./monorepo"; export * from "./build-config"; +export * from "./release-candidates"; +export * from "./io/filesystem"; diff --git a/automation/utils/src/io/filesystem.ts b/automation/utils/src/io/filesystem.ts new file mode 100644 index 0000000000..2e504e2929 --- /dev/null +++ b/automation/utils/src/io/filesystem.ts @@ -0,0 +1,43 @@ +import { access, readdir, readFile, writeFile } from "fs/promises"; + +/** + * Abstraction layer for filesystem operations + * Allows for testing and alternative implementations (e.g., MCP, in-memory) + */ +export interface FileSystem { + readFile(path: string): Promise; + writeFile(path: string, content: string): Promise; + exists(path: string): Promise; + readdir(path: string): Promise; +} + +/** + * Default Node.js filesystem implementation + */ +export class NodeFileSystem implements FileSystem { + async readFile(path: string): Promise { + return readFile(path, "utf-8"); + } + + async writeFile(path: string, content: string): Promise { + await writeFile(path, content, "utf-8"); + } + + async exists(path: string): Promise { + try { + await access(path); + return true; + } catch { + return false; + } + } + + async readdir(path: string): Promise { + return readdir(path); + } +} + +/** + * Default filesystem instance for convenience + */ +export const defaultFS = new NodeFileSystem(); diff --git a/automation/utils/src/release-candidates.ts b/automation/utils/src/release-candidates.ts new file mode 100644 index 0000000000..761a5a0a9b --- /dev/null +++ b/automation/utils/src/release-candidates.ts @@ -0,0 +1,296 @@ +import { join } from "path"; +import { + getWidgetChangelog, + getModuleChangelog, + WidgetChangelogFileWrapper, + ModuleChangelogFileWrapper +} from "./changelog-parser"; +import { FileSystem, defaultFS } from "./io/filesystem"; +import { getPackageInfo, getWidgetInfo, getModuleInfo, PackageInfo } from "./package-info"; + +/** + * Represents a single changelog entry + */ +export interface ChangelogEntry { + type: "Fixed" | "Added" | "Changed" | "Removed"; + description: string; +} + +/** + * Release information for a dependent widget + */ +export interface DependentWidgetInfo { + name: string; + path: string; + currentVersion: string; + appName: string; + hasUnreleasedChanges: boolean; + unreleasedEntries: ChangelogEntry[]; +} + +/** + * Release candidate - represents any package that can be released + */ +export interface ReleaseCandidate { + packageType: "widget" | "module"; + hasDependencies: boolean; + name: string; + path: string; + currentVersion: string; + appNumber: number; + appName: string; + hasUnreleasedChanges: boolean; + unreleasedEntries: ChangelogEntry[]; + dependentWidgets?: DependentWidgetInfo[]; // Only present if hasDependencies is true +} + +/** + * Check if a package is an aggregator (has dependencies and appNumber) + */ +function isAggregator(info: PackageInfo): boolean { + return !!info.marketplace.appNumber && info.mxpackage.dependencies && info.mxpackage.dependencies.length > 0; +} + +/** + * Check if a package is independent widget (has appNumber, no dependencies, is a widget) + */ +function isIndependentWidget(info: PackageInfo): boolean { + return ( + !!info.marketplace.appNumber && + (!info.mxpackage.dependencies || info.mxpackage.dependencies.length === 0) && + info.mxpackage.type === "widget" + ); +} + +/** + * Check if a package is a standalone module (has appNumber, no dependencies, is a module) + * Example: web-actions (just JavaScript actions, no widgets) + */ +function isStandaloneModule(info: PackageInfo): boolean { + return ( + !!info.marketplace.appNumber && + (!info.mxpackage.dependencies || info.mxpackage.dependencies.length === 0) && + info.mxpackage.type === "module" + ); +} + +/** + * Extract simple changelog entries from changelog wrapper + */ +export function extractChangelogEntries( + changelog: WidgetChangelogFileWrapper | ModuleChangelogFileWrapper +): ChangelogEntry[] { + const unreleased = changelog.changelog.content[0]; + return unreleased.sections.flatMap(section => + section.logs.map(log => ({ + type: section.type, + description: log + })) + ); +} + +/** + * Load dependent widget info + * Note: Some "widgets" might actually be other types (e.g., jsactions) + * We need to check the package type first + */ +async function loadDependentWidgetInfo(path: string): Promise { + const basicInfo = await getPackageInfo(path); + + // Only process actual widgets + if (basicInfo.mxpackage.type !== "widget") { + throw new Error(`Package ${basicInfo.name} is not a widget (type: ${basicInfo.mxpackage.type})`); + } + + const info = await getWidgetInfo(path); + const changelog = await getWidgetChangelog(path); + const hasUnreleasedChanges = changelog.hasUnreleasedLogs(); + const unreleasedEntries = hasUnreleasedChanges ? extractChangelogEntries(changelog) : []; + + return { + name: info.name, + path, + currentVersion: info.version.format(), + appName: info.marketplace.appName ?? info.mxpackage.name, + hasUnreleasedChanges, + unreleasedEntries + }; +} + +/** + * Scan a directory for packages + */ +async function scanPackagesDirectory(dirPath: string, fs: FileSystem = defaultFS): Promise { + try { + const exists = await fs.exists(dirPath); + if (!exists) return []; + + const entries = await fs.readdir(dirPath); + const packagePaths: string[] = []; + + for (const entry of entries) { + const fullPath = join(dirPath, entry); + const packageJsonPath = join(fullPath, "package.json"); + const hasPackageJson = await fs.exists(packageJsonPath); + + if (hasPackageJson) { + packagePaths.push(fullPath); + } + } + + return packagePaths; + } catch (error) { + console.warn(`Warning: Could not scan directory ${dirPath}:`, error); + return []; + } +} + +/** + * Load release candidates (packages with unreleased changes) + * @param rootPath - Root path of the monorepo (defaults to cwd) + * @param fs - Filesystem implementation (defaults to Node.js fs) + * @returns Array of release candidates that have unreleased changes + */ +export async function loadReleaseCandidates( + rootPath: string = process.cwd(), + fs: FileSystem = defaultFS +): Promise { + // Load all packages first + const allPackages = await loadAllPackages(rootPath, fs); + + // Filter to only those with unreleased changes + return allPackages.filter(pkg => { + if (pkg.hasDependencies) { + // For packages with dependencies, check if package itself OR any dependent has changes + return pkg.hasUnreleasedChanges || pkg.dependentWidgets!.some(w => w.hasUnreleasedChanges); + } else { + // For independent packages, just check the package itself + return pkg.hasUnreleasedChanges; + } + }); +} + +/** + * Load all packages (including those without unreleased changes) + * Loads all releasable packages from the monorepo + * @param rootPath - Root path of the monorepo (defaults to cwd) + * @param fs - Filesystem implementation (defaults to Node.js fs) + * @returns Array of all release candidates + */ +export async function loadAllPackages( + rootPath: string = process.cwd(), + fs: FileSystem = defaultFS +): Promise { + const candidates: ReleaseCandidate[] = []; + + // Scan pluggableWidgets directory + const widgetsDir = join(rootPath, "packages", "pluggableWidgets"); + const widgetPaths = await scanPackagesDirectory(widgetsDir, fs); + + // Scan modules directory + const modulesDir = join(rootPath, "packages", "modules"); + const modulePaths = await scanPackagesDirectory(modulesDir, fs); + + // Track dependent widgets to skip them in the first pass + const dependentWidgets = new Set(); + + // First pass: identify all packages and their relationships + const allPackages = new Map(); + + for (const path of [...widgetPaths, ...modulePaths]) { + try { + const info = await getPackageInfo(path); + allPackages.set(info.name, { path, info }); + + // If this is an aggregator, mark its dependencies + if (isAggregator(info)) { + info.mxpackage.dependencies.forEach(dep => dependentWidgets.add(dep)); + } + } catch (error) { + console.warn(`Warning: Could not load package info for ${path}:`, error); + } + } + + // Second pass: process all packages (no filtering here) + for (const [name, { path, info }] of allPackages.entries()) { + try { + if (isAggregator(info)) { + // Package with dependencies (module or widget aggregator) + const packageType = info.mxpackage.type as "widget" | "module"; + + // Load changelog (both use module format) + const changelog = await getModuleChangelog(path, info.mxpackage.name); + const hasOwnChanges = changelog.hasUnreleasedLogs(); + const ownEntries = hasOwnChanges ? extractChangelogEntries(changelog) : []; + + // Load dependent widgets + const dependentWidgetInfos: DependentWidgetInfo[] = []; + for (const depName of info.mxpackage.dependencies) { + const depPackage = allPackages.get(depName); + if (depPackage) { + try { + const depInfo = await loadDependentWidgetInfo(depPackage.path); + dependentWidgetInfos.push(depInfo); + } catch (error) { + console.warn(`Warning: Could not load dependent widget ${depName}:`, error); + } + } + } + + candidates.push({ + packageType, + hasDependencies: true, + name: info.name, + path, + currentVersion: info.version.format(), + appNumber: info.marketplace.appNumber!, + appName: info.marketplace.appName ?? info.mxpackage.name, + hasUnreleasedChanges: hasOwnChanges, + unreleasedEntries: ownEntries, + dependentWidgets: dependentWidgetInfos + }); + } else if (isIndependentWidget(info) && !dependentWidgets.has(name)) { + // Independent widget (no dependencies) + const widgetInfo = await getWidgetInfo(path); + const changelog = await getWidgetChangelog(path); + const hasUnreleasedChanges = changelog.hasUnreleasedLogs(); + const unreleasedEntries = hasUnreleasedChanges ? extractChangelogEntries(changelog) : []; + + candidates.push({ + packageType: "widget", + hasDependencies: false, + name: info.name, + path, + currentVersion: widgetInfo.version.format(), + appNumber: info.marketplace.appNumber!, + appName: info.marketplace.appName ?? info.mxpackage.name, + hasUnreleasedChanges, + unreleasedEntries + }); + } else if (isStandaloneModule(info)) { + // Standalone module (no dependencies) + const moduleInfo = await getModuleInfo(path); + const changelog = await getModuleChangelog(path, info.mxpackage.name); + const hasUnreleasedChanges = changelog.hasUnreleasedLogs(); + const unreleasedEntries = hasUnreleasedChanges ? extractChangelogEntries(changelog) : []; + + candidates.push({ + packageType: "module", + hasDependencies: false, + name: info.name, + path, + currentVersion: moduleInfo.version.format(), + appNumber: info.marketplace.appNumber!, + appName: info.marketplace.appName ?? info.mxpackage.name, + hasUnreleasedChanges, + unreleasedEntries + }); + } + // Skip dependent widgets - they're handled as part of aggregators + } catch (error) { + console.warn(`Warning: Could not process package ${name}:`, error); + } + } + + return candidates; +} diff --git a/docs/release-process/release-info-tool.md b/docs/release-process/release-info-tool.md new file mode 100644 index 0000000000..ea4544a86b --- /dev/null +++ b/docs/release-process/release-info-tool.md @@ -0,0 +1,166 @@ +# Release Info Tool + +## Purpose + +The `rui-release-info` tool scans the Mendix web widgets monorepo and outputs structured JSON about packages ready for release. This document explains how to use it. + +## Quick Start + +```bash +# Get packages with unreleased changes (release candidates) +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --candidates + +# Get summary with counts +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --summary + +# Get all packages (including those without changes) +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --all +``` + +## Output Structure + +### ReleaseCandidate Type + +```typescript +interface ReleaseCandidate { + packageType: "widget" | "module"; // From package.json mxpackage.type + hasDependencies: boolean; // Does it bundle other widgets? + name: string; // NPM package name + path: string; // Filesystem path + currentVersion: string; // Current version (e.g., "3.9.0") + appNumber: number; // Mendix Marketplace app number + appName: string; // Display name in Marketplace + hasUnreleasedChanges: boolean; // Does the package itself have changes? + unreleasedEntries: ChangelogEntry[]; // Changelog entries for this package + dependentWidgets?: DependentWidgetInfo[]; // Only present if hasDependencies is true +} + +interface ChangelogEntry { + type: "Fixed" | "Added" | "Changed" | "Removed" | "Breaking changes"; + description: string; +} + +interface DependentWidgetInfo { + name: string; + path: string; + currentVersion: string; + appName: string; + hasUnreleasedChanges: boolean; + unreleasedEntries: ChangelogEntry[]; +} +``` + +## Available Commands + +| Command | Output | Use Case | +| ------------------- | --------------------------------------------------- | --------------------------------------- | +| `--candidates` `-c` | Full JSON array of packages with unreleased changes | Planning releases, reviewing changelogs | +| `--all` `-a` | Full JSON array of ALL packages | Understanding repo structure | +| `--summary` `-s` | Statistics + basic package list | Quick overview | +| `--help` `-h` | Help text | Command reference | + +## Package Types + +Packages are described by two properties: + +- **`packageType`**: Either `"widget"` or `"module"` +- **`hasDependencies`**: Either `true` (bundles other widgets) or `false` (standalone) + +This creates four combinations: + +| packageType | hasDependencies | Description | Examples | +| ----------- | --------------- | ------------------------------------------ | ----------------------------------------------------- | +| `"widget"` | `false` | Standalone widget | `@mendix/carousel-web`, `@mendix/document-viewer-web` | +| `"widget"` | `true` | Widget that bundles other widgets | `@mendix/charts-web` (only example) | +| `"module"` | `false` | Standalone module (no widget dependencies) | `@mendix/web-actions` (JavaScript actions only) | +| `"module"` | `true` | Module that bundles widgets | `@mendix/data-widgets` (Data Grid 2 + filters) | + +### Dependent Widgets + +Widgets that are bundled by packages with `hasDependencies: true` don't have their own Marketplace app number. They appear in the `dependentWidgets` array of their parent package, not as top-level candidates. + +**Example**: `@mendix/datagrid-web` is part of `@mendix/data-widgets` module + +### Version Synchronization + +When a package with `hasDependencies: true` is released, **all its dependent widgets get the same version**, even if they have no code changes. + +## Release Rules + +### When to Release + +A package is a **release candidate** if: + +1. **Independent package** (widget or module): `hasUnreleasedChanges: true` +2. **Aggregator** (widget or module): The package itself OR any dependent widget has `hasUnreleasedChanges: true` + +### Version Synchronization + +When an aggregator is released, **all dependent widgets get the same version** even if they have no changes. + +Example: If `@mendix/data-widgets` releases v3.10.0: + +- All 9 widgets in the bundle → v3.10.0 +- Even widgets without code changes get the version bump + +## Example Output + +```json +[ + { + "packageType": "widget", + "hasDependencies": false, + "name": "@mendix/document-viewer-web", + "currentVersion": "1.2.0", + "appNumber": 240853, + "appName": "Document Viewer", + "hasUnreleasedChanges": true, + "unreleasedEntries": [ + { + "type": "Changed", + "description": "We changed the internal structure of the widget" + } + ] + }, + { + "packageType": "module", + "hasDependencies": true, + "name": "@mendix/data-widgets", + "currentVersion": "3.9.0", + "appNumber": 116540, + "appName": "Data Widgets", + "hasUnreleasedChanges": false, + "unreleasedEntries": [], + "dependentWidgets": [ + { + "name": "@mendix/datagrid-date-filter-web", + "currentVersion": "3.9.0", + "appName": "Date Filter", + "hasUnreleasedChanges": true, + "unreleasedEntries": [ + { + "type": "Fixed", + "description": "We fixed an issue with filter selector dropdown not choosing the best placement on small viewports." + } + ] + } + ] + } +] +``` + +## Testing + +```bash +# Verify tool works +cd /path/to/web-widgets +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --summary + +# Pipe to jq for filtering +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --candidates | \ + jq '.[] | select(.packageType == "module")' + +# Count packages by type +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --summary | \ + jq '{widgets: .independentWidgets, modules: .independentModules, aggregators: (.widgetAggregators + .moduleAggregators)}' +``` diff --git a/docs/release-process/release-workflow.md b/docs/release-process/release-workflow.md new file mode 100644 index 0000000000..d28f4c84b8 --- /dev/null +++ b/docs/release-process/release-workflow.md @@ -0,0 +1,732 @@ +# Release Workflow Documentation + +## Overview + +This document describes the release process for Mendix web widgets and modules in the monorepo. + +**Package types**: + +- **Widgets** (`packageType: "widget"`) - UI components +- **Modules** (`packageType: "module"`) - Bundles of widgets and/or JavaScript actions + +**Dependency structure**: + +- **Standalone** (`hasDependencies: false`) - Released independently +- **Aggregators** (`hasDependencies: true`) - Bundle other widgets, all share the same version +- **Dependent widgets** - Widgets without Marketplace app numbers, bundled by aggregators + +## Package Structure + +### Standalone Widgets + +**Location**: `packages/pluggableWidgets/*/` +**Properties**: `packageType: "widget"`, `hasDependencies: false` + +**Characteristics**: + +- Has `marketplace.appNumber` field in `package.json` +- Released independently +- Own version tracking (semantic versioning) +- Own changelog lifecycle + +**Example**: `@mendix/carousel-web` + +```json +{ + "name": "@mendix/carousel-web", + "version": "2.3.2", + "mxpackage": { + "type": "widget" + }, + "marketplace": { + "appNumber": 47784, + "appName": "Carousel" + } +} +``` + +### Standalone Modules + +**Location**: `packages/modules/*/` +**Properties**: `packageType: "module"`, `hasDependencies: false` + +**Characteristics**: + +- Has `marketplace.appNumber` +- No widget dependencies (just JavaScript actions or other module content) +- Released independently +- Own version tracking + +**Example**: `@mendix/web-actions` + +```json +{ + "name": "@mendix/web-actions", + "version": "2.0.0", + "mxpackage": { + "type": "module" + }, + "marketplace": { + "appNumber": 114337, + "appName": "Web Actions" + } +} +``` + +### Widget Aggregators + +**Location**: `packages/pluggableWidgets/*/` +**Properties**: `packageType: "widget"`, `hasDependencies: true` + +**Characteristics**: + +- Has `mxpackage.type: "widget"` in `package.json` +- Has `mxpackage.changelogType: "module"` (special flag for changelog aggregation) +- Has `marketplace.appNumber` (published to marketplace) +- Lists dependent widgets in `mxpackage.dependencies` array +- Has own CHANGELOG.md that aggregates widget changelogs + +**Example**: `@mendix/charts-web` (the only widget-to-widget aggregator) + +```json +{ + "name": "@mendix/charts-web", + "version": "6.3.0", + "mxpackage": { + "type": "widget", + "changelogType": "module", + "dependencies": [ + "@mendix/area-chart-web", + "@mendix/bar-chart-web", + "@mendix/line-chart-web", + "@mendix/pie-doughnut-chart-web" + // ... more chart widgets + ] + }, + "marketplace": { + "appNumber": 105695, + "appName": "Charts" + } +} +``` + +### Module Aggregators + +**Location**: `packages/modules/*/` +**Properties**: `packageType: "module"`, `hasDependencies: true` + +**Characteristics**: + +- Has `mxpackage.type: "module"` in `package.json` +- Has `marketplace.appNumber` (published to marketplace) +- Lists dependent widgets in `mxpackage.dependencies` array +- Has own CHANGELOG.md that aggregates widget changelogs +- May contain JS actions in addition to widgets + +**Example**: `@mendix/data-widgets` + +```json +{ + "name": "@mendix/data-widgets", + "version": "3.9.0", + "mxpackage": { + "type": "module", + "dependencies": [ + "@mendix/datagrid-web", + "@mendix/datagrid-date-filter-web", + "@mendix/gallery-web" + // ... more widgets + ] + }, + "marketplace": { + "appNumber": 116540, + "appName": "Data Widgets" + } +} +``` + +### Dependent Widgets + +**Location**: `packages/pluggableWidgets/*/` +**Not returned as top-level release candidates** - appear in `dependentWidgets` array + +**Characteristics**: + +- Does NOT have `marketplace.appNumber` in `package.json` +- Listed in an aggregator's `mxpackage.dependencies` array +- Released only as part of their parent aggregator +- Version always matches parent aggregator version +- Has own CHANGELOG.md (aggregated into aggregator changelog on release) + +**Example**: `@mendix/datagrid-web` + +```json +{ + "name": "@mendix/datagrid-web", + "version": "3.9.0", + "mxpackage": { + "type": "widget" + }, + "marketplace": { + "appName": "Data Grid 2" + // Note: no appNumber + } +} +``` + +## Version Management + +### Standalone Packages (hasDependencies: false) + +- **Version tracking**: Each package has its own independent version +- **Semantic versioning rules**: + - **Patch**: Bug fixes, small improvements + - **Minor**: New features, backward-compatible changes + - **Major**: Breaking changes, major rewrites + +### Aggregator Packages (hasDependencies: true) + +- **Version tracking**: Aggregator and all dependent widgets share the same version +- When aggregator releases version `3.8.0`, ALL dependent widgets become `3.8.0` +- When aggregator releases `3.8.1` for a single widget fix, ALL widgets still bump to `3.8.1` +- Even if a widget's code didn't change, it gets the version bump + +**Example**: If `@mendix/data-widgets` (module aggregator) releases `3.9.0`, then: + +- `@mendix/datagrid-web` → `3.9.0` +- `@mendix/gallery-web` → `3.9.0` +- `@mendix/dropdown-sort-web` → `3.9.0` +- ... all widgets in the module → `3.9.0` + +## Changelog Management + +### Format + +All changelogs follow the [Keep a Changelog](https://keepachangelog.com/) format with Mendix-specific extensions. + +**Standard sections**: + +- `## [Unreleased]` - Unreleased changes +- `## [X.Y.Z] - YYYY-MM-DD` - Released versions + +**Change categories**: + +- `### Fixed` - Bug fixes +- `### Added` - New features +- `### Changed` - Changes to existing functionality +- `### Removed` - Removed features + +### Workflow + +1. **During development**: Developer adds entries under `## [Unreleased]` section +2. **On merge to main**: Changes merged with unreleased changelog entries (no version bump yet) +3. **Release decision**: Team decides to release based on: + - Unreleased changes exist + - Jira story is complete + - Team decision (may wait to bundle multiple stories) +4. **On release**: GitHub workflow moves unreleased entries to new version section + +### Widget Changelogs + +**Location**: `packages/pluggableWidgets/*/CHANGELOG.md` + +**Format** (for widget `@mendix/datagrid-web`): + +```markdown +# Changelog + +All notable changes to this widget will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.9.0] - 2026-03-23 + +### Changed + +- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. + +### Added + +- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes. + +### Fixed + +- We fixed an issue with Data export crashing on some Android devices. +``` + +### Module Changelogs + +**Location**: `packages/modules/*/CHANGELOG.md` + +**Format** (for module `@mendix/data-widgets`): + +```markdown +# Changelog + +All notable changes to this widget will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.9.0] DataWidgets - 2026-03-23 + +### [3.9.0] DatagridDropdownFilter + +#### Fixed + +- We fixed an issue with Dropdown filter captions not updating properly when their template parameters change. + +### [3.9.0] Datagrid + +#### Changed + +- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. + +#### Added + +- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes. + +#### Fixed + +- We fixed an issue with Data export crashing on some Android devices. + +### [3.9.0] Gallery + +#### Fixed + +- We fixed the pagination properties `Page attribute`, `Page size attribute`, and `Total count` not being shown in Studio Pro for Virtual Scrolling and Load More pagination modes. +``` + +**Key differences**: + +- Module name in version header: `[3.9.0] DataWidgets - 2026-03-23` +- Subcomponent sections for each widget: `### [3.9.0] Datagrid` +- Aggregates all widget changelogs into single module changelog +- Generated automatically by GitHub workflow during release + +## Release Preparation Process + +### Prerequisites + +- Changes merged to `main` branch +- Unreleased entries in CHANGELOG.md +- Jira story complete (or team decision to release) +- GitHub authentication configured (`gh` CLI) +- Jira authentication configured (optional) + +### Steps + +The release is prepared using the `rui-prepare-release` script: + +#### 1. Determine what to release + +**For standalone packages** (`hasDependencies: false`): + +- Check if package has unreleased changelog entries +- Package can be released independently + +**For aggregators** (`hasDependencies: true`): + +- Check if aggregator has unreleased changelog entries OR +- Check if any dependent widget has unreleased changelog entries +- If either has changes → aggregator is releasable + +#### 2. Determine version bump type + +**Manual decision** (currently): + +- **Patch** (X.Y.Z+1): Bug fixes, small changes +- **Minor** (X.Y+1.0): New features, backward-compatible +- **Major** (X+1.0.0): Breaking changes, major rewrites + +**Future**: Agent should determine this based on changelog entry types + +#### 3. Bump versions + +**For standalone packages** (`hasDependencies: false`): + +``` +package.json: version → X.Y.Z +src/package.xml: version → X.Y.Z (widgets only, modules don't have this) +``` + +**For aggregators** (`hasDependencies: true`): + +``` +# Aggregator package +packages/{modules|pluggableWidgets}/AGGREGATOR_NAME/package.json: version → X.Y.Z +packages/pluggableWidgets/AGGREGATOR_NAME/src/package.xml: version → X.Y.Z (widget aggregators only) + +# All dependent widgets +packages/pluggableWidgets/WIDGET_1/package.json: version → X.Y.Z +packages/pluggableWidgets/WIDGET_1/src/package.xml: version → X.Y.Z +packages/pluggableWidgets/WIDGET_2/package.json: version → X.Y.Z +packages/pluggableWidgets/WIDGET_2/src/package.xml: version → X.Y.Z +... (all dependent widgets) +``` + +**Note**: + +- Module aggregators (`packageType: "module"`, `hasDependencies: true`): package.json in `packages/modules/`, no package.xml +- Widget aggregators (`packageType: "widget"`, `hasDependencies: true`): package.json in `packages/pluggableWidgets/`, has package.xml +- Standalone modules (`packageType: "module"`, `hasDependencies: false`): package.json in `packages/modules/`, no package.xml +- Standalone widgets (`packageType: "widget"`, `hasDependencies: false`): package.json in `packages/pluggableWidgets/`, has package.xml + +**Important**: Changelog files are NOT modified at this stage + +#### 4. Create release branch + +Create temporary branch: + +``` +release/PACKAGE_NAME-vX.Y.Z +``` + +Example: `release/carousel-web-v2.3.2` or `release/data-widgets-v3.9.0` + +#### 5. Commit and push + +Commit message format: + +``` +chore(PACKAGE_NAME): bump version to X.Y.Z +``` + +Push branch to GitHub + +#### 6. Trigger GitHub release workflow + +The GitHub workflow (`Release` workflow) does: + +1. Reads the version from package.json +2. Updates CHANGELOG.md: + - Moves `## [Unreleased]` entries to `## [X.Y.Z] - YYYY-MM-DD` + - Clears Unreleased section +3. For aggregators: aggregates widget changelogs into aggregator changelog +4. Creates a draft GitHub release +5. Builds artifacts (MPK files) + +#### 7. OSS Clearance + +**Manual step** (not automatable yet): + +- Team member requests OSS clearance +- Uses scripts in `automation/utils/bin/rui-oss-clearance.ts` + +#### 8. Approve and publish + +**Manual step**: + +- Team member reviews draft release on GitHub +- Approves and publishes the release +- Artifacts are uploaded to Mendix Marketplace + +## Determining Releasability + +### Data Structure for Release Candidates + +See `docs/requirements/release-info-tool-summary.md` for the complete reference. + +```typescript +interface ReleaseCandidate { + packageType: "widget" | "module"; // From package.json mxpackage.type + hasDependencies: boolean; // Does it bundle other widgets? + name: string; // NPM package name + path: string; // Filesystem path + currentVersion: string; // Current version (e.g., "3.9.0") + appNumber: number; // Mendix Marketplace app number + appName: string; // Display name in Marketplace + hasUnreleasedChanges: boolean; // Does the package itself have changes? + unreleasedEntries: ChangelogEntry[]; // Changelog entries for this package + dependentWidgets?: DependentWidgetInfo[]; // Only present if hasDependencies is true +} + +interface ChangelogEntry { + type: "Fixed" | "Added" | "Changed" | "Removed" | "Breaking changes"; + description: string; +} + +interface DependentWidgetInfo { + name: string; + path: string; + currentVersion: string; + appName: string; + hasUnreleasedChanges: boolean; + unreleasedEntries: ChangelogEntry[]; +} +``` + +### Algorithm for determining release candidates + +Use the `rui-release-info` tool: + +```bash +# Get packages with unreleased changes +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --candidates +``` + +**Logic**: + +``` +FOR EACH package in [packages/pluggableWidgets/*, packages/modules/*]: + pkg = read package.json + + IF pkg.marketplace.appNumber does NOT exist: + → Skip (dependent widget, handled by its aggregator) + + IF pkg.mxpackage.dependencies exists AND length > 0: + # Aggregator (packageType: "widget" or "module", hasDependencies: true) + hasChanges = (package changelog has unreleased entries) OR + (any dependent widget has unreleased entries) + + IF hasChanges: + → Add to release candidates with dependentWidgets array + ELSE: + # Standalone (packageType: "widget" or "module", hasDependencies: false) + IF changelog has unreleased entries: + → Add to release candidates +``` + +See `automation/utils/src/release-candidates.ts` for the implementation. + +## Dependencies and Constraints + +### Dependency structure + +- **Aggregators (`hasDependencies: true`) → Widgets**: Both module and widget aggregators can bundle widgets + - Module aggregators (e.g., `data-widgets` → `datagrid-web`) + - Widget aggregators (e.g., `charts-web` → `line-chart-web`) - only one example exists +- **No deeper nesting**: Maximum 2 levels, no aggregator-to-aggregator dependencies +- **No circular dependencies**: A widget cannot be both standalone and dependent + +**Key identifier**: `hasDependencies: true` means package has `mxpackage.dependencies` array with items AND `marketplace.appNumber`. + +### Version synchronization + +- All widgets in an aggregator MUST have same version +- Version is determined by aggregator version +- Even unchanged widgets get version bumps + +### Package.json vs package.xml + +- `package.json`: Source of truth for version +- `src/package.xml`: Widget-specific, must stay in sync +- Modules don't have `package.xml` +- Scripts handle synchronization automatically + +## Existing Automation Tools + +Located in `automation/utils/`: + +### Package Information + +- `src/package-info.ts`: TypeScript types and parsers for package.json +- `src/monorepo.ts`: Utilities for working with pnpm workspace + +### Changelog Operations + +- `src/changelog-parser/`: Parser and writer for CHANGELOG.md files +- `src/changelog.ts`: High-level changelog operations +- `bin/rui-update-changelog-widget.ts`: Update widget changelog +- `bin/rui-update-changelog-module.ts`: Update module changelog + +### Version Bumping + +- `src/bump-version.ts`: Bump versions in package.json and package.xml +- `src/version.ts`: Version parsing and manipulation + +### Release Preparation + +- `bin/rui-prepare-release.ts`: Interactive wizard for release preparation +- `src/prepare-release-helpers.ts`: Helper functions for release prep + +### Release Information + +- `bin/rui-release-info.ts`: CLI tool to query release candidates (see `docs/requirements/release-info-tool-summary.md`) +- `src/release-candidates.ts`: Core logic for identifying releasable packages +- `src/io/filesystem.ts`: Filesystem abstraction for testing + +### Other Tools + +- `bin/rui-check-changelogs.ts`: Validate changelog format +- `bin/rui-oss-clearance.ts`: OSS clearance workflow +- `bin/rui-create-gh-release.ts`: Create GitHub releases +- `bin/rui-publish-marketplace.ts`: Publish to Mendix Marketplace + +## Future Automation Goals + +### Release Agent Capabilities + +1. ✅ **Discover releasable packages**: Use `rui-release-info --candidates` to scan monorepo and identify packages with unreleased changes +2. **Suggest version bumps**: Analyze changelog entries, suggest patch/minor/major based on entry types +3. **Check for related work**: Query Jira for related stories, suggest bundling multiple changes +4. **Validate release readiness**: Check CI status, changelog format, required files +5. **Execute release preparation**: Bump versions, create branch, trigger workflow (reuse existing `rui-prepare-release` logic) +6. **Monitor release status**: Track workflow progress, OSS clearance, publication + +### Integration Approaches + +**CLI Tool** (✅ implemented): + +- `rui-release-info` script outputs JSON +- Called via Bash tool in agent workflows +- Reusable functions in `@mendix/automation-utils` + +**MCP Server** (future): + +- Expose structured API for querying release state +- Can be called from Claude Code, CLI, or CI/CD +- Build on top of existing `loadReleaseCandidates()` function + +## Questions and Decisions + +### Open Questions + +None at this time - all clarifications provided inline. + +### Design Decisions + +1. **Version synchronization**: All widgets in aggregator share version (simplifies dependency management) +2. **Changelog aggregation**: Widget changelogs copied into aggregator changelog (single source of release notes) +3. **Release from main**: All releases branch from main (simpler workflow, relies on main being stable) +4. **Manual OSS clearance**: Cannot be automated yet (external process) +5. **Draft releases**: Manual approval required (safety gate before publication) +6. **Widget aggregators**: Special case for `charts-web` - widget that bundles other widgets (identified by `changelogType: "module"`) + +## Appendix: Example Scenarios + +### Scenario 1: Release standalone widget with bug fix + +**Initial state**: + +``` +@mendix/carousel-web + version: 2.3.1 + CHANGELOG.md: + ## [Unreleased] + ### Fixed + - We fixed an issue with carousel navigation on mobile devices. +``` + +**Steps**: + +1. Run `rui-prepare-release` +2. Select `@mendix/carousel-web` +3. Choose "patch" bump → `2.3.2` +4. Bump `package.json` and `src/package.xml` to `2.3.2` +5. Create branch `release/carousel-web-v2.3.2` +6. Commit and push +7. GitHub workflow updates changelog, creates draft release +8. Team approves and publishes + +**Final state**: + +``` +@mendix/carousel-web + version: 2.3.2 + CHANGELOG.md: + ## [Unreleased] + + ## [2.3.2] - 2026-04-15 + ### Fixed + - We fixed an issue with carousel navigation on mobile devices. +``` + +### Scenario 2: Release module aggregator with multiple widget changes + +**Initial state**: + +``` +@mendix/data-widgets (module) + version: 3.9.0 + dependencies: [@mendix/datagrid-web, @mendix/gallery-web, @mendix/dropdown-sort-web] + CHANGELOG.md: + ## [Unreleased] + +@mendix/datagrid-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + ### Fixed + - We fixed an issue with column sorting. + +@mendix/gallery-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + ### Added + - We added support for lazy loading images. + +@mendix/dropdown-sort-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + (empty - no changes) +``` + +**Steps**: + +1. Run `rui-prepare-release` +2. Select `@mendix/data-widgets` +3. Choose "minor" bump → `3.10.0` +4. Bump all package.json and package.xml files to `3.10.0`: + - `packages/modules/data-widgets/package.json` + - `packages/pluggableWidgets/datagrid-web/package.json` + `src/package.xml` + - `packages/pluggableWidgets/gallery-web/package.json` + `src/package.xml` + - `packages/pluggableWidgets/dropdown-sort-web/package.json` + `src/package.xml` +5. Create branch `release/data-widgets-v3.10.0` +6. Commit and push +7. GitHub workflow: + - Aggregates widget changelogs into module changelog + - Updates all CHANGELOG.md files + - Creates draft release + +**Final state**: + +``` +@mendix/data-widgets (module) + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] DataWidgets - 2026-04-15 + + ### [3.10.0] Datagrid + #### Fixed + - We fixed an issue with column sorting. + + ### [3.10.0] Gallery + #### Added + - We added support for lazy loading images. + +@mendix/datagrid-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + ### Fixed + - We fixed an issue with column sorting. + +@mendix/gallery-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + ### Added + - We added support for lazy loading images. + +@mendix/dropdown-sort-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + (empty release - version bump only) +``` + +Note: `dropdown-sort-web` gets version bump even though it had no code changes. From 22353f0118877e7d939c04f47116ab3ccc193d2f Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Fri, 17 Apr 2026 12:13:38 +0200 Subject: [PATCH 2/4] chore: update release workflow description --- .../release-info-tool-usage.md | 306 +++++++++++ docs/release-process/release-workflow.md | 509 +++--------------- 2 files changed, 384 insertions(+), 431 deletions(-) create mode 100644 docs/release-process/release-info-tool-usage.md diff --git a/docs/release-process/release-info-tool-usage.md b/docs/release-process/release-info-tool-usage.md new file mode 100644 index 0000000000..d93f4f6029 --- /dev/null +++ b/docs/release-process/release-info-tool-usage.md @@ -0,0 +1,306 @@ +## Determining Releasability + +### Data Structure for Release Candidates + +See `docs/release-process/release-info-tool.md` for the complete reference. + +```typescript +interface ReleaseCandidate { + packageType: "widget" | "module"; // From package.json mxpackage.type + hasDependencies: boolean; // Does it bundle other widgets? + name: string; // NPM package name + path: string; // Filesystem path + currentVersion: string; // Current version (e.g., "3.9.0") + appNumber: number; // Mendix Marketplace app number + appName: string; // Display name in Marketplace + hasUnreleasedChanges: boolean; // Does the package itself have changes? + unreleasedEntries: ChangelogEntry[]; // Changelog entries for this package + dependentWidgets?: DependentWidgetInfo[]; // Only present if hasDependencies is true +} + +interface ChangelogEntry { + type: "Fixed" | "Added" | "Changed" | "Removed" | "Breaking changes"; + description: string; +} + +interface DependentWidgetInfo { + name: string; + path: string; + currentVersion: string; + appName: string; + hasUnreleasedChanges: boolean; + unreleasedEntries: ChangelogEntry[]; +} +``` + +### Algorithm for determining release candidates + +Use the `rui-release-info` tool: + +```bash +# Get packages with unreleased changes +pnpm exec ts-node automation/utils/bin/rui-release-info.ts --candidates +``` + +**Logic**: + +``` +FOR EACH package in [packages/pluggableWidgets/*, packages/modules/*]: + pkg = read package.json + + IF pkg.marketplace.appNumber does NOT exist: + → Skip (dependent widget, handled by its aggregator) + + IF pkg.mxpackage.dependencies exists AND length > 0: + # Aggregator (packageType: "widget" or "module", hasDependencies: true) + hasChanges = (package changelog has unreleased entries) OR + (any dependent widget has unreleased entries) + + IF hasChanges: + → Add to release candidates with dependentWidgets array + ELSE: + # Standalone (packageType: "widget" or "module", hasDependencies: false) + IF changelog has unreleased entries: + → Add to release candidates +``` + +See `automation/utils/src/release-candidates.ts` for the implementation. + +## Dependencies and Constraints + +### Dependency structure + +- **Aggregators (`hasDependencies: true`) → Widgets**: Both module and widget aggregators can bundle widgets + - Module aggregators (e.g., `data-widgets` → `datagrid-web`) + - Widget aggregators (e.g., `charts-web` → `line-chart-web`) - only one example exists +- **No deeper nesting**: Maximum 2 levels, no aggregator-to-aggregator dependencies +- **No circular dependencies**: A widget cannot be both standalone and dependent + +**Key identifier**: `hasDependencies: true` means package has `mxpackage.dependencies` array with items AND `marketplace.appNumber`. + +### Version synchronization + +- All widgets in an aggregator MUST have same version +- Version is determined by aggregator version +- Even unchanged widgets get version bumps + +### Package.json vs package.xml + +- `package.json`: Source of truth for version +- `src/package.xml`: Widget-specific, must stay in sync +- Modules don't have `package.xml` +- Scripts handle synchronization automatically + +## Existing Automation Tools + +Located in `automation/utils/`: + +### Package Information + +- `src/package-info.ts`: TypeScript types and parsers for package.json +- `src/monorepo.ts`: Utilities for working with pnpm workspace + +### Changelog Operations + +- `src/changelog-parser/`: Parser and writer for CHANGELOG.md files +- `src/changelog.ts`: High-level changelog operations +- `bin/rui-update-changelog-widget.ts`: Update widget changelog +- `bin/rui-update-changelog-module.ts`: Update module changelog + +### Version Bumping + +- `src/bump-version.ts`: Bump versions in package.json and package.xml +- `src/version.ts`: Version parsing and manipulation + +### Release Preparation + +- `bin/rui-prepare-release.ts`: Interactive wizard for release preparation +- `src/prepare-release-helpers.ts`: Helper functions for release prep + +### Release Information + +- `bin/rui-release-info.ts`: CLI tool to query release candidates (see `docs/requirements/release-info-tool-summary.md`) +- `src/release-candidates.ts`: Core logic for identifying releasable packages +- `src/io/filesystem.ts`: Filesystem abstraction for testing + +### Other Tools + +- `bin/rui-check-changelogs.ts`: Validate changelog format +- `bin/rui-oss-clearance.ts`: OSS clearance workflow +- `bin/rui-create-gh-release.ts`: Create GitHub releases +- `bin/rui-publish-marketplace.ts`: Publish to Mendix Marketplace + +## Future Automation Goals + +### Release Agent Capabilities + +1. ✅ **Discover releasable packages**: Use `rui-release-info --candidates` to scan monorepo and identify packages with unreleased changes +2. **Suggest version bumps**: Analyze changelog entries, suggest patch/minor/major based on entry types +3. **Check for related work**: Query Jira for related stories, suggest bundling multiple changes +4. **Validate release readiness**: Check CI status, changelog format, required files +5. **Execute release preparation**: Bump versions, create branch, trigger workflow (reuse existing `rui-prepare-release` logic) +6. **Monitor release status**: Track workflow progress, OSS clearance, publication + +### Integration Approaches + +**CLI Tool** (✅ implemented): + +- `rui-release-info` script outputs JSON +- Called via Bash tool in agent workflows +- Reusable functions in `@mendix/automation-utils` + +**MCP Server** (future): + +- Expose structured API for querying release state +- Can be called from Claude Code, CLI, or CI/CD +- Build on top of existing `loadReleaseCandidates()` function + +## Questions and Decisions + +### Open Questions + +None at this time - all clarifications provided inline. + +### Design Decisions + +1. **Version synchronization**: All widgets in aggregator share version (simplifies dependency management) +2. **Changelog aggregation**: Widget changelogs copied into aggregator changelog (single source of release notes) +3. **Release from main**: All releases branch from main (simpler workflow, relies on main being stable) +4. **Manual OSS clearance**: Cannot be automated yet (external process) +5. **Draft releases**: Manual approval required (safety gate before publication) +6. **Widget aggregators**: Special case for `charts-web` - widget that bundles other widgets (identified by `changelogType: "module"`) + +## Appendix: Example Scenarios + +### Scenario 1: Release standalone widget with bug fix + +**Initial state**: + +``` +@mendix/carousel-web + version: 2.3.1 + CHANGELOG.md: + ## [Unreleased] + ### Fixed + - We fixed an issue with carousel navigation on mobile devices. +``` + +**Steps**: + +1. Run `rui-prepare-release` +2. Select `@mendix/carousel-web` +3. Choose "patch" bump → `2.3.2` +4. Bump `package.json` and `src/package.xml` to `2.3.2` +5. Create branch `release/carousel-web-v2.3.2` +6. Commit and push +7. GitHub workflow updates changelog, creates draft release +8. Team approves and publishes + +**Final state**: + +``` +@mendix/carousel-web + version: 2.3.2 + CHANGELOG.md: + ## [Unreleased] + + ## [2.3.2] - 2026-04-15 + ### Fixed + - We fixed an issue with carousel navigation on mobile devices. +``` + +### Scenario 2: Release module aggregator with multiple widget changes + +**Initial state**: + +``` +@mendix/data-widgets (module) + version: 3.9.0 + dependencies: [@mendix/datagrid-web, @mendix/gallery-web, @mendix/dropdown-sort-web] + CHANGELOG.md: + ## [Unreleased] + +@mendix/datagrid-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + ### Fixed + - We fixed an issue with column sorting. + +@mendix/gallery-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + ### Added + - We added support for lazy loading images. + +@mendix/dropdown-sort-web + version: 3.9.0 + CHANGELOG.md: + ## [Unreleased] + (empty - no changes) +``` + +**Steps**: + +1. Run `rui-prepare-release` +2. Select `@mendix/data-widgets` +3. Choose "minor" bump → `3.10.0` +4. Bump all package.json and package.xml files to `3.10.0`: + - `packages/modules/data-widgets/package.json` + - `packages/pluggableWidgets/datagrid-web/package.json` + `src/package.xml` + - `packages/pluggableWidgets/gallery-web/package.json` + `src/package.xml` + - `packages/pluggableWidgets/dropdown-sort-web/package.json` + `src/package.xml` +5. Create branch `release/data-widgets-v3.10.0` +6. Commit and push +7. GitHub workflow: + - Aggregates widget changelogs into module changelog + - Updates all CHANGELOG.md files + - Creates draft release + +**Final state**: + +``` +@mendix/data-widgets (module) + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] DataWidgets - 2026-04-15 + + ### [3.10.0] Datagrid + #### Fixed + - We fixed an issue with column sorting. + + ### [3.10.0] Gallery + #### Added + - We added support for lazy loading images. + +@mendix/datagrid-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + ### Fixed + - We fixed an issue with column sorting. + +@mendix/gallery-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + ### Added + - We added support for lazy loading images. + +@mendix/dropdown-sort-web + version: 3.10.0 + CHANGELOG.md: + ## [Unreleased] + + ## [3.10.0] - 2026-04-15 + (empty release - version bump only) +``` + +Note: `dropdown-sort-web` gets version bump even though it had no code changes. diff --git a/docs/release-process/release-workflow.md b/docs/release-process/release-workflow.md index d28f4c84b8..e33391566d 100644 --- a/docs/release-process/release-workflow.md +++ b/docs/release-process/release-workflow.md @@ -6,21 +6,20 @@ This document describes the release process for Mendix web widgets and modules i **Package types**: -- **Widgets** (`packageType: "widget"`) - UI components -- **Modules** (`packageType: "module"`) - Bundles of widgets and/or JavaScript actions +- **Widgets** - UI components +- **Modules** - Bundles of widgets, JavaScript actions, and Mendix documents like Nanoflows, Pages as well as Entities in domain model. +- **Dependent widgets** - Widgets without Marketplace app numbers, bundled by other modules or widgets **Dependency structure**: -- **Standalone** (`hasDependencies: false`) - Released independently -- **Aggregators** (`hasDependencies: true`) - Bundle other widgets, all share the same version -- **Dependent widgets** - Widgets without Marketplace app numbers, bundled by aggregators +- Every module or widget can contain other widgets (dependent widgets). Most of the modules do, most of the widgets don't. +- There is maximum one nesting level (widget → widget or module → widget). -## Package Structure +## Package Types -### Standalone Widgets +### Widgets -**Location**: `packages/pluggableWidgets/*/` -**Properties**: `packageType: "widget"`, `hasDependencies: false` +**Location**: `packages/pluggableWidgets/*/` **Characteristics**: @@ -45,83 +44,23 @@ This document describes the release process for Mendix web widgets and modules i } ``` -### Standalone Modules - -**Location**: `packages/modules/*/` -**Properties**: `packageType: "module"`, `hasDependencies: false` - -**Characteristics**: - -- Has `marketplace.appNumber` -- No widget dependencies (just JavaScript actions or other module content) -- Released independently -- Own version tracking - -**Example**: `@mendix/web-actions` +**Special case** -```json -{ - "name": "@mendix/web-actions", - "version": "2.0.0", - "mxpackage": { - "type": "module" - }, - "marketplace": { - "appNumber": 114337, - "appName": "Web Actions" - } -} -``` +- Widget `@mendix/charts-web` contains other widgets as if it is a module. +- Referencing dependencies, versioning and changelog structure works the same way as for module → widget dependency +- Should be still be referred as normal widget, no extra treatment (dependencies is only an extra step in build process) -### Widget Aggregators +### Modules -**Location**: `packages/pluggableWidgets/*/` -**Properties**: `packageType: "widget"`, `hasDependencies: true` - -**Characteristics**: - -- Has `mxpackage.type: "widget"` in `package.json` -- Has `mxpackage.changelogType: "module"` (special flag for changelog aggregation) -- Has `marketplace.appNumber` (published to marketplace) -- Lists dependent widgets in `mxpackage.dependencies` array -- Has own CHANGELOG.md that aggregates widget changelogs - -**Example**: `@mendix/charts-web` (the only widget-to-widget aggregator) - -```json -{ - "name": "@mendix/charts-web", - "version": "6.3.0", - "mxpackage": { - "type": "widget", - "changelogType": "module", - "dependencies": [ - "@mendix/area-chart-web", - "@mendix/bar-chart-web", - "@mendix/line-chart-web", - "@mendix/pie-doughnut-chart-web" - // ... more chart widgets - ] - }, - "marketplace": { - "appNumber": 105695, - "appName": "Charts" - } -} -``` - -### Module Aggregators - -**Location**: `packages/modules/*/` -**Properties**: `packageType: "module"`, `hasDependencies: true` +**Location**: `packages/modules/*/` **Characteristics**: - Has `mxpackage.type: "module"` in `package.json` - Has `marketplace.appNumber` (published to marketplace) -- Lists dependent widgets in `mxpackage.dependencies` array -- Has own CHANGELOG.md that aggregates widget changelogs -- May contain JS actions in addition to widgets +- Lists dependent widgets in `mxpackage.dependencies` array, might be empty +- Has own CHANGELOG.md entries and additionally aggregates widget changelogs +- Contain other Mendix elements in addition to widgets: JS actions, nanoflows, pages, entities, etc. Those are not referenced in package.json **Example**: `@mendix/data-widgets` @@ -153,10 +92,10 @@ This document describes the release process for Mendix web widgets and modules i **Characteristics**: - Does NOT have `marketplace.appNumber` in `package.json` -- Listed in an aggregator's `mxpackage.dependencies` array -- Released only as part of their parent aggregator -- Version always matches parent aggregator version -- Has own CHANGELOG.md (aggregated into aggregator changelog on release) +- Listed in parent's `mxpackage.dependencies` array +- Released only as part of their parent +- Version is set to match the parent version on each release cycle +- Has own CHANGELOG.md (aggregated into parent changelog on release) **Example**: `@mendix/datagrid-web` @@ -176,7 +115,7 @@ This document describes the release process for Mendix web widgets and modules i ## Version Management -### Standalone Packages (hasDependencies: false) +### Packages without dependencies - **Version tracking**: Each package has its own independent version - **Semantic versioning rules**: @@ -184,14 +123,14 @@ This document describes the release process for Mendix web widgets and modules i - **Minor**: New features, backward-compatible changes - **Major**: Breaking changes, major rewrites -### Aggregator Packages (hasDependencies: true) +### Packages with dependencies -- **Version tracking**: Aggregator and all dependent widgets share the same version -- When aggregator releases version `3.8.0`, ALL dependent widgets become `3.8.0` -- When aggregator releases `3.8.1` for a single widget fix, ALL widgets still bump to `3.8.1` +- **Version tracking**: Parent package and all dependent widgets share the same version +- When parent releases version `3.8.0`, ALL dependent widgets become `3.8.0` +- When parent releases `3.8.1` for a single widget fix, ALL widgets still bump to `3.8.1` - Even if a widget's code didn't change, it gets the version bump -**Example**: If `@mendix/data-widgets` (module aggregator) releases `3.9.0`, then: +**Example**: If `@mendix/data-widgets` (module) releases `3.9.0`, then: - `@mendix/datagrid-web` → `3.9.0` - `@mendix/gallery-web` → `3.9.0` @@ -305,7 +244,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Module name in version header: `[3.9.0] DataWidgets - 2026-03-23` - Subcomponent sections for each widget: `### [3.9.0] Datagrid` - Aggregates all widget changelogs into single module changelog -- Generated automatically by GitHub workflow during release +- Generated automatically by GitHub workflow during release — the workflow reads each dependent widget's flat `## [Unreleased]` entries and transforms them into the nested `### [X.Y.Z] WidgetName` format shown above ## Release Preparation Process @@ -313,26 +252,21 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - Changes merged to `main` branch - Unreleased entries in CHANGELOG.md -- Jira story complete (or team decision to release) -- GitHub authentication configured (`gh` CLI) -- Jira authentication configured (optional) ### Steps -The release is prepared using the `rui-prepare-release` script: - #### 1. Determine what to release -**For standalone packages** (`hasDependencies: false`): +**Packages without dependencies**: - Check if package has unreleased changelog entries -- Package can be released independently +- If so → is releasable -**For aggregators** (`hasDependencies: true`): +**Packages with dependencies**: -- Check if aggregator has unreleased changelog entries OR +- Check if package itself has unreleased changelog entries OR - Check if any dependent widget has unreleased changelog entries -- If either has changes → aggregator is releasable +- If so → is releasable #### 2. Determine version bump type @@ -342,50 +276,43 @@ The release is prepared using the `rui-prepare-release` script: - **Minor** (X.Y+1.0): New features, backward-compatible - **Major** (X+1.0.0): Breaking changes, major rewrites -**Future**: Agent should determine this based on changelog entry types +**Future**: Agent should advise this based on changelog entries #### 3. Bump versions -**For standalone packages** (`hasDependencies: false`): +**For packages without dependencies**: ``` package.json: version → X.Y.Z src/package.xml: version → X.Y.Z (widgets only, modules don't have this) ``` -**For aggregators** (`hasDependencies: true`): +**For packages with dependencies**: ``` -# Aggregator package -packages/{modules|pluggableWidgets}/AGGREGATOR_NAME/package.json: version → X.Y.Z -packages/pluggableWidgets/AGGREGATOR_NAME/src/package.xml: version → X.Y.Z (widget aggregators only) +# Package itself +packages/{modules|pluggableWidgets}/PACKAGE_NAME/package.json: version → X.Y.Z +packages/pluggableWidgets/PACKAGE_NAME/src/package.xml: version → X.Y.Z (widgets only) # All dependent widgets -packages/pluggableWidgets/WIDGET_1/package.json: version → X.Y.Z -packages/pluggableWidgets/WIDGET_1/src/package.xml: version → X.Y.Z -packages/pluggableWidgets/WIDGET_2/package.json: version → X.Y.Z -packages/pluggableWidgets/WIDGET_2/src/package.xml: version → X.Y.Z +packages/pluggableWidgets/DEPENDENT_WIDGET_1/package.json: version → X.Y.Z +packages/pluggableWidgets/DEPENDENT_WIDGET_1/src/package.xml: version → X.Y.Z +packages/pluggableWidgets/DEPENDENT_WIDGET_2/package.json: version → X.Y.Z +packages/pluggableWidgets/DEPENDENT_WIDGET_2/src/package.xml: version → X.Y.Z ... (all dependent widgets) ``` -**Note**: - -- Module aggregators (`packageType: "module"`, `hasDependencies: true`): package.json in `packages/modules/`, no package.xml -- Widget aggregators (`packageType: "widget"`, `hasDependencies: true`): package.json in `packages/pluggableWidgets/`, has package.xml -- Standalone modules (`packageType: "module"`, `hasDependencies: false`): package.json in `packages/modules/`, no package.xml -- Standalone widgets (`packageType: "widget"`, `hasDependencies: false`): package.json in `packages/pluggableWidgets/`, has package.xml - -**Important**: Changelog files are NOT modified at this stage +**Important**: Only version is bumped at this moment. Changelog files themselves are updated on step 6. #### 4. Create release branch Create temporary branch: ``` -release/PACKAGE_NAME-vX.Y.Z +tmp/PACKAGE_NAME-vX.Y.Z ``` -Example: `release/carousel-web-v2.3.2` or `release/data-widgets-v3.9.0` +Example: `tmp/carousel-web-v2.3.2` or `tmp/data-widgets-v3.9.0` #### 5. Commit and push @@ -399,334 +326,54 @@ Push branch to GitHub #### 6. Trigger GitHub release workflow -The GitHub workflow (`Release` workflow) does: +The GitHub workflow (`Release` workflow) is triggered manually via UI or via a script. + +What it does: 1. Reads the version from package.json 2. Updates CHANGELOG.md: - - Moves `## [Unreleased]` entries to `## [X.Y.Z] - YYYY-MM-DD` - - Clears Unreleased section -3. For aggregators: aggregates widget changelogs into aggregator changelog -4. Creates a draft GitHub release -5. Builds artifacts (MPK files) + - Moves all entries from `## [Unreleased]` section to a new section for the new release `## [X.Y.Z] - YYYY-MM-DD` + - Unreleased section becomes empty +3. For packages with dependencies: aggregates widget changelogs into parent's changelog +4. Builds artifacts and creates a draft GitHub release based on them +5. Commits changelog updates to `tmp/PACKAGE_NAME-vX.Y.Z` +6. Opens PR to `main` containing changes from `tmp/PACKAGE_NAME-vX.Y.Z` -#### 7. OSS Clearance +#### 7. Create Jira version entries -**Manual step** (not automatable yet): - -- Team member requests OSS clearance -- Uses scripts in `automation/utils/bin/rui-oss-clearance.ts` - -#### 8. Approve and publish +By team member via Jira UI or via helper scripts. **Manual step**: -- Team member reviews draft release on GitHub -- Approves and publishes the release -- Artifacts are uploaded to Mendix Marketplace - -## Determining Releasability - -### Data Structure for Release Candidates - -See `docs/requirements/release-info-tool-summary.md` for the complete reference. - -```typescript -interface ReleaseCandidate { - packageType: "widget" | "module"; // From package.json mxpackage.type - hasDependencies: boolean; // Does it bundle other widgets? - name: string; // NPM package name - path: string; // Filesystem path - currentVersion: string; // Current version (e.g., "3.9.0") - appNumber: number; // Mendix Marketplace app number - appName: string; // Display name in Marketplace - hasUnreleasedChanges: boolean; // Does the package itself have changes? - unreleasedEntries: ChangelogEntry[]; // Changelog entries for this package - dependentWidgets?: DependentWidgetInfo[]; // Only present if hasDependencies is true -} - -interface ChangelogEntry { - type: "Fixed" | "Added" | "Changed" | "Removed" | "Breaking changes"; - description: string; -} - -interface DependentWidgetInfo { - name: string; - path: string; - currentVersion: string; - appName: string; - hasUnreleasedChanges: boolean; - unreleasedEntries: ChangelogEntry[]; -} -``` - -### Algorithm for determining release candidates - -Use the `rui-release-info` tool: - -```bash -# Get packages with unreleased changes -pnpm exec ts-node automation/utils/bin/rui-release-info.ts --candidates -``` +- In Jira a new Release is created with the name `PACKAGE_NAME-vX.Y.Z`. +- This release is then attached to the Jira story (or stories if new version contains multiple work items) -**Logic**: +#### 8. OSS Clearance -``` -FOR EACH package in [packages/pluggableWidgets/*, packages/modules/*]: - pkg = read package.json - - IF pkg.marketplace.appNumber does NOT exist: - → Skip (dependent widget, handled by its aggregator) - - IF pkg.mxpackage.dependencies exists AND length > 0: - # Aggregator (packageType: "widget" or "module", hasDependencies: true) - hasChanges = (package changelog has unreleased entries) OR - (any dependent widget has unreleased entries) - - IF hasChanges: - → Add to release candidates with dependentWidgets array - ELSE: - # Standalone (packageType: "widget" or "module", hasDependencies: false) - IF changelog has unreleased entries: - → Add to release candidates -``` - -See `automation/utils/src/release-candidates.ts` for the implementation. - -## Dependencies and Constraints - -### Dependency structure - -- **Aggregators (`hasDependencies: true`) → Widgets**: Both module and widget aggregators can bundle widgets - - Module aggregators (e.g., `data-widgets` → `datagrid-web`) - - Widget aggregators (e.g., `charts-web` → `line-chart-web`) - only one example exists -- **No deeper nesting**: Maximum 2 levels, no aggregator-to-aggregator dependencies -- **No circular dependencies**: A widget cannot be both standalone and dependent - -**Key identifier**: `hasDependencies: true` means package has `mxpackage.dependencies` array with items AND `marketplace.appNumber`. - -### Version synchronization - -- All widgets in an aggregator MUST have same version -- Version is determined by aggregator version -- Even unchanged widgets get version bumps - -### Package.json vs package.xml - -- `package.json`: Source of truth for version -- `src/package.xml`: Widget-specific, must stay in sync -- Modules don't have `package.xml` -- Scripts handle synchronization automatically - -## Existing Automation Tools - -Located in `automation/utils/`: - -### Package Information - -- `src/package-info.ts`: TypeScript types and parsers for package.json -- `src/monorepo.ts`: Utilities for working with pnpm workspace - -### Changelog Operations - -- `src/changelog-parser/`: Parser and writer for CHANGELOG.md files -- `src/changelog.ts`: High-level changelog operations -- `bin/rui-update-changelog-widget.ts`: Update widget changelog -- `bin/rui-update-changelog-module.ts`: Update module changelog - -### Version Bumping - -- `src/bump-version.ts`: Bump versions in package.json and package.xml -- `src/version.ts`: Version parsing and manipulation - -### Release Preparation - -- `bin/rui-prepare-release.ts`: Interactive wizard for release preparation -- `src/prepare-release-helpers.ts`: Helper functions for release prep - -### Release Information - -- `bin/rui-release-info.ts`: CLI tool to query release candidates (see `docs/requirements/release-info-tool-summary.md`) -- `src/release-candidates.ts`: Core logic for identifying releasable packages -- `src/io/filesystem.ts`: Filesystem abstraction for testing - -### Other Tools - -- `bin/rui-check-changelogs.ts`: Validate changelog format -- `bin/rui-oss-clearance.ts`: OSS clearance workflow -- `bin/rui-create-gh-release.ts`: Create GitHub releases -- `bin/rui-publish-marketplace.ts`: Publish to Mendix Marketplace - -## Future Automation Goals - -### Release Agent Capabilities - -1. ✅ **Discover releasable packages**: Use `rui-release-info --candidates` to scan monorepo and identify packages with unreleased changes -2. **Suggest version bumps**: Analyze changelog entries, suggest patch/minor/major based on entry types -3. **Check for related work**: Query Jira for related stories, suggest bundling multiple changes -4. **Validate release readiness**: Check CI status, changelog format, required files -5. **Execute release preparation**: Bump versions, create branch, trigger workflow (reuse existing `rui-prepare-release` logic) -6. **Monitor release status**: Track workflow progress, OSS clearance, publication - -### Integration Approaches - -**CLI Tool** (✅ implemented): - -- `rui-release-info` script outputs JSON -- Called via Bash tool in agent workflows -- Reusable functions in `@mendix/automation-utils` - -**MCP Server** (future): - -- Expose structured API for querying release state -- Can be called from Claude Code, CLI, or CI/CD -- Build on top of existing `loadReleaseCandidates()` function - -## Questions and Decisions - -### Open Questions - -None at this time - all clarifications provided inline. - -### Design Decisions - -1. **Version synchronization**: All widgets in aggregator share version (simplifies dependency management) -2. **Changelog aggregation**: Widget changelogs copied into aggregator changelog (single source of release notes) -3. **Release from main**: All releases branch from main (simpler workflow, relies on main being stable) -4. **Manual OSS clearance**: Cannot be automated yet (external process) -5. **Draft releases**: Manual approval required (safety gate before publication) -6. **Widget aggregators**: Special case for `charts-web` - widget that bundles other widgets (identified by `changelogType: "module"`) - -## Appendix: Example Scenarios - -### Scenario 1: Release standalone widget with bug fix - -**Initial state**: - -``` -@mendix/carousel-web - version: 2.3.1 - CHANGELOG.md: - ## [Unreleased] - ### Fixed - - We fixed an issue with carousel navigation on mobile devices. -``` +**Manual step**: -**Steps**: +- When the draft release created by workflow on step 6 is ready. +- Team member requests OSS clearance based on draft release artifacts +- Uses scripts in `automation/utils/bin/rui-oss-clearance.ts` +- When OSS clearance is complete team member uploads clearance artifact (HTML file) to the draft release. -1. Run `rui-prepare-release` -2. Select `@mendix/carousel-web` -3. Choose "patch" bump → `2.3.2` -4. Bump `package.json` and `src/package.xml` to `2.3.2` -5. Create branch `release/carousel-web-v2.3.2` -6. Commit and push -7. GitHub workflow updates changelog, creates draft release -8. Team approves and publishes +This step has to be finished before the next steps. -**Final state**: +#### 9. Approve and publish -``` -@mendix/carousel-web - version: 2.3.2 - CHANGELOG.md: - ## [Unreleased] - - ## [2.3.2] - 2026-04-15 - ### Fixed - - We fixed an issue with carousel navigation on mobile devices. -``` - -### Scenario 2: Release module aggregator with multiple widget changes +**Manual step**: -**Initial state**: +- Team member reviews draft release on GitHub and makes sure release artifacts and oss clearance artifact (HTML file) are present +- If comfortable with release they "Publish" the release +- Artifacts are uploaded to Mendix Marketplace by a separate workflow -``` -@mendix/data-widgets (module) - version: 3.9.0 - dependencies: [@mendix/datagrid-web, @mendix/gallery-web, @mendix/dropdown-sort-web] - CHANGELOG.md: - ## [Unreleased] - -@mendix/datagrid-web - version: 3.9.0 - CHANGELOG.md: - ## [Unreleased] - ### Fixed - - We fixed an issue with column sorting. - -@mendix/gallery-web - version: 3.9.0 - CHANGELOG.md: - ## [Unreleased] - ### Added - - We added support for lazy loading images. - -@mendix/dropdown-sort-web - version: 3.9.0 - CHANGELOG.md: - ## [Unreleased] - (empty - no changes) -``` +#### 10. Merging changelogs and completion -**Steps**: - -1. Run `rui-prepare-release` -2. Select `@mendix/data-widgets` -3. Choose "minor" bump → `3.10.0` -4. Bump all package.json and package.xml files to `3.10.0`: - - `packages/modules/data-widgets/package.json` - - `packages/pluggableWidgets/datagrid-web/package.json` + `src/package.xml` - - `packages/pluggableWidgets/gallery-web/package.json` + `src/package.xml` - - `packages/pluggableWidgets/dropdown-sort-web/package.json` + `src/package.xml` -5. Create branch `release/data-widgets-v3.10.0` -6. Commit and push -7. GitHub workflow: - - Aggregates widget changelogs into module changelog - - Updates all CHANGELOG.md files - - Creates draft release - -**Final state**: +Team members approve and merge PR opened at step 6. +After merge `main` contains correct changelogs +Branch `tmp/PACKAGE_NAME-vX.Y.Z` is removed manually -``` -@mendix/data-widgets (module) - version: 3.10.0 - CHANGELOG.md: - ## [Unreleased] - - ## [3.10.0] DataWidgets - 2026-04-15 - - ### [3.10.0] Datagrid - #### Fixed - - We fixed an issue with column sorting. - - ### [3.10.0] Gallery - #### Added - - We added support for lazy loading images. - -@mendix/datagrid-web - version: 3.10.0 - CHANGELOG.md: - ## [Unreleased] - - ## [3.10.0] - 2026-04-15 - ### Fixed - - We fixed an issue with column sorting. - -@mendix/gallery-web - version: 3.10.0 - CHANGELOG.md: - ## [Unreleased] - - ## [3.10.0] - 2026-04-15 - ### Added - - We added support for lazy loading images. - -@mendix/dropdown-sort-web - version: 3.10.0 - CHANGELOG.md: - ## [Unreleased] - - ## [3.10.0] - 2026-04-15 - (empty release - version bump only) -``` +#### 11. Completion in Jira -Note: `dropdown-sort-web` gets version bump even though it had no code changes. +At this stage the release process is considered complete. +Relevant Jira stories are marked as Done and relevant Jira releases is marked as released. From 9e7554d11203b5676a1cc2d7285456dbb9534a7d Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Mon, 11 May 2026 14:55:02 +0200 Subject: [PATCH 3/4] chore: describe package types in separate docs --- docs/release-process/package-types.md | 46 +++++++++ docs/release-process/release-workflow.md | 113 +---------------------- 2 files changed, 49 insertions(+), 110 deletions(-) create mode 100644 docs/release-process/package-types.md diff --git a/docs/release-process/package-types.md b/docs/release-process/package-types.md new file mode 100644 index 0000000000..19462a1f83 --- /dev/null +++ b/docs/release-process/package-types.md @@ -0,0 +1,46 @@ +# Package Types + +## Widgets + +**Location**: `packages/pluggableWidgets/*/`, `mxpackage.type: "widget"` + +Widgets with `marketplace.appNumber` are published independently (**standalone widgets**). Widgets without it are **dependent widgets** — not published of their own and are listed in a parent's `mxpackage.dependencies`. + +```json +// Standalone - has appNumber +{ + "mxpackage": { "type": "widget" }, + "marketplace": { "appNumber": 47784, "appName": "Carousel" } +} + +// Dependent — no appNumber +{ + "mxpackage": { "type": "widget" }, + "marketplace": { "appName": "Data Grid 2" } +} +``` + +**Special case**: `@mendix/charts-web` has `mxpackage.dependencies`, acts as a module but is still a regular widget. + +## Modules + +**Location**: `packages/modules/*/`, `mxpackage.type: "module"` + +- Has `marketplace.appNumber`. +- Lists dependent widgets in `mxpackage.dependencies`. +- May also contain JS actions, nanoflows, pages, and domain model entities (not referenced in `package.json`). + +```json +{ + "mxpackage": { + "type": "module", + "dependencies": ["@mendix/datagrid-web", "@mendix/gallery-web"] + }, + "marketplace": { "appNumber": 116540, "appName": "Data Widgets" } +} +``` + +## Dependency Structure + +- Max one level of nesting: `module → widget` or `widget → widget`. +- Most modules have dependent widgets; standalone widgets (except charts) don't. diff --git a/docs/release-process/release-workflow.md b/docs/release-process/release-workflow.md index e33391566d..b39edb3c82 100644 --- a/docs/release-process/release-workflow.md +++ b/docs/release-process/release-workflow.md @@ -4,118 +4,11 @@ This document describes the release process for Mendix web widgets and modules in the monorepo. -**Package types**: - -- **Widgets** - UI components -- **Modules** - Bundles of widgets, JavaScript actions, and Mendix documents like Nanoflows, Pages as well as Entities in domain model. -- **Dependent widgets** - Widgets without Marketplace app numbers, bundled by other modules or widgets - -**Dependency structure**: - -- Every module or widget can contain other widgets (dependent widgets). Most of the modules do, most of the widgets don't. -- There is maximum one nesting level (widget → widget or module → widget). - -## Package Types - -### Widgets - -**Location**: `packages/pluggableWidgets/*/` - -**Characteristics**: - -- Has `marketplace.appNumber` field in `package.json` -- Released independently -- Own version tracking (semantic versioning) -- Own changelog lifecycle - -**Example**: `@mendix/carousel-web` - -```json -{ - "name": "@mendix/carousel-web", - "version": "2.3.2", - "mxpackage": { - "type": "widget" - }, - "marketplace": { - "appNumber": 47784, - "appName": "Carousel" - } -} -``` - -**Special case** - -- Widget `@mendix/charts-web` contains other widgets as if it is a module. -- Referencing dependencies, versioning and changelog structure works the same way as for module → widget dependency -- Should be still be referred as normal widget, no extra treatment (dependencies is only an extra step in build process) - -### Modules - -**Location**: `packages/modules/*/` - -**Characteristics**: - -- Has `mxpackage.type: "module"` in `package.json` -- Has `marketplace.appNumber` (published to marketplace) -- Lists dependent widgets in `mxpackage.dependencies` array, might be empty -- Has own CHANGELOG.md entries and additionally aggregates widget changelogs -- Contain other Mendix elements in addition to widgets: JS actions, nanoflows, pages, entities, etc. Those are not referenced in package.json - -**Example**: `@mendix/data-widgets` - -```json -{ - "name": "@mendix/data-widgets", - "version": "3.9.0", - "mxpackage": { - "type": "module", - "dependencies": [ - "@mendix/datagrid-web", - "@mendix/datagrid-date-filter-web", - "@mendix/gallery-web" - // ... more widgets - ] - }, - "marketplace": { - "appNumber": 116540, - "appName": "Data Widgets" - } -} -``` - -### Dependent Widgets - -**Location**: `packages/pluggableWidgets/*/` -**Not returned as top-level release candidates** - appear in `dependentWidgets` array - -**Characteristics**: - -- Does NOT have `marketplace.appNumber` in `package.json` -- Listed in parent's `mxpackage.dependencies` array -- Released only as part of their parent -- Version is set to match the parent version on each release cycle -- Has own CHANGELOG.md (aggregated into parent changelog on release) - -**Example**: `@mendix/datagrid-web` - -```json -{ - "name": "@mendix/datagrid-web", - "version": "3.9.0", - "mxpackage": { - "type": "widget" - }, - "marketplace": { - "appName": "Data Grid 2" - // Note: no appNumber - } -} -``` +For an overview of package types (widgets, modules, dependent widgets) see [package-types.md](./package-types.md). ## Version Management -### Packages without dependencies +### Standalone package w/o dependencies - **Version tracking**: Each package has its own independent version - **Semantic versioning rules**: @@ -123,7 +16,7 @@ This document describes the release process for Mendix web widgets and modules i - **Minor**: New features, backward-compatible changes - **Major**: Breaking changes, major rewrites -### Packages with dependencies +### Standalone packages with dependencies - **Version tracking**: Parent package and all dependent widgets share the same version - When parent releases version `3.8.0`, ALL dependent widgets become `3.8.0` From 3566af28896ef33a0dd52ff717246d9d0f4d6ff0 Mon Sep 17 00:00:00 2001 From: Roman Vyakhirev Date: Tue, 12 May 2026 14:29:28 +0200 Subject: [PATCH 4/4] chore: describe changelogs format in a separate file --- .../utils/src/changelog-parser/types.ts | 2 +- docs/release-process/changelog-files.md | 129 ++++++++++++++++++ docs/release-process/release-workflow.md | 115 ++-------------- packages/modules/data-widgets/CHANGELOG.md | 2 +- packages/modules/web-actions/CHANGELOG.md | 2 +- 5 files changed, 143 insertions(+), 107 deletions(-) create mode 100644 docs/release-process/changelog-files.md diff --git a/automation/utils/src/changelog-parser/types.ts b/automation/utils/src/changelog-parser/types.ts index 5ec8e39c2a..b63fe94052 100644 --- a/automation/utils/src/changelog-parser/types.ts +++ b/automation/utils/src/changelog-parser/types.ts @@ -41,7 +41,7 @@ export interface ModuleUnreleasedVersionEntry extends UnreleasedVersionEntry { } export interface LogSection { - type: "Fixed" | "Added" | "Changed" | "Removed"; + type: "Fixed" | "Added" | "Changed" | "Removed" | "Deprecated" | "Security" | "Breaking changes" | "Documentation"; logs: string[]; } diff --git a/docs/release-process/changelog-files.md b/docs/release-process/changelog-files.md new file mode 100644 index 0000000000..bfc128f8ad --- /dev/null +++ b/docs/release-process/changelog-files.md @@ -0,0 +1,129 @@ +--- +description: This file describes how changelog files are organized, read ONLY if you need to understand the internal structure of them. If you need to work with changelogs, use high level tools for this, not the knowledge from this file. +alwaysApply: false +--- + +# Changelog Formats + +All changelogs follow the [Keep a Changelog](https://keepachangelog.com/) format with Mendix-specific extensions. + +There are two types of changelogs, **module** type and **widget** type. All share the same concepts, but differ in representation. + +Changelogs always follows the same pattern: + +**Header**: Has "Changelog" heading and some default wording almost identical for all changelogs. + +**Standard sections**: + +- `[Unreleased]` - Unreleased changes, only one at the top to keep unreleased entries before the next release +- `[X.Y.Z] - YYYY-MM-DD` - Released versions, multiple entries like this, represent released versions + +Every section has **Change categories**: + +- `Fixed` - Bug fixes +- `Added` - New features +- `Changed` - Changes to existing functionality +- `Removed` - Removed features +- `Deprecated` - Features that will be removed in upcoming releases +- `Security` - Security-related fixes or improvements +- `Breaking changes` - Breaking changes +- `Documentation` - Documentation-only changes + +Every change category has a changelog entry as a bullet point: + +- `- We added a new exciting column sorting feature` + or +- `- We fixed an issue with column sorting` + +While Widget changelog follows this exactly, module has extra differences, explained below. + +### Widget Changelogs + +Usually found in widgets: `packages/pluggableWidgets/*/CHANGELOG.md` + +Example for widget `@mendix/datagrid-web`: + +```markdown +# Changelog + +All notable changes to this widget will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +## [3.9.0] - 2026-03-23 + +### Changed + +- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. + +### Added + +- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes. + +### Fixed + +- We fixed an issue with Data export crashing on some Android devices. +``` + +### Module Changelogs + +Usually found in modules: `packages/modules/*/CHANGELOG.md` + +Example format for module `@mendix/data-widgets`: + +```markdown +# Changelog + +All notable changes to this module will be documented in this file. + +The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). + +## [Unreleased] + +### Changed + +- We changed something at module level that does not belong to a specific widget. + +## [3.10.0] DataWidgets - 2026-05-06 + +### Changed + +- We changed some stylings hardcoded values to have a better support for css variables. + +### [3.10.0] DatagridDropdownFilter + +#### Fixed + +- We fixed an issue with Dropdown filter captions not updating properly when their template parameters change. + +### [3.10.0] Datagrid + +#### Changed + +- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. + +#### Added + +- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded. + +#### Fixed + +- We fixed an issue with Data export crashing on some Android devices. + +### [3.10.0] Gallery + +#### Fixed + +- We fixed the pagination properties `Page attribute`, `Page size attribute`, and `Total count` not being shown. +``` + +**Key differences**: + +- Module name in version header: `[X.Y.Z] ModuleName - YYYY-MM-DD` +- Next to standard Change categories that describe changes to the module directly, they aggregate changes in dependent widgets: `### [3.10.0] Datagrid`, `### [3.10.0] Gallery`, etc +- Structure for each widget stays the same as for Widget changelog but every section is one h-level deeper as they fall under the module header +- Aggregates all widget changelogs into single module changelog, changes are duplicated into this changelog on release. + +`[Unreleased]` section in modules works the same way as in widgets — holds all pending module changes, but not changes in the dependent widgets. diff --git a/docs/release-process/release-workflow.md b/docs/release-process/release-workflow.md index b39edb3c82..fddf7e9349 100644 --- a/docs/release-process/release-workflow.md +++ b/docs/release-process/release-workflow.md @@ -8,13 +8,15 @@ For an overview of package types (widgets, modules, dependent widgets) see [pack ## Version Management +### Semantic versioning rules + +- **Patch**: Bug fixes, small improvements +- **Minor**: New features, backward-compatible changes +- **Major**: Breaking changes, major rewrites, happens rarely + ### Standalone package w/o dependencies -- **Version tracking**: Each package has its own independent version -- **Semantic versioning rules**: - - **Patch**: Bug fixes, small improvements - - **Minor**: New features, backward-compatible changes - - **Major**: Breaking changes, major rewrites +- **Version tracking**: Each package has its own independent version, follows Semantic versioning rules. ### Standalone packages with dependencies @@ -30,114 +32,19 @@ For an overview of package types (widgets, modules, dependent widgets) see [pack - `@mendix/dropdown-sort-web` → `3.9.0` - ... all widgets in the module → `3.9.0` -## Changelog Management - -### Format - -All changelogs follow the [Keep a Changelog](https://keepachangelog.com/) format with Mendix-specific extensions. - -**Standard sections**: +Same Semantic versioning rules but based on all widgets. -- `## [Unreleased]` - Unreleased changes -- `## [X.Y.Z] - YYYY-MM-DD` - Released versions - -**Change categories**: - -- `### Fixed` - Bug fixes -- `### Added` - New features -- `### Changed` - Changes to existing functionality -- `### Removed` - Removed features +## Changelog Management ### Workflow -1. **During development**: Developer adds entries under `## [Unreleased]` section +1. **During development**: Developer adds entries under `[Unreleased]` changelog section 2. **On merge to main**: Changes merged with unreleased changelog entries (no version bump yet) 3. **Release decision**: Team decides to release based on: - Unreleased changes exist - Jira story is complete - Team decision (may wait to bundle multiple stories) -4. **On release**: GitHub workflow moves unreleased entries to new version section - -### Widget Changelogs - -**Location**: `packages/pluggableWidgets/*/CHANGELOG.md` - -**Format** (for widget `@mendix/datagrid-web`): - -```markdown -# Changelog - -All notable changes to this widget will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [3.9.0] - 2026-03-23 - -### Changed - -- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. - -### Added - -- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes. - -### Fixed - -- We fixed an issue with Data export crashing on some Android devices. -``` - -### Module Changelogs - -**Location**: `packages/modules/*/CHANGELOG.md` - -**Format** (for module `@mendix/data-widgets`): - -```markdown -# Changelog - -All notable changes to this widget will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [3.9.0] DataWidgets - 2026-03-23 - -### [3.9.0] DatagridDropdownFilter - -#### Fixed - -- We fixed an issue with Dropdown filter captions not updating properly when their template parameters change. - -### [3.9.0] Datagrid - -#### Changed - -- We improved accessibility on column selector, added aria-attributes and changed the role to 'menuitemcheckbox'. - -#### Added - -- We added a new `Loaded rows` attribute that reflects the number of rows currently loaded for virtual scrolling and load-more pagination modes. - -#### Fixed - -- We fixed an issue with Data export crashing on some Android devices. - -### [3.9.0] Gallery - -#### Fixed - -- We fixed the pagination properties `Page attribute`, `Page size attribute`, and `Total count` not being shown in Studio Pro for Virtual Scrolling and Load More pagination modes. -``` - -**Key differences**: - -- Module name in version header: `[3.9.0] DataWidgets - 2026-03-23` -- Subcomponent sections for each widget: `### [3.9.0] Datagrid` -- Aggregates all widget changelogs into single module changelog -- Generated automatically by GitHub workflow during release — the workflow reads each dependent widget's flat `## [Unreleased]` entries and transforms them into the nested `### [X.Y.Z] WidgetName` format shown above +4. **On release**: GitHub workflow moves unreleased entries to new version section, `[Unreleased]` is cleared. ## Release Preparation Process diff --git a/packages/modules/data-widgets/CHANGELOG.md b/packages/modules/data-widgets/CHANGELOG.md index 9178733355..a2cdaa9983 100644 --- a/packages/modules/data-widgets/CHANGELOG.md +++ b/packages/modules/data-widgets/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -All notable changes to this widget will be documented in this file. +All notable changes to this module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). diff --git a/packages/modules/web-actions/CHANGELOG.md b/packages/modules/web-actions/CHANGELOG.md index 3e67d844ab..d348475658 100644 --- a/packages/modules/web-actions/CHANGELOG.md +++ b/packages/modules/web-actions/CHANGELOG.md @@ -1,6 +1,6 @@ # Changelog -All notable changes to this widget will be documented in this file. +All notable changes to this module will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).