Skip to content

chore(automation-utils): migrate to native ESM and drop ts-node - #2484

Open
r0b1n wants to merge 14 commits into
mainfrom
chore/automation-utils-esm
Open

r0b1n wants to merge 14 commits into
mainfrom
chore/automation-utils-esm

Conversation

@r0b1n

@r0b1n r0b1n commented Oct 21, 2026 •

Copy link
Copy Markdown
Collaborator

Pull request type

Refactoring (e.g. file rename, variable rename, etc.)

Dependency changes (any modification to dependencies in package.json)


Description

Moves @mendix/automation-utils, the module build/release scripts and the MCP servers from ts-node/tsc builds and CommonJS to native ESM, run directly by Node 24's built-in type stripping. ts-node is removed. Also unifies how scripts are type-checked and linted across packages.

automation/utils

  • "type": "module" plus an exports map; main/types removed.
  • All bins and scripts now run as node file.ts; shebangs changed from ts-node-script to node.
  • tsconfig: module/moduleResolution: NodeNext, allowImportingTsExtensions, erasableSyntaxOnly and verbatimModuleSyntax, so tsc flags anything Node can't strip. It now also covers the root entry files (index.ts, shell.ts, steps.ts, utils.ts).
  • Relative imports now carry explicit .ts extensions, type-only imports use inline type, <T>x casts became as T, and constructor parameter properties became plain fields.
  • CommonJS interop: shelljs and enquirer are now default imports. __dirname became import.meta.dirname, require.main became import.meta.main, and package.json is read with readFile + JSON.parse instead of a dynamic import.
  • lint is now eslint . package.json, so bin/ and package.json are linted too. This fixed 7 errors that were already in bin/ and adds cause to rethrown errors.
  • .prettierrc.js is renamed to .prettierrc.cjs.

Changelog parsers

  • peggy goes from 1.2.0 to 5.1.0 (pinned exactly). The parsers are generated with --format es as .mjs files.
  • The generated parsers are now committed, and prepare only runs tsc. Run pnpm run compile:parser after editing a .pegjs grammar; each grammar's header says so.
  • Fixed a mix-up in the compile scripts: compile:parser:module used to compile the widget grammar and compile:parser:widget the module grammar. Each name now matches its output.

Modules (calendar, data-widgets, file-uploader, google-tag, pusher, web-actions)

  • "type": "module" added to package.json, so the scripts stay plain .ts and run as node scripts/X.ts. .prettierrc.js is renamed to .prettierrc.cjs.
  • Every module now type-checks its scripts in lint (tsc --project scripts/tsconfig.json), and each scripts/tsconfig.json sets "include": ["."]. Before, four of them inherited automation-utils' include, so tsc checked none of their scripts. ts-node used to type-check scripts on every run; this replaces that check.
  • data-widgets: the rollup plugin imports in bundle-xlsx.ts are fixed for NodeNext typings.

charts-web

  • Its build script is scripts/build.mts. Widgets have no Node "type" field, and adding one would affect pluggable-widgets-tools, so .mts keeps it ESM.

MCP servers (e2e-mcp, marketplace-mcp)

  • They run from src/index.ts; the build/dev/postinstall tsc steps are removed, so pnpm i no longer compiles them and .mcp.json can't run a stale dist/. lint is a tsc type-check, and both are marked "private": true.
  • Anyone with a personal MCP config pointing at dist/index.js should switch it to src/index.ts.

Lint cleanups

  • calendar-web uses the standard eslint src/ package.json.
  • automation/scripts has lint enabled, with an eslint config like run-e2e's.
  • Removed eslint-config-web-widgets/.prettierrc.mjs. It used CommonJS inside an ES module and imported a file that doesn't exist; Prettier always picked the .prettierrc.js next to it.

Docs

  • widget-to-module.md describes the new module setup. The release-widget skill no longer mentions ts-node.

Notes

  • import.meta.main requires Node ≥ 24.2 (.nvmrc is 24.12).
  • ts-node remains in the lockfile as a dependency of @mendix/pluggable-widgets-tools; nothing in this repo uses it directly.

What should be covered while testing?

  • pnpm i runs utils' prepare (tsc only) and no longer builds the MCP servers.
  • pnpm lint passes for automation/utils, all modules, charts-web, calendar-web, automation/scripts and both MCP servers.
  • Changelog parsing: all 62 package changelogs parse with the 5.1.0 parsers, and rui-check-changelogs runs.
  • Verified locally: build:module for web-actions, data-widgets, calendar, file-uploader and pusher; charts-web pnpm turbo build; rui-package-info; rui-verify-package-format; both MCP servers answer an MCP handshake via .mcp.json, pnpm start and their bins.
  • google-tag build:module fails, and it fails the same way on main: scripts/build.ts copies GoogleTagActions.js, but the file is GoogleTagAction.js. Not fixed here.
  • Still worth exercising in CI: a release dry run.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

gjulivan
gjulivan previously approved these changes Oct 21, 2026
@r0b1n
r0b1n force-pushed the chore/automation-utils-esm branch from b4b9747 to bfbe914 Compare October 22, 2026 11:59
@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

@github-actions

This comment has been minimized.

rui-prepare-release.ts was the only bin without a shebang, and three bins were not
executable. tsconfig has noEmit, so outDir, declaration and noEmitOnError had no effect.
@github-actions

Copy link
Copy Markdown
Contributor

AI Code Review

🔶 Changes requested — one or more medium-severity items must be addressed


What was reviewed

File Change
automation/utils/package.json \"type\": \"module\", exports map, drop ts-node/peggy v1
automation/utils/tsconfig.json NodeNext module/moduleResolution, erasableSyntaxOnly, verbatimModuleSyntax
automation/utils/bin/rui-*.ts All bins: shebang → #!/usr/bin/env node, .ts-extension imports, enquirer default-import
automation/utils/src/*.ts __dirname → import.meta.dirname, require.main → import.meta.main, angle-cast → as, CJS interop
automation/utils/src/shell.ts shelljs destructured from default import
automation/utils/src/package-info.ts dynamic import(pkgPath) → readFile + JSON.parse
automation/utils/src/changelog-parser/ parsers regenerated with peggy 5.1.0 --format es, committed as .mjs
automation/e2e-mcp/, automation/marketplace-mcp/ Drop build/postinstall, run from src/index.ts, private: true
.mcp.json marketplace-mcp args → src/index.ts
packages/modules/{calendar,data-widgets,file-uploader,google-tag,pusher,web-actions}/ \"type\": \"module\", node in place of ts-node, lint: tsc added
packages/pluggableWidgets/charts-web/ build.ts → build.mts, node in place of ts-node
packages/shared/eslint-config-web-widgets/.prettierrc.mjs Deleted (used CJS require inside .mjs)
docs/requirements/widget-to-module.md, .agents/skills/release-widget/SKILL.md Updated to reflect node-native invocation
.gitignore, .prettierignore, pnpm-lock.yaml Housekeeping

Skipped (out of scope): dist/, pnpm-lock.yaml content beyond dep changes, generated .mjs parser bodies


Findings

🔶 Medium — Snyk security check failing

File: CI (security/snyk (AppDev AppStore content))
Problem: The security/snyk status check is currently FAILURE. This likely relates to the peggy major version bump (1.2.0 → 5.1.0), which pulls in @peggyjs/from-mem@3.1.3 as a new transitive dependency. The PR should not merge until the Snyk result is resolved — either the finding is a false positive that can be approved in the Snyk dashboard, or the vulnerability needs a fix.
Fix: Check the Snyk PR check URL in the CI details, identify the vulnerable path, and either accept the risk through the Snyk dashboard or pin a patched transitive. Note that peggy itself is a devDependency of automation/utils and is only used at parser compile time, so runtime exposure is zero — this context may be sufficient for a Snyk approval.


⚠️ Low — index.ts missing from files in automation/utils/package.json

File: automation/utils/package.json
Note: The exports map exposes "." → "./index.ts" but index.ts is not listed in the files array. For a workspace package resolved by pnpm this is harmless, but it's inconsistent — if the package is ever published the main entry would be absent. Adding "index.ts" to files closes the gap at zero cost.


⚠️ Low — esModuleInterop: true kept alongside verbatimModuleSyntax: true

File: automation/utils/tsconfig.json
Note: With verbatimModuleSyntax enabled, TypeScript enforces correct import/export syntax and esModuleInterop is effectively superseded for type-checking. Keeping both is harmless but mildly redundant. Not blocking.


Positives

  • Correct import.meta.main guard — rui-generate-package-xml.ts switched from require.main === module to import.meta.main. .nvmrc is 24.12 (≥ 24.2 required), so this is safe and idiomatic.
  • Bug fix for swapped parser compile scripts — compile:parser:module previously compiled the widget grammar and vice versa; each name now matches its output. Good catch.
  • readFile + JSON.parse replaces dynamic import(pkgPath) — Avoids Node's ESM module cache for JSON files, which was the correct problem to solve for the NodeNext migration.
  • Generated parsers committed with regeneration instructions — Each .pegjs grammar header explicitly says to run pnpm run compile:parser after editing, removing the silent mismatch risk.
  • private: true on both MCP packages — Prevents accidental npm publish; clean hygiene fix.
  • shelljs CJS interop comment — The inline comment in shell.ts explaining why named imports are unavailable under native ESM is exactly the kind of non-obvious context that belongs in source.
  • Broken .prettierrc.mjs removed — The file used module.exports = require(...) inside an .mjs file (always ESM), which silently failed. Correct removal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants