From 790521caec6b55d836a318378de83137365d2911 Mon Sep 17 00:00:00 2001 From: Arjo Bruijnes Date: Wed, 10 Jun 2026 17:04:25 +0200 Subject: [PATCH 1/5] Format generated types --- .../src/typings-generator/index.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/packages/pluggable-widgets-tools/src/typings-generator/index.ts b/packages/pluggable-widgets-tools/src/typings-generator/index.ts index 901415ef..ecb5dd55 100644 --- a/packages/pluggable-widgets-tools/src/typings-generator/index.ts +++ b/packages/pluggable-widgets-tools/src/typings-generator/index.ts @@ -1,12 +1,18 @@ -import { promises } from "fs"; +import { readFileSync, promises } from "fs"; import { join } from "path"; import { parseStringPromise } from "xml2js"; import { PackageXml } from "./PackageXml"; import { WidgetXml } from "./WidgetXml"; import { generateForWidget } from "./generate"; +import { format } from "prettier"; const { mkdir, readFile, stat, writeFile } = promises; +const prettierConfig = { + parser: "babel-ts", + ...JSON.parse(readFileSync(join(__dirname, "../../configs/prettier.base.json"), "utf-8")) +}; + export async function transformPackage(content: string, basePath: string) { const contentXml = (await parseStringPromise(content)) as PackageXml; if (!contentXml) { @@ -29,7 +35,7 @@ export async function transformPackage(content: string, basePath: string) { const sourcePath = widgetFileXml.$.path; const source = await readFile(join(basePath, sourcePath), "utf-8"); - let generatedContent; + let generatedContent: string; try { const sourceXml = (await parseStringPromise(source)) as WidgetXml; generatedContent = generateForWidget(sourceXml, toWidgetName(sourcePath)); @@ -39,8 +45,9 @@ export async function transformPackage(content: string, basePath: string) { ); } + const formattedContent = await format(generatedContent, prettierConfig); const resultPath = sourcePath.replace(/(\.xml)?$/, "Props.d.ts"); - await writeFile(join(resultBasePath, resultPath), generatedContent); + await writeFile(join(resultBasePath, resultPath), formattedContent); } } From a553feb36c592b8e1187dc326c02523ed6b1b965 Mon Sep 17 00:00:00 2001 From: Arjo Bruijnes Date: Wed, 10 Jun 2026 17:40:37 +0200 Subject: [PATCH 2/5] Use widget prettier config and base config as fallback --- .../src/typings-generator/index.ts | 11 ++------ .../src/utils/formatting.ts | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 8 deletions(-) create mode 100644 packages/pluggable-widgets-tools/src/utils/formatting.ts diff --git a/packages/pluggable-widgets-tools/src/typings-generator/index.ts b/packages/pluggable-widgets-tools/src/typings-generator/index.ts index ecb5dd55..727eccf7 100644 --- a/packages/pluggable-widgets-tools/src/typings-generator/index.ts +++ b/packages/pluggable-widgets-tools/src/typings-generator/index.ts @@ -1,18 +1,13 @@ -import { readFileSync, promises } from "fs"; +import { promises } from "fs"; import { join } from "path"; import { parseStringPromise } from "xml2js"; import { PackageXml } from "./PackageXml"; import { WidgetXml } from "./WidgetXml"; import { generateForWidget } from "./generate"; -import { format } from "prettier"; +import { formatTypeScript } from "../utils/formatting"; const { mkdir, readFile, stat, writeFile } = promises; -const prettierConfig = { - parser: "babel-ts", - ...JSON.parse(readFileSync(join(__dirname, "../../configs/prettier.base.json"), "utf-8")) -}; - export async function transformPackage(content: string, basePath: string) { const contentXml = (await parseStringPromise(content)) as PackageXml; if (!contentXml) { @@ -45,7 +40,7 @@ export async function transformPackage(content: string, basePath: string) { ); } - const formattedContent = await format(generatedContent, prettierConfig); + const formattedContent = await formatTypeScript(generatedContent); const resultPath = sourcePath.replace(/(\.xml)?$/, "Props.d.ts"); await writeFile(join(resultBasePath, resultPath), formattedContent); } diff --git a/packages/pluggable-widgets-tools/src/utils/formatting.ts b/packages/pluggable-widgets-tools/src/utils/formatting.ts new file mode 100644 index 00000000..be5aea72 --- /dev/null +++ b/packages/pluggable-widgets-tools/src/utils/formatting.ts @@ -0,0 +1,28 @@ +import { readFile } from "fs/promises"; +import { join } from "path"; +import { resolveConfig, format, Options } from "prettier"; +import { cwd } from "process"; + +const prettierConfigBasePath = join(__dirname, "../../configs/prettier.base.json"); + +let prettierTypescriptConfig: Options | undefined; + +/** + * Uses prettier to format the given TypeScript sourcecode. + * @param source The TypeScript snippet that needs to be formatted + */ +export async function formatTypeScript(source: string): Promise { + if (prettierTypescriptConfig === undefined) { + const fakeFilename = join(cwd(), "./src/widget.ts"); + // If the widget does not have a prettier config, fall back to packaged base config + const prettierConfig = + (await resolveConfig(fakeFilename)) ?? JSON.parse(await readFile(prettierConfigBasePath, "utf-8")); + + prettierTypescriptConfig = { + ...prettierConfig, + parser: "babel-ts" + }; + } + + return await format(source, prettierTypescriptConfig); +} From e48757dfdb53068a1c958810c9d88e1befe351e3 Mon Sep 17 00:00:00 2001 From: Arjo Bruijnes Date: Wed, 10 Jun 2026 17:07:09 +0200 Subject: [PATCH 3/5] Bump version to 11.12 --- packages/pluggable-widgets-tools/package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/pluggable-widgets-tools/package.json b/packages/pluggable-widgets-tools/package.json index 1e3c2ae4..8d83977c 100644 --- a/packages/pluggable-widgets-tools/package.json +++ b/packages/pluggable-widgets-tools/package.json @@ -1,6 +1,6 @@ { "name": "@mendix/pluggable-widgets-tools", - "version": "11.11.0", + "version": "11.12.0", "description": "Mendix Pluggable Widgets Tools", "engines": { "node": ">=20" From 9b75666cf72a384a829be41c1f62747485d66ed8 Mon Sep 17 00:00:00 2001 From: Arjo Bruijnes Date: Thu, 18 Jun 2026 14:11:59 +0200 Subject: [PATCH 4/5] Update release notes --- packages/pluggable-widgets-tools/CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/packages/pluggable-widgets-tools/CHANGELOG.md b/packages/pluggable-widgets-tools/CHANGELOG.md index aa700345..14e5efb0 100644 --- a/packages/pluggable-widgets-tools/CHANGELOG.md +++ b/packages/pluggable-widgets-tools/CHANGELOG.md @@ -19,6 +19,11 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), - The `jest-jasmine2` runner has been removed. Tests using Jasmine-specific globals (`jasmine.createSpy()`, `jasmine.objectContaining()`, etc.) will throw `ReferenceError: jasmine is not defined`. Replace with Jest equivalents: `jest.fn()`, `expect.objectContaining()`. - Consumers who extended the base config with `globals['ts-jest']` options must migrate those settings to the `@swc/jest` transform config. +### Fixed + +- We updated the type generator to format types according to prettier. Inconsistencies would block the `release` command. + + ## [11.11.0] - 2026-06-04 ### Added From 7aae11b7ae283a3af94b135965cf9fae5ad91615 Mon Sep 17 00:00:00 2001 From: Arjo Bruijnes Date: Tue, 23 Jun 2026 16:06:40 +0200 Subject: [PATCH 5/5] Centralize the definitions of common widget paths --- .../pluggable-widgets-tools/bin/mx-scripts.js | 19 +++---- .../configs/rollup.config.mjs | 31 ++++------- .../configs/rollup.config.native.mjs | 30 ++++------- .../configs/shared.mjs | 22 ++------ .../src/commands/audit.ts | 3 +- .../src/utils/enzyme-detector.ts | 6 +-- .../src/utils/formatting.ts | 16 +++--- .../src/utils/npmAudit.ts | 3 +- .../src/widget/package.ts | 53 +++++++++++++++++++ .../src/widget/paths.ts | 8 +++ .../utils/migration.js | 29 +++++----- 11 files changed, 123 insertions(+), 97 deletions(-) create mode 100644 packages/pluggable-widgets-tools/src/widget/package.ts create mode 100644 packages/pluggable-widgets-tools/src/widget/paths.ts diff --git a/packages/pluggable-widgets-tools/bin/mx-scripts.js b/packages/pluggable-widgets-tools/bin/mx-scripts.js index 708bf48f..56981568 100755 --- a/packages/pluggable-widgets-tools/bin/mx-scripts.js +++ b/packages/pluggable-widgets-tools/bin/mx-scripts.js @@ -1,12 +1,14 @@ #! /usr/bin/env node const { execSync, spawnSync } = require("child_process"); const { existsSync } = require("fs"); -const { delimiter, dirname, join, parse } = require("path"); +const { delimiter, join, parse } = require("path"); const { checkMigration } = require("../utils/migration"); const { checkForEnzymeUsage } = require("../dist/utils/enzyme-detector"); const { red, blue, bold, whiteBright } = require("ansi-colors"); const semver = require("semver"); -const { auditPluggableWidgetsTools } = require("../dist/commands/audit"); +const { auditPluggableWidgetsTools } = require("../dist/commands/audit.js"); +const { prettierConfigPath } = require("../dist/utils/formatting.js"); +const { toolsRoot, widgetRoot } = require("../dist/widget/paths.js"); checkNodeVersion(); (async () => { @@ -16,10 +18,7 @@ checkNodeVersion(); console.log(red("An error occurred while checking migration dependencies: ", e)); } - const [, currentScriptPath, cmd, ...args] = process.argv; - const toolsRoot = currentScriptPath.endsWith("pluggable-widgets-tools") - ? join(dirname(currentScriptPath), "../@mendix/pluggable-widgets-tools") - : join(dirname(currentScriptPath), ".."); + const [, , cmd, ...args] = process.argv; if (args.indexOf("--subprojectPath") > -1) { args.splice(args.indexOf("--subprojectPath"), 2); @@ -46,7 +45,7 @@ checkNodeVersion(); const commandWithArgs = realCommand + " " + args.join(" "); for (const subCommand of commandWithArgs.split(/&&/g)) { const result = spawnSync(subCommand.trim(), [], { - cwd: process.cwd(), + cwd: widgetRoot, env: { ...process.env, PATH: [process.env.PATH].concat(nodeModulesBins).join(delimiter), @@ -64,10 +63,8 @@ checkNodeVersion(); } })(); -function getRealCommand(cmd, toolsRoot) { +function getRealCommand(cmd) { const eslintCommand = "eslint --config .eslintrc.js --ext .jsx,.js,.ts,.tsx src"; - const prettierConfigRootPath = join(__dirname, "../../../prettier.config.js"); - const prettierConfigPath = existsSync(prettierConfigRootPath) ? prettierConfigRootPath : "prettier.config.js"; const prettierCommand = `prettier --config "${prettierConfigPath}" "{src,typings,tests}/**/*.{js,jsx,ts,tsx,scss}"`; const rollupCommandWeb = `rollup --config "${join(toolsRoot, "configs/rollup.config.mjs")}"`; const rollupCommandNative = `rollup --config "${join(toolsRoot, "configs/rollup.config.native.mjs")}"`; @@ -147,7 +144,7 @@ function findNodeModulesBin() { } function checkNodeVersion() { - const packageJson = require(join(__dirname, "../package.json")); + const packageJson = require(join(toolsRoot, "./package.json")); const nodeRange = new semver.Range(packageJson.engines.node); console.log("Checking node and npm version..."); diff --git a/packages/pluggable-widgets-tools/configs/rollup.config.mjs b/packages/pluggable-widgets-tools/configs/rollup.config.mjs index 4f584b9f..346d6ee9 100644 --- a/packages/pluggable-widgets-tools/configs/rollup.config.mjs +++ b/packages/pluggable-widgets-tools/configs/rollup.config.mjs @@ -20,30 +20,21 @@ import postcss from "rollup-plugin-postcss"; import terser from "@rollup/plugin-terser"; import shelljs from "shelljs"; import { widgetTyping } from "./rollup-plugin-widget-typing.mjs"; -import { - editorConfigEntry, - isTypescript, - previewEntry, - projectPath, - sourcePath, - widgetEntry, - widgetName, - widgetPackage, - widgetVersion, - onwarn -} from "./shared.mjs"; +import { editorConfigEntry, isTypescript, previewEntry, projectPath, widgetEntry, onwarn } from "./shared.mjs"; import { copyLicenseFile, createMpkFile, licenseCustomTemplate } from "./helpers/rollup-helper.mjs"; import url from "./rollup-plugin-assets.mjs"; +import { widgetName, widgetOrganization, widgetVersion } from "../dist/widget/package.js"; +import { widgetRoot } from "../dist/widget/paths.js"; const { loadConfigFile } = rollupLoadConfigFile; const { cp } = shelljs; -const outDir = join(sourcePath, "/dist/tmp/widgets/"); -const outWidgetDir = join(widgetPackage.replace(/\./g, "/"), widgetName.toLowerCase()); +const outDir = join(widgetRoot, "/dist/tmp/widgets/"); +const outWidgetDir = join(widgetOrganization.replace(/\./g, "/"), widgetName.toLowerCase()); const outWidgetFile = join(outWidgetDir, `${widgetName}`); const absoluteOutPackageDir = join(outDir, outWidgetDir); -const mpkDir = join(sourcePath, "dist", widgetVersion); -const mpkFile = join(mpkDir, process.env.MPKOUTPUT ? process.env.MPKOUTPUT : `${widgetPackage}.${widgetName}.mpk`); +const mpkDir = join(widgetRoot, "dist", widgetVersion); +const mpkFile = join(mpkDir, process.env.MPKOUTPUT ? process.env.MPKOUTPUT : `${widgetOrganization}.${widgetName}.mpk`); const assetsDirName = "assets"; const absoluteOutAssetsDir = join(absoluteOutPackageDir, assetsDirName); const outAssetsDir = join(outWidgetDir, assetsDirName); @@ -208,8 +199,8 @@ export default async args => { }); } - const customConfigPathJS = join(sourcePath, "rollup.config.js"); - const customConfigPathESM = join(sourcePath, "rollup.config.mjs"); + const customConfigPathJS = join(widgetRoot, "rollup.config.js"); + const customConfigPathESM = join(widgetRoot, "rollup.config.mjs"); const existingConfigPath = existsSync(customConfigPathJS) ? customConfigPathJS : existsSync(customConfigPathESM) @@ -300,7 +291,7 @@ export default async args => { // configs affected by a change => we cannot know in advance which one will be "the last". // So we run the same logic for all configs, letting the last one win. command([ - async () => config.licenses && copyLicenseFile(sourcePath, outDir), + async () => config.licenses && copyLicenseFile(widgetRoot, outDir), async () => createMpkFile({ mpkDir, @@ -316,7 +307,7 @@ export default async args => { function getClientComponentPlugins() { return [ - isTypescript ? widgetTyping({ sourceDir: join(sourcePath, "src") }) : null, + isTypescript ? widgetTyping({ sourceDir: join(widgetRoot, "src") }) : null, clear({ targets: [outDir, mpkDir] }), command([ () => { diff --git a/packages/pluggable-widgets-tools/configs/rollup.config.native.mjs b/packages/pluggable-widgets-tools/configs/rollup.config.native.mjs index f864e3b9..62c5c82d 100644 --- a/packages/pluggable-widgets-tools/configs/rollup.config.native.mjs +++ b/packages/pluggable-widgets-tools/configs/rollup.config.native.mjs @@ -16,26 +16,18 @@ import terser from "@rollup/plugin-terser"; import shelljs from "shelljs"; import { widgetTyping } from "./rollup-plugin-widget-typing.mjs"; import { collectDependencies } from "./rollup-plugin-collect-dependencies.mjs"; -import { - editorConfigEntry, - isTypescript, - projectPath, - sourcePath, - widgetEntry, - widgetName, - widgetPackage, - widgetVersion, - onwarn -} from "./shared.mjs"; +import { editorConfigEntry, isTypescript, projectPath, widgetEntry, onwarn } from "./shared.mjs"; import { copyLicenseFile, createMpkFile, licenseCustomTemplate } from "./helpers/rollup-helper.mjs"; +import { widgetName, widgetOrganization, widgetVersion } from "../dist/widget/package.js"; +import { widgetRoot } from "../dist/widget/paths.js"; const { cp } = shelljs; const { blue } = colors; -const outDir = join(sourcePath, "/dist/tmp/widgets/"); -const outWidgetFile = join(widgetPackage.replace(/\./g, "/"), widgetName.toLowerCase(), `${widgetName}`); -const mpkDir = join(sourcePath, "dist", widgetVersion); -const mpkFile = join(mpkDir, process.env.MPKOUTPUT ? process.env.MPKOUTPUT : `${widgetPackage}.${widgetName}.mpk`); +const outDir = join(widgetRoot, "/dist/tmp/widgets/"); +const outWidgetFile = join(widgetOrganization.replace(/\./g, "/"), widgetName.toLowerCase(), `${widgetName}`); +const mpkDir = join(widgetRoot, "dist", widgetVersion); +const mpkFile = join(mpkDir, process.env.MPKOUTPUT ? process.env.MPKOUTPUT : `${widgetOrganization}.${widgetName}.mpk`); const extensions = [".js", ".jsx", ".tsx", ".ts"]; @@ -161,8 +153,8 @@ export default async args => { }); } - const customConfigPathJS = join(sourcePath, "rollup.config.js"); - const customConfigPathESM = join(sourcePath, "rollup.config.mjs"); + const customConfigPathJS = join(widgetRoot, "rollup.config.js"); + const customConfigPathESM = join(widgetRoot, "rollup.config.mjs"); const existingConfigPath = existsSync(customConfigPathJS) ? customConfigPathJS : existsSync(customConfigPathESM) @@ -237,7 +229,7 @@ export default async args => { // configs affected by a change => we cannot know in advance which one will be "the last". // So we run the same logic for all configs, letting the last one win. command([ - async () => config.licenses && copyLicenseFile(sourcePath, outDir), + async () => config.licenses && copyLicenseFile(widgetRoot, outDir), async () => createMpkFile({ mpkDir, @@ -253,7 +245,7 @@ export default async args => { function getClientComponentPlugins() { return [ - isTypescript ? widgetTyping({ sourceDir: join(sourcePath, "src") }) : null, + isTypescript ? widgetTyping({ sourceDir: join(widgetRoot, "src") }) : null, clear({ targets: [outDir, mpkDir] }), command([ () => { diff --git a/packages/pluggable-widgets-tools/configs/shared.mjs b/packages/pluggable-widgets-tools/configs/shared.mjs index 1cc81392..f95ae49c 100644 --- a/packages/pluggable-widgets-tools/configs/shared.mjs +++ b/packages/pluggable-widgets-tools/configs/shared.mjs @@ -1,10 +1,10 @@ -import { existsSync, readdirSync, promises as fs, readFileSync } from "node:fs"; +import { existsSync, readdirSync, promises as fs } from "node:fs"; import { join, relative } from "node:path"; import { config } from "dotenv"; import colors from "ansi-colors"; -import { throwOnIllegalChars, throwOnNoMatch } from "../dist/utils/validation.js"; +import { dir as sourcePath, widgetName, json } from "../dist/widget/package.js"; -config({ path: join(process.cwd(), ".env"), quiet: true }); +config({ path: join(sourcePath, ".env"), quiet: true }); export async function listDir(path) { const entries = await fs.readdir(path, { withFileTypes: true }); @@ -14,20 +14,6 @@ export async function listDir(path) { .concat(...(await Promise.all(entries.filter(e => e.isDirectory()).map(e => listDir(join(path, e.name)))))); } -export const sourcePath = process.cwd(); - -const widgetPackageJson = JSON.parse(readFileSync(join(sourcePath, "package.json"))); -export const widgetName = widgetPackageJson.widgetName; -export const widgetPackage = widgetPackageJson.packagePath; -export const widgetVersion = widgetPackageJson.version; -if (!widgetName || !widgetPackageJson) { - throw new Error("Widget does not define widgetName in its package.json"); -} - -throwOnIllegalChars(widgetName, "a-zA-Z", "The `widgetName` property in package.json"); -throwOnIllegalChars(widgetPackage, "a-zA-Z0-9_.-", "The `packagePath` property in package.json"); -throwOnNoMatch(widgetPackage, /^([a-zA-Z0-9_-]+.)*[a-zA-Z0-9_-]+$/, "The `packagePath` property in package.json"); - const widgetSrcFiles = readdirSync(join(sourcePath, "src")).map(file => join(sourcePath, "src", file)); export const widgetEntry = widgetSrcFiles.filter(file => file.match(new RegExp(`[/\\\\]${escape(widgetName)}\\.[jt]sx?$`, "i")) @@ -47,7 +33,7 @@ export const isTypescript = [widgetEntry, editorConfigEntry, previewEntry].some( export const projectPath = [ process.env.MX_PROJECT_PATH, - widgetPackageJson.config.projectPath, + json.config.projectPath, join(sourcePath, "tests/testProject") ].filter(path => path && existsSync(path))[0]; diff --git a/packages/pluggable-widgets-tools/src/commands/audit.ts b/packages/pluggable-widgets-tools/src/commands/audit.ts index b8e64ee2..5bd7e114 100644 --- a/packages/pluggable-widgets-tools/src/commands/audit.ts +++ b/packages/pluggable-widgets-tools/src/commands/audit.ts @@ -5,7 +5,7 @@ import { exec } from "node:child_process"; import { maxSatisfying, minSatisfying } from "semver"; import { confirm } from "../cli/confirm"; import { readFile, writeFile } from "node:fs"; -import { join } from "node:path"; +import { path as packageJsonPath } from "../widget/package"; const pluggableWidgetsTools = "@mendix/pluggable-widgets-tools" as NpmAudit.PackageName; @@ -58,7 +58,6 @@ export async function auditPluggableWidgetsTools(fix: boolean = false) { (fix || (await confirm("Add overrides to package.json for vulnerable packages?"))) ) { console.log("Adding overrides"); - const packageJsonPath = join(process.cwd(), "package.json"); const widgetPackage = await promisify(readFile)(packageJsonPath, "utf8").then(raw => JSON.parse(raw)); const overrides = updateable diff --git a/packages/pluggable-widgets-tools/src/utils/enzyme-detector.ts b/packages/pluggable-widgets-tools/src/utils/enzyme-detector.ts index 14c9e722..94978005 100644 --- a/packages/pluggable-widgets-tools/src/utils/enzyme-detector.ts +++ b/packages/pluggable-widgets-tools/src/utils/enzyme-detector.ts @@ -1,10 +1,10 @@ import { existsSync, readdirSync, readFileSync, statSync } from "fs"; import { join } from "path"; import { yellow } from "ansi-colors"; +import { widgetRoot } from "../widget/paths"; export function checkForEnzymeUsage(srcDir: string = "src"): void { - const projectRoot = process.cwd(); - const srcPath = join(projectRoot, srcDir); + const srcPath = join(widgetRoot, srcDir); if (!existsSync(srcPath)) { return; @@ -36,7 +36,7 @@ export function checkForEnzymeUsage(srcDir: string = "src"): void { content ) ) { - enzymeFiles.push(fullPath.replace(projectRoot, ".")); + enzymeFiles.push(fullPath.replace(widgetRoot, ".")); } } } catch (error) { diff --git a/packages/pluggable-widgets-tools/src/utils/formatting.ts b/packages/pluggable-widgets-tools/src/utils/formatting.ts index be5aea72..8f8498dd 100644 --- a/packages/pluggable-widgets-tools/src/utils/formatting.ts +++ b/packages/pluggable-widgets-tools/src/utils/formatting.ts @@ -1,9 +1,11 @@ -import { readFile } from "fs/promises"; +import { existsSync } from "fs"; import { join } from "path"; -import { resolveConfig, format, Options } from "prettier"; -import { cwd } from "process"; +import { format, Options } from "prettier"; +import { toolsRoot, widgetRoot } from "../widget/paths"; -const prettierConfigBasePath = join(__dirname, "../../configs/prettier.base.json"); +const baseConfigPath = join(toolsRoot, "./configs/prettier.base.json"); +const widgetConfigPath = join(widgetRoot, "./prettier.config.js"); +export const prettierConfigPath = existsSync(widgetConfigPath) ? widgetConfigPath : baseConfigPath; let prettierTypescriptConfig: Options | undefined; @@ -13,11 +15,7 @@ let prettierTypescriptConfig: Options | undefined; */ export async function formatTypeScript(source: string): Promise { if (prettierTypescriptConfig === undefined) { - const fakeFilename = join(cwd(), "./src/widget.ts"); - // If the widget does not have a prettier config, fall back to packaged base config - const prettierConfig = - (await resolveConfig(fakeFilename)) ?? JSON.parse(await readFile(prettierConfigBasePath, "utf-8")); - + const prettierConfig = await import(prettierConfigPath); prettierTypescriptConfig = { ...prettierConfig, parser: "babel-ts" diff --git a/packages/pluggable-widgets-tools/src/utils/npmAudit.ts b/packages/pluggable-widgets-tools/src/utils/npmAudit.ts index 1e98fc5b..9eabe7d3 100644 --- a/packages/pluggable-widgets-tools/src/utils/npmAudit.ts +++ b/packages/pluggable-widgets-tools/src/utils/npmAudit.ts @@ -2,6 +2,7 @@ import assert from "node:assert"; import { exec } from "node:child_process"; import { existsSync } from "node:fs"; import { join } from "node:path"; +import { widgetRoot } from "../widget/paths"; export type Report = { auditReportVersion: 2; @@ -70,7 +71,7 @@ export function collectVulnerabilities(report: Report, dependency: Dependency): } export async function run(): Promise { - const packageLock = join(process.cwd(), "package-lock.json"); + const packageLock = join(widgetRoot, "package-lock.json"); assert( existsSync(packageLock), "Expected to find an npm lockfile. To run npm audit, dependencies must be installed with npm." diff --git a/packages/pluggable-widgets-tools/src/widget/package.ts b/packages/pluggable-widgets-tools/src/widget/package.ts new file mode 100644 index 00000000..2b58e679 --- /dev/null +++ b/packages/pluggable-widgets-tools/src/widget/package.ts @@ -0,0 +1,53 @@ +import { existsSync, readFileSync } from "fs"; +import { throwOnIllegalChars, throwOnNoMatch } from "../utils/validation"; +import { join } from "path"; +import { cwd } from "process"; + +/** + * The full path to the root directory of the widget package + */ +export const dir = cwd(); + +/** + * The full path to the widget's package.json file + */ +export const path = join(dir, "package.json"); +if (!existsSync(path)) { + throw new Error(`"Could not locate the widget's package.json at "${path}"`); +} + +/** + * The contents of the widget's package.json + */ +export const json = JSON.parse(readFileSync(path, "utf-8")); + +/** + * The name of the widget + */ +export const widgetName: string = json.widgetName; +if (!widgetName) { + throw new Error("Widget does not define widgetName in its package.json"); +} +throwOnIllegalChars(widgetName, "a-zA-Z", "The `widgetName` property in package.json"); + +/** + * The organization name of the widget as defined by the `packagePath` package.json property. + */ +export const widgetOrganization: string = json.packagePath; +throwOnIllegalChars(widgetOrganization, "a-zA-Z0-9_.-", "The `packagePath` property in package.json"); +throwOnNoMatch(widgetOrganization, /^([a-zA-Z0-9_-]+.)*[a-zA-Z0-9_-]+$/, "The `packagePath` property in package.json"); + +/** + * The name of the widget package + */ +export const packageName: string = json.name; + +/** + * The version of the widget package. + */ +export const widgetVersion: string = json.version; + +/** + * The ID of the widget + */ +export const widgetId: string = [widgetOrganization, packageName, widgetName].join("."); diff --git a/packages/pluggable-widgets-tools/src/widget/paths.ts b/packages/pluggable-widgets-tools/src/widget/paths.ts new file mode 100644 index 00000000..5fced2fb --- /dev/null +++ b/packages/pluggable-widgets-tools/src/widget/paths.ts @@ -0,0 +1,8 @@ +import { join } from "path"; + +export { dir as widgetRoot } from "./package"; + +/** + * The full path to the root of the pluggable-widgets-tools + */ +export const toolsRoot = join(__dirname, "../.."); diff --git a/packages/pluggable-widgets-tools/utils/migration.js b/packages/pluggable-widgets-tools/utils/migration.js index 97730d81..f6dcbd02 100644 --- a/packages/pluggable-widgets-tools/utils/migration.js +++ b/packages/pluggable-widgets-tools/utils/migration.js @@ -1,12 +1,14 @@ const { join } = require("path"); -const { readJson, writeJson } = require("fs-extra"); +const { writeJson } = require("fs-extra"); const { execSync } = require("child_process"); const { red, green, yellow, whiteBright, bold } = require("ansi-colors"); const { copyFileSync, existsSync, mkdirSync, promises } = require("fs"); const { parseStringPromise } = require("xml2js"); const { rm } = require("fs/promises"); -const { auditPluggableWidgetsTools } = require("../dist/commands/audit"); -const { confirm } = require("../dist/cli/confirm"); +const { auditPluggableWidgetsTools } = require("../dist/commands/audit.js"); +const { confirm } = require("../dist/cli/confirm.js"); +const { toolsRoot, widgetRoot } = require("../dist/widget/paths.js"); +const widgetPackage = require("../dist/widget/package.js"); let requirePatch = false; const CheckType = { @@ -93,11 +95,11 @@ function replaceOldDependencies(listOfOutdatedDependencies, packageJson, key) { packageJson[key][dep.name] = dep.newVersion; if (dep.patch) { - const dir = join(process.cwd(), "patches"); + const dir = join(widgetRoot, "patches"); if (!existsSync(dir)) { mkdirSync(dir); } - copyFileSync(join(__dirname, "../patches", dep.patch), join(process.cwd(), "patches", dep.patch)); + copyFileSync(join(toolsRoot, "./patches", dep.patch), join(widgetRoot, "patches", dep.patch)); requirePatch = true; } console.log(green(`${dep.name}: ${red(dep.oldVersion)} -> ${dep.newVersion}`)); @@ -119,8 +121,7 @@ async function addExtraDependencies(packageJson, key) { } async function getExtraDependencies(packageJson, key) { - const sourceDir = process.cwd(); - const rawPackageXML = await promises.readFile(join(sourceDir, "src/package.xml"), "utf-8"); + const rawPackageXML = await promises.readFile(join(widgetRoot, "src/package.xml"), "utf-8"); if (!rawPackageXML) { throw new Error("package.xml file was not found, please check your src folder"); } @@ -142,7 +143,7 @@ async function getExtraDependencies(packageJson, key) { const parsedWidgetDefinitionXMLs = []; for (const widgetDefinitionXMLPath of widgetDefinitionXMLPaths) { const rawWidgetDefinitionXML = await promises.readFile( - join(sourceDir, "src/", widgetDefinitionXMLPath.$.path), + join(widgetRoot, "src/", widgetDefinitionXMLPath.$.path), "utf-8" ); if (!rawWidgetDefinitionXML) { @@ -181,13 +182,13 @@ async function getExtraDependencies(packageJson, key) { async function checkMigration() { const paths = { - packageJson: join(process.cwd(), "package.json"), - packageLock: join(process.cwd(), "package-lock.json"), - node_modules: join(process.cwd(), "node_modules") + packageJson: widgetPackage.path, + packageLock: join(widgetRoot, "package-lock.json"), + node_modules: join(widgetRoot, "node_modules") }; console.log("Checking if dependencies should be migrated..."); - const packageJson = await readJson(paths.packageJson); + const packageJson = widgetPackage.json; const args = process.argv; if (!args.includes("--skip-migration") && process.env.CI !== "true") { const outdatedDependencies = getOutdatedDependencies(packageJson.dependencies || {}); @@ -253,10 +254,10 @@ async function checkMigration() { await rm(paths.packageLock); console.log("Installing dependencies..."); - execSync(`npm install`, { cwd: process.cwd(), stdio: "inherit" }); + execSync(`npm install`, { cwd: widgetRoot, stdio: "inherit" }); execSync( `npx eslint --no-config-lookup --rule '@typescript-eslint/no-unused-vars: ["error", { enableAutofixRemoval: { imports: true } }]' --ext ts,tsx,js,jsx --parser @typescript-eslint/parser --plugin '@typescript-eslint' --fix ./src`, - { cwd: process.cwd(), stdio: "inherit" } + { cwd: widgetRoot, stdio: "inherit" } ); await auditPluggableWidgetsTools();