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 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)))); 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 { diff --git a/.github/workflows/e2e-timings.yml b/.github/workflows/e2e-timings.yml index 4c36bc238..af2c58955 100644 --- a/.github/workflows/e2e-timings.yml +++ b/.github/workflows/e2e-timings.yml @@ -30,6 +30,7 @@ jobs: uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12 with: runTests: false + - name: Cypress run uses: cypress-io/github-action@18a6541367f4580a515371905f499a27a44e8dbe # v6.7.12 id: cypress @@ -45,6 +46,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..1dbfc41d0 --- /dev/null +++ b/scripts/compare-timings.ts @@ -0,0 +1,115 @@ +/** + * 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; + duration: number; +} + +interface TimingsFile { + durations: Timing[]; +} + +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 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: [] }; + } +} + +function readNewTimings(): TimingsFile { + return JSON.parse(fs.readFileSync(TIMINGS_PATH, 'utf8')); +} + +function cleanupFiles({ keepNew, reason }: CleanupOptions): void { + if (keepNew) { + log(`Keeping new timings: ${reason}`); + } else { + log(`Reverting to old timings: ${reason}`); + execSync(`git checkout HEAD -- ${TIMINGS_FILE}`); + } +} + +function compareTimings(): void { + 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)); + + // 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); + const changePercent = change / oldTiming.duration; + return { spec: newTiming.spec, change, changePercent }; + }); + + // 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: 5s and 20%)'); + return cleanupFiles({ keepNew: false, reason: 'No significant timing changes' }); + } + + log('Significant timing changes:'); + significantChanges.forEach((t) => { + log(`${t.spec}: ${t.change.toFixed(1)}ms (${(t.changePercent * 100).toFixed(1)}%)`); + }); + + cleanupFiles({ keepNew: true, reason: 'Significant timing changes detected' }); +} + +compareTimings();