From 92c0aa433101b8ce7477690a8a4062e55c2547e4 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 7 Apr 2025 17:41:14 +0800 Subject: [PATCH 1/6] build(esbuild): support multiple IIFE packages Support multiple IIFE packages by namespacing all of them into a `globalThis.__esbuild_esm_mermaid_nm` object. --- .esbuild/util.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/.esbuild/util.ts b/.esbuild/util.ts index 6d6d1d59b..dde0352af 100644 --- a/.esbuild/util.ts +++ b/.esbuild/util.ts @@ -58,6 +58,7 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => { format, minify, options: { name, file, packageName }, + globalName = 'mermaid', } = options; const external: string[] = ['require', 'fs', 'path']; const outFileName = getFileName(name, options); @@ -68,6 +69,7 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => { }, metafile, minify, + globalName, logLevel: 'info', chunkNames: `chunks/${outFileName}/[name]-[hash]`, define: { @@ -89,11 +91,12 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => { if (format === 'iife') { output.format = 'iife'; output.splitting = false; - output.globalName = '__esbuild_esm_mermaid'; + const originalGlobalName = output.globalName ?? 'mermaid'; + output.globalName = `__esbuild_esm_mermaid_nm[${JSON.stringify(originalGlobalName)}]`; // Workaround for removing the .default access in esbuild IIFE. // https://github.com/mermaid-js/mermaid/pull/4109#discussion_r1292317396 output.footer = { - js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;', + js: `globalThis[${JSON.stringify(originalGlobalName)}] = globalThis.${output.globalName}.default;`, }; output.outExtension = { '.js': '.js' }; } else { From 535121125663accd83e5e8b9d5a3e73b5ea7ac84 Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 7 Apr 2025 16:49:40 +0800 Subject: [PATCH 2/6] build(zenuml): add back IIFE ZenUML builds These are present in [v0.2.0][1] so removing these would be a breaking change. I suspect we accidentally removed them when we moved from Vite to ESBuild. [1]: https://www.npmjs.com/package/@mermaid-js/mermaid-zenuml/v/0.2.0 --- .esbuild/build.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/.esbuild/build.ts b/.esbuild/build.ts index 423e8f047..05002cb16 100644 --- a/.esbuild/build.ts +++ b/.esbuild/build.ts @@ -34,6 +34,19 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => { { ...iifeOptions, minify: true, metafile: shouldVisualize } ); } + if (entryName === 'mermaid-zenuml') { + const iifeOptions: MermaidBuildOptions = { + ...commonOptions, + format: 'iife', + globalName: 'mermaid-zenuml', + }; + buildConfigs.push( + // mermaid-zenuml.js + { ...iifeOptions }, + // mermaid-zenuml.min.js + { ...iifeOptions, minify: true, metafile: shouldVisualize } + ); + } const results = await Promise.all(buildConfigs.map((option) => build(getBuildConfig(option)))); From 03119fea2c6f88927676d25d4d09a808b1a1807a Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Mon, 7 Apr 2025 17:02:30 +0800 Subject: [PATCH 3/6] chore(zenuml): add pending ZenUML changesets Add ZenUML changesets for: - Bumping the minimum version of ZenUML to 3.23.28 - Limiting the `peerDepdencies` to v10 and v11 See: 9d06d8f31e7f12af9e9e092214f907f2dc93ad75 See: 0ad44c12feead9d20c6a870a49327ada58d6e657 --- .changeset/honest-trees-dress.md | 7 +++++++ .changeset/yellow-mirrors-change.md | 7 +++++++ 2 files changed, 14 insertions(+) create mode 100644 .changeset/honest-trees-dress.md create mode 100644 .changeset/yellow-mirrors-change.md diff --git a/.changeset/honest-trees-dress.md b/.changeset/honest-trees-dress.md new file mode 100644 index 000000000..054f1bedb --- /dev/null +++ b/.changeset/honest-trees-dress.md @@ -0,0 +1,7 @@ +--- +'@mermaid-js/mermaid-zenuml': patch +--- + +chore: bump minimum ZenUML version to 3.23.28 + +commit: 9d06d8f31e7f12af9e9e092214f907f2dc93ad75 diff --git a/.changeset/yellow-mirrors-change.md b/.changeset/yellow-mirrors-change.md new file mode 100644 index 000000000..09a766104 --- /dev/null +++ b/.changeset/yellow-mirrors-change.md @@ -0,0 +1,7 @@ +--- +'@mermaid-js/mermaid-zenuml': patch +--- + +fix(zenuml): limit `peerDependencies` to Mermaid v10 and v11 + +commit: 0ad44c12feead9d20c6a870a49327ada58d6e657 From 7a5f999f42b28ce0d9ec77b9ad2752720e9eec4b Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 9 Apr 2025 12:08:57 +0530 Subject: [PATCH 4/6] chore: Add compare-timings script Avoid creating unnecessary PRs when there is no significant timings change. --- .github/workflows/e2e-timings.yml | 8 +++ scripts/compare-timings.ts | 88 +++++++++++++++++++++++++++++++ 2 files changed, 96 insertions(+) create mode 100644 scripts/compare-timings.ts diff --git a/.github/workflows/e2e-timings.yml b/.github/workflows/e2e-timings.yml index 4c36bc238..85778f421 100644 --- a/.github/workflows/e2e-timings.yml +++ b/.github/workflows/e2e-timings.yml @@ -30,6 +30,10 @@ jobs: uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12 with: runTests: false + + - name: Copy previous timings + run: cp cypress/timings.json cypress/timings-old.json + - name: Cypress run uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12 id: cypress @@ -45,6 +49,10 @@ jobs: SPLIT: 1 SPLIT_INDEX: 0 SPLIT_FILE: 'cypress/timings.json' + + - name: Compare timings + run: pnpm tsx scripts/compare-timings.ts + - name: Commit and create pull request uses: peter-evans/create-pull-request@271a8d0340265f705b14b6d32b9829c1cb33d45e with: diff --git a/scripts/compare-timings.ts b/scripts/compare-timings.ts new file mode 100644 index 000000000..6c63190ad --- /dev/null +++ b/scripts/compare-timings.ts @@ -0,0 +1,88 @@ +import fs from 'node:fs'; +import path from 'node:path'; + +interface Timing { + spec: string; + duration: number; +} + +interface TimingsFile { + durations: Timing[]; +} + +const TIMINGS_PATH = path.join(process.cwd(), 'cypress', 'timings.json'); +const TIMINGS_OLD_PATH = path.join(process.cwd(), 'cypress', 'timings-old.json'); + +function log(message: string): void { + // eslint-disable-next-line no-console + console.log(message); +} + +function readTimings(filePath: string): TimingsFile { + return JSON.parse(fs.readFileSync(filePath, 'utf8')); +} + +interface CleanupOptions { + keepNew: boolean; + reason: string; +} + +function cleanupFiles({ keepNew, reason }: CleanupOptions): void { + if (keepNew) { + log(`Keeping new timings: ${reason}`); + fs.unlinkSync(TIMINGS_OLD_PATH); + } else { + log(`Reverting to old timings: ${reason}`); + fs.unlinkSync(TIMINGS_PATH); + fs.renameSync(TIMINGS_OLD_PATH, TIMINGS_PATH); + } +} + +function compareTimings(): void { + const oldTimings = readTimings(TIMINGS_OLD_PATH); + const newTimings = readTimings(TIMINGS_PATH); + + const oldSpecs = new Set(oldTimings.durations.map((d) => d.spec)); + const newSpecs = new Set(newTimings.durations.map((d) => d.spec)); + + // Check if specs were added or removed + const addedSpecs = [...newSpecs].filter((spec) => !oldSpecs.has(spec)); + const removedSpecs = [...oldSpecs].filter((spec) => !newSpecs.has(spec)); + + if (addedSpecs.length > 0 || removedSpecs.length > 0) { + log('Specs changed:'); + if (addedSpecs.length > 0) { + log(`Added: ${addedSpecs.join(', ')}`); + } + if (removedSpecs.length > 0) { + log(`Removed: ${removedSpecs.join(', ')}`); + } + return cleanupFiles({ keepNew: true, reason: 'Specs were added or removed' }); + } + + // Check timing variations + const timingChanges = newTimings.durations.map((newTiming) => { + const oldTiming = oldTimings.durations.find((d) => d.spec === newTiming.spec); + if (!oldTiming) { + throw new Error(`Could not find old timing for spec: ${newTiming.spec}`); + } + const change = Math.abs(newTiming.duration - oldTiming.duration) / oldTiming.duration; + return { spec: newTiming.spec, change }; + }); + + const significantChanges = timingChanges.filter((t) => t.change >= 0.2); + + if (significantChanges.length === 0) { + log('No significant timing changes detected (threshold: 20%)'); + return cleanupFiles({ keepNew: false, reason: 'No significant timing changes' }); + } + + log('Significant timing changes:'); + significantChanges.forEach((t) => { + log(`${t.spec}: ${(t.change * 100).toFixed(1)}%`); + }); + + cleanupFiles({ keepNew: true, reason: 'Significant timing changes detected' }); +} + +compareTimings(); From 97e35fd30a6fac8029ceb31ccdd1452a21fde006 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 9 Apr 2025 12:19:13 +0530 Subject: [PATCH 5/6] chore: Use git to read old timings --- .github/workflows/e2e-timings.yml | 3 -- scripts/compare-timings.ts | 49 +++++++++++++++++++++++-------- 2 files changed, 37 insertions(+), 15 deletions(-) diff --git a/.github/workflows/e2e-timings.yml b/.github/workflows/e2e-timings.yml index 85778f421..af2c58955 100644 --- a/.github/workflows/e2e-timings.yml +++ b/.github/workflows/e2e-timings.yml @@ -31,9 +31,6 @@ jobs: with: runTests: false - - name: Copy previous timings - run: cp cypress/timings.json cypress/timings-old.json - - name: Cypress run uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12 id: cypress diff --git a/scripts/compare-timings.ts b/scripts/compare-timings.ts index 6c63190ad..aaf070767 100644 --- a/scripts/compare-timings.ts +++ b/scripts/compare-timings.ts @@ -1,5 +1,22 @@ +/** + * Compares new E2E test timings with previous timings and determines whether to keep the new timings. + * + * The script will: + * 1. Read old timings from git HEAD + * 2. Read new timings from the current file + * 3. Compare the timings and specs + * 4. Keep new timings if: + * - Specs were added/removed + * - Any timing changed by 20% or more + * 5. Revert to old timings if: + * - No significant timing changes + * + * This helps prevent unnecessary timing updates when test performance hasn't changed significantly. + */ + import fs from 'node:fs'; import path from 'node:path'; +import { execSync } from 'node:child_process'; interface Timing { spec: string; @@ -10,37 +27,45 @@ interface TimingsFile { durations: Timing[]; } -const TIMINGS_PATH = path.join(process.cwd(), 'cypress', 'timings.json'); -const TIMINGS_OLD_PATH = path.join(process.cwd(), 'cypress', 'timings-old.json'); +interface CleanupOptions { + keepNew: boolean; + reason: string; +} + +const TIMINGS_FILE = 'cypress/timings.json'; +const TIMINGS_PATH = path.join(process.cwd(), TIMINGS_FILE); function log(message: string): void { // eslint-disable-next-line no-console console.log(message); } -function readTimings(filePath: string): TimingsFile { - return JSON.parse(fs.readFileSync(filePath, 'utf8')); +function readOldTimings(): TimingsFile { + try { + const oldContent = execSync(`git show HEAD:${TIMINGS_FILE}`, { encoding: 'utf8' }); + return JSON.parse(oldContent); + } catch { + log('Error getting old timings, using empty file'); + return { durations: [] }; + } } -interface CleanupOptions { - keepNew: boolean; - reason: string; +function readNewTimings(): TimingsFile { + return JSON.parse(fs.readFileSync(TIMINGS_PATH, 'utf8')); } function cleanupFiles({ keepNew, reason }: CleanupOptions): void { if (keepNew) { log(`Keeping new timings: ${reason}`); - fs.unlinkSync(TIMINGS_OLD_PATH); } else { log(`Reverting to old timings: ${reason}`); - fs.unlinkSync(TIMINGS_PATH); - fs.renameSync(TIMINGS_OLD_PATH, TIMINGS_PATH); + execSync(`git checkout HEAD -- ${TIMINGS_FILE}`); } } function compareTimings(): void { - const oldTimings = readTimings(TIMINGS_OLD_PATH); - const newTimings = readTimings(TIMINGS_PATH); + const oldTimings = readOldTimings(); + const newTimings = readNewTimings(); const oldSpecs = new Set(oldTimings.durations.map((d) => d.spec)); const newSpecs = new Set(newTimings.durations.map((d) => d.spec)); From f87d0dd88ae4db3231ce1a4dfe20b2d07389a938 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Wed, 9 Apr 2025 13:20:19 +0530 Subject: [PATCH 6/6] chore: Update change detection logic --- scripts/compare-timings.ts | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/scripts/compare-timings.ts b/scripts/compare-timings.ts index aaf070767..1dbfc41d0 100644 --- a/scripts/compare-timings.ts +++ b/scripts/compare-timings.ts @@ -91,20 +91,22 @@ function compareTimings(): void { if (!oldTiming) { throw new Error(`Could not find old timing for spec: ${newTiming.spec}`); } - const change = Math.abs(newTiming.duration - oldTiming.duration) / oldTiming.duration; - return { spec: newTiming.spec, change }; + const change = Math.abs(newTiming.duration - oldTiming.duration); + const changePercent = change / oldTiming.duration; + return { spec: newTiming.spec, change, changePercent }; }); - const significantChanges = timingChanges.filter((t) => t.change >= 0.2); + // Filter changes that's more than 5 seconds and 20% different + const significantChanges = timingChanges.filter((t) => t.change > 5000 && t.changePercent >= 0.2); if (significantChanges.length === 0) { - log('No significant timing changes detected (threshold: 20%)'); + log('No significant timing changes detected (threshold: 5s and 20%)'); return cleanupFiles({ keepNew: false, reason: 'No significant timing changes' }); } log('Significant timing changes:'); significantChanges.forEach((t) => { - log(`${t.spec}: ${(t.change * 100).toFixed(1)}%`); + log(`${t.spec}: ${t.change.toFixed(1)}ms (${(t.changePercent * 100).toFixed(1)}%)`); }); cleanupFiles({ keepNew: true, reason: 'Significant timing changes detected' });