From 2ce87da0477bb7ae741dc4b874605b41bada5e04 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:16:09 +0100 Subject: [PATCH 01/44] Post reports as a single PR comment --- .github/actions/aggregate-report/action.yml | 135 ++++++++++++++++++ .github/actions/base-coverage/action.yml | 42 ++++++ .../mutation-tests-changed-files/action.yml | 58 +++----- .github/actions/unit-tests/action.yml | 27 ++-- .github/scripts/compare-coverage.js | 117 +++++++++++++++ .github/scripts/generate-report.js | 22 +++ .github/scripts/process-mutation.js | 93 ++++++++++++ .github/scripts/update-aggregated-comment.js | 103 +++++++++++++ .github/workflows/collaboration-manager.yml | 15 ++ .github/workflows/core.yml | 14 ++ .github/workflows/dom-adapters.yml | 41 ++++++ .github/workflows/model.yml | 14 ++ .github/workflows/ot-server.yml | 15 ++ .github/workflows/sdk.yml | 17 ++- .gitignore | 1 + packages/collaboration-manager/package.json | 2 +- packages/core/stryker.conf.mjs | 1 + packages/model/stryker.conf.mjs | 1 + 18 files changed, 666 insertions(+), 52 deletions(-) create mode 100644 .github/actions/aggregate-report/action.yml create mode 100644 .github/actions/base-coverage/action.yml create mode 100644 .github/scripts/compare-coverage.js create mode 100644 .github/scripts/generate-report.js create mode 100644 .github/scripts/process-mutation.js create mode 100644 .github/scripts/update-aggregated-comment.js diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml new file mode 100644 index 00000000..c5cebd8c --- /dev/null +++ b/.github/actions/aggregate-report/action.yml @@ -0,0 +1,135 @@ +name: Aggregate report +description: 'Process downloaded artifacts (downloaded with actions/download-artifact) and produce a markdown report section' +inputs: + package-name: + description: 'Full package name (workspace)' + required: true +outputs: + report: + description: 'Markdown report section generated by the action' + value: ${{ steps.section-report.outputs.report }} +runs: + using: 'composite' + steps: + - name: Download artifacts + uses: actions/download-artifact@v3 + + - name: Process coverage reports + uses: actions/github-script@v9 + id: process_test_report + with: + script: | + const core = require('@actions/core'); + const processReports = require('./.github/scripts/compare-coverage.js') + + core.setOutput('message', processReports('test-report', 'test-report-base')); + + - name: Process mutation report + uses: actions/github-script@v9 + id: process_mutation_report + with: + script: | + const core = require('@actions/core'); + const processReports = require('./.github/scripts/process-mutation.js') + + core.setOutput('message', processReports('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); + + - name: Generate markdown report + id: section-report + uses: actions/github-script@v6 + env: + TESTS_MESSAGE: ${{ steps.process_test_report.outputs.message }} + MUTATION_MESSAGE: ${{ steps.process_mutation_report.outputs.message }} + PACKAGE: ${{ inputs.package-name }} + with: + script: | + const core = require('@actions/core'); + const generateReport = require('./.github/scripts/generate-report.js').generateReport + const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; + + core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); + + - name: Resolve pull request number + id: resolve_pr + uses: actions/github-script@v9 + with: + script: | + const core = require('@actions/core'); + const github = require('@actions/github'); + (async () => { + const owner = github.context.repo.owner; + const repo = github.context.repo.repo; + let pr = (github.context.payload.pull_request && github.context.payload.pull_request.number) || github.context.issue.number; + + // try commit-associated PRs if not in context + if (!pr) { + const sha = github.context.sha; + + if (sha) { + try { + const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); + if (assoc && Array.isArray(assoc.data) && assoc.data.length > 0) { + const prObj = assoc.data.find(p => p.state === 'open') || assoc.data[0]; + pr = prObj.number; + } + } catch (e) { + core.info('Error while searching for PR associated with commit: ' + String(e)); + } + } + } + + // fallback: try to find an open PR by branch ref (refs/heads/) + if (!pr) { + const ref = github.context.ref || ''; + const headsPrefix = 'refs/heads/'; + if (ref.startsWith(headsPrefix)) { + const branch = ref.slice(headsPrefix.length); + try { + const pulls = await github.rest.pulls.list({ owner, repo, head: `${owner}:${branch}`, state: 'open' }); + if (pulls && Array.isArray(pulls.data) && pulls.data.length > 0) { + pr = pulls.data[0].number; + } + } catch (e) { + core.info('Error while searching for PR by branch ref: ' + String(e)); + } + } + } + + core.setOutput('pr-number', pr ? String(pr) : ''); + })(); + - name: Acquire comment-update lock + id: acquire_lock + uses: softprops/turnstyle@v2 + with: + key: aggregated-comment-${{ steps.resolve_pr.outputs.pr-number || github.ref_name || github.ref }} + timeout: 600 + + - name: Create or update PR comment with report + uses: actions/github-script@v6 + env: + REPORT: ${{ steps.section-report.outputs.report }} + PACKAGE: ${{ inputs.package-name }} + PR_NUMBER: ${{ steps.resolve_pr.outputs.pr-number }} + with: + script: | + const core = require('@actions/core'); + const updater = require('./.github/scripts/update-aggregated-comment.js'); + + (async () => { + try { + const report = process.env.REPORT || ''; + const packageName = process.env.PACKAGE; + const pr = process.env.PR_NUMBER || undefined; + await updater.updateAggregatedComment(report, packageName, pr); + } catch (err) { + core.error(err && err.stack ? err.stack : String(err)); + throw err; + } + })(); + + - name: Release comment-update lock + if: ${{ always() }} + uses: softprops/turnstyle@v2 + with: + key: aggregated-comment-${{ steps.resolve_pr.outputs.pr-number || github.ref_name || github.ref }} + action: 'release' diff --git a/.github/actions/base-coverage/action.yml b/.github/actions/base-coverage/action.yml new file mode 100644 index 00000000..8f788598 --- /dev/null +++ b/.github/actions/base-coverage/action.yml @@ -0,0 +1,42 @@ +name: Base coverage +description: "Checkout base ref, run package coverage and upload artifact" +inputs: + package-name: + description: 'Full package name (workspace)' + required: true + working-directory: + description: 'Working directory relative to repo root (package folder)' + required: true + +runs: + using: "composite" + steps: + - name: Checkout base ref + uses: actions/checkout@v4 + with: + ref: ${{ steps.resolve_base.outputs.base_ref }} + fetch-depth: 0 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version-file: .nvmrc + + - name: Install dependencies + run: yarn --frozen-lockfile + working-directory: ${{ inputs.working-directory }} + shell: bash + + - name: Run coverage + shell: bash + run: yarn workspace ${{ inputs.package-name }} test:coverage + working-directory: ${{ inputs.working-directory }} + + - name: Upload base coverage artifact + uses: actions/upload-artifact@v3 + with: + name: test-report-base + path: | + ${{ inputs.working-directory }}/coverage + ${{ inputs.working-directory }}/jest-report.json + diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index 1d764581..4bd60457 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -51,51 +51,27 @@ runs: uses: jwalton/gh-find-current-pr@v1 id: findPr - - name: Comment on successful mutation testing - uses: thollander/actions-comment-pull-request@v2 - if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'success' && ${{ steps.findPr.outputs.number != '' }} - with: - message: | - ✅ Mutation testing passed for `${{ inputs.working-directory }}` - - Report: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${{ steps.findPr.outputs.number }}?module=${{ inputs.package-name }} - -
- Mutated files -
-            ${{ join(fromJson(format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files))), '
') }} -
-
- comment_tag: mutation-tests for `${{ inputs.working-directory }}` - pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}} - - - name: Comment on failed mutation testing - uses: thollander/actions-comment-pull-request@v2 - if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'failure' && ${{ steps.findPr.outputs.number != '' }} - with: - message: | - ❌ Mutation testing hasn't passed score threshold for `${{ inputs.working-directory }}` - - Report: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${{ steps.findPr.outputs.number }}?module=${{ inputs.package-name }} + - name: Save mutated files list + if: steps.changed-files.outputs.src_any_changed == 'true' + shell: bash + run: | + echo "${{ format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files)) }}" > changed-files.json -
- Mutated files -
-            ${{ join(fromJson(format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files))), '
') }} -
-
- comment_tag: mutation-tests for `${{ inputs.working-directory }}` - pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}} + - name: Save empty list if no files changed + if: steps.changed-files.outputs.src_any_changed == 'false' + shell: bash + run: echo "[]" > changed-files.json - - name: Comment on empty changes - uses: thollander/actions-comment-pull-request@v2 - if: steps.changed-files.outputs.src_any_changed == 'false' && ${{ steps.findPr.outputs.number != '' }} + - name: Upload mutation report artifact + if: steps.changed-files.outputs.src_any_changed == 'true' + uses: actions/upload-artifact@v3 with: - message: | - ⏭️ No files to mutate for `${{ inputs.working-directory }}` + name: mutation-report + path: | + ${{ inputs.working-directory }}/reports/mutation/mutation.json + changed-files.json - comment_tag: mutation-tests for `${{ inputs.working-directory }}` - pr_number: ${{ steps.findPr.outputs.number != '' && steps.findPr.outputs.number || '1'}} + # reporting will be triggered by workflow_run on workflow completion - if: steps.changed-files.outputs.src_any_changed == 'true' && steps.run-mutation-tests.outcome == 'failure' shell: bash diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml index c6760569..a2ff4332 100644 --- a/.github/actions/unit-tests/action.yml +++ b/.github/actions/unit-tests/action.yml @@ -19,15 +19,24 @@ runs: with: package-name: ${{ inputs.package-name }} - # Find current PR's number - - uses: jwalton/gh-find-current-pr@v1 - id: findPr + - name: Run unit tests with coverage + if: ${{ github.ref != 'refs/heads/main' }} # Skip coverage on main branch to save time and resources + shell: bash + run: | + yarn workspace ${{ inputs.package-name }} test:coverage - - name: Run unit tests - uses: ArtiomTr/jest-coverage-report-action@v2 + - name: Upload coverage artifact (PRs only) + if: ${{ github.event_name == 'pull_request' }} + uses: actions/upload-artifact@v3 with: - custom-title: Coverage report for `${{ inputs.working-directory }}` + name: test-report + path: | + ${{ inputs.working-directory }}/coverage + ${{ inputs.working-directory }}/jest-report.json + + - name: Run base coverage action + uses: ./.github/actions/base-coverage + with: + package-name: ${{ inputs.package-name }} working-directory: ${{ inputs.working-directory }} - test-script: yarn workspace ${{ inputs.package-name }} test - package-manager: yarn - prnumber: ${{ steps.findPr.outputs.number }} + diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js new file mode 100644 index 00000000..f93cc675 --- /dev/null +++ b/.github/scripts/compare-coverage.js @@ -0,0 +1,117 @@ +const fs = require('fs'); +const path = require('path'); + +function findCoverageJson(dir) { + if (!dir) return null; + const report = path.join(dir, 'coverage', 'coverage-final.json'); + + + if (fs.existsSync(report)) { + try { + return JSON.parse(fs.readFileSync(report, 'utf8')); + } catch (report) { + // continue + } + } + + return null; +} + +function findJestJsonResults(dir) { + if (!dir) return null; + + const report = path.join(dir, 'jest-report.json') + + try { + const raw = fs.readFileSync(report, 'utf8'); + const obj = JSON.parse(raw); + + if (typeof obj === 'object' && obj !== null) { + const hasTests = ('numPassedTests' in obj) || ('numPassedTestSuites' in obj) || ('numPassedAssertions' in obj); + const hasSuites = ('numPassedTestSuites' in obj) || ('testResults' in obj && Array.isArray(obj.testResults)); + + if (hasTests || hasSuites) return obj; + } + } catch (e) { + // ignore parse errors + } + + return null; +} + +function pctFromCoverageSummary(obj, category) { + if (!obj) return null; + + if (obj.total && obj.total[category] && typeof obj.total[category].pct === 'number') return obj.total[category].pct; + + if (obj[category] && typeof obj[category].pct === 'number') return obj[category].pct; + + return null; +} + +function extractPassedCounts(jestJson) { + if (!jestJson) return { passedTests: null, passedSuites: null }; + const passedTests = ('numPassedTests' in jestJson) ? Number(jestJson.numPassedTests) : (jestJson.numPassedAssertions ? Number(jestJson.numPassedAssertions) : null); + let passedSuites = null; + if ('numPassedTestSuites' in jestJson) passedSuites = Number(jestJson.numPassedTestSuites); + else if (Array.isArray(jestJson.testResults)) { + passedSuites = jestJson.testResults.filter(r => r.status === 'passed').length; + } + return { passedTests: Number.isFinite(passedTests) ? passedTests : null, passedSuites: Number.isFinite(passedSuites) ? passedSuites : null }; +} + +function compute(headSummary, baseSummary) { + const categories = ['statements', 'branches', 'functions', 'lines']; + + const out = {}; + + for (const cat of categories) { + const headPct = pctFromCoverageSummary(headSummary, cat); + const basePct = pctFromCoverageSummary(baseSummary, cat); + + let delta = 'N/A'; + + if (headPct != null && basePct != null) { + delta = Number((headPct - basePct).toFixed(2)); + } + + out[cat] = { head: headPct, base: basePct, delta }; + } + return out; +} + +export default function processReports(headDir, baseDir) { + const headCov = findCoverageJson(headDir); + const baseCov = findCoverageJson(baseDir); + const headJest = findJestJsonResults(headDir); + + const categories = compute(headCov, baseCov); + const headCounts = extractPassedCounts(headJest); + +// prefer head counts; if missing, fallback to base counts; otherwise null + const tests = { + passedTests: headCounts.passedTests != null ? headCounts.passedTests : 'N/A', + passedSuites: headCounts.passedSuites != null ? headCounts.passedSuites : 'N/A' + }; + + + let message = `${tests.passedTests} tests passed in ${tests.passedSuites} suites.\n` + + message += `Branches coverage: ${categories.branches.head}\n`; + + let warning = ''; + + for (const cat in categories) { + if (categories[cat].delta < 0) { + warning += `Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; + } + } + + if (warning.length > 0) { + message += `> [!WARNING]\n> ${message}\n`; + } + + return message; +} + + diff --git a/.github/scripts/generate-report.js b/.github/scripts/generate-report.js new file mode 100644 index 00000000..20ced1ba --- /dev/null +++ b/.github/scripts/generate-report.js @@ -0,0 +1,22 @@ +#!/usr/bin/env node + +function generateReport(package, testMessage, mutationMessage) { + let md = ''; + + md += `\n`; + md += `## ${package}\n\n`; + + md += `### Unit tests report\n ${testMessage}\n\n`; + + if (mutationMessage !== '') { + md += `### Mutation tests report\n${mutationMessage}\n\n`; + } + + + md += `\n`; + + return md; +} + +module.exports = { generateReport }; + diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js new file mode 100644 index 00000000..39fcff38 --- /dev/null +++ b/.github/scripts/process-mutation.js @@ -0,0 +1,93 @@ +const fs = require('fs'); +const path = require('path'); + +function normalizeStatusToCanonical(s) { + if (s == null) return ''; + const st = String(s).toLowerCase(); + if (st.includes('surviv')) return 'Survived'; + if (st.includes('no') && st.includes('coverage')) return 'NoCoverage'; + if (st.includes('nocoverage')) return 'NoCoverage'; + if (st.includes('killed')) return 'Killed'; + if (st.includes('timeout')) return 'Timeout'; + if (st.includes('runtime')) return 'RuntimeError'; + if (st.includes('compile')) return 'CompileError'; + if (st.includes('ignored') || st.includes('skip')) return 'Ignored'; + if (st === 'survived') return 'Survived'; + if (st === 'killed') return 'Killed'; + // return original-ish with capitalization + return String(s); +} + +function getMetrics(obj) { + const mutants = []; + + if (obj.files) { + for (const fileEntry of Object.values(obj.files)) { + if (fileEntry && Array.isArray(fileEntry.mutants)) { + mutants.push(...fileEntry.mutants); + } + } + } + + const total = mutants.length; + const survived = mutants.filter(m => m.status === 'Survived').length; + const notCovered = mutants.filter(m => m.status === 'NoCoverage').length; + const killed = mutants.filter(m => m.status === 'Killed').length; + const timedOut = mutants.filter(m => m.status === 'TimedOut').length; + + const score = (killed + timedOut) / (killed + timedOut + notCovered + survived).toFixed(2); + + return { + total, + score, + survived, + notCovered, + thresholds: obj.thresholds, + }; +} + + +function processMutationReport(reportPath, changedFilesPath) { + if (!fs.existsSync(reportPath)) { + return ''; + } + + const raw = fs.readFileSync(reportPath, 'utf8'); + const obj = JSON.parse(raw); + const changedFiles = JSON.parse(fs.readFileSync(changedFilesPath, 'utf8')); + + if (changedFiles.length === 0) { + return 'No files to mutate found.'; + } + + const metrics = getMetrics(obj); + + let message = `Mutation tests run with mutation score ${metrics.score}%.\n`; + + if (metrics.survived) { + message += `Survived mutants: ${metrics.survived}\n`; + } + + if (metrics.notCovered) { + message += `Not covered mutants: ${metrics.notCovered}\n`; + } + + if (metrics.score < metrics.thresholds.low) { + message += `> [!CAUTION]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; + } else if (metrics.score < metrics.thresholds.high) { + message += `> [!WARNING]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; + } + + + return message; +} + +module.exports = { processMutationReport }; + +// If invoked directly from CLI, read the first arg as the report path and print JSON +if (require.main === module) { + const reportPath = process.argv[2]; + const res = processMutationReport(reportPath); + console.log(JSON.stringify(res, null, 2)); +} + diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js new file mode 100644 index 00000000..36aa68ba --- /dev/null +++ b/.github/scripts/update-aggregated-comment.js @@ -0,0 +1,103 @@ +#!/usr/bin/env node +// .github/scripts/update-aggregated-comment.js +// Exports: updateAggregatedComment(report, packageName, prNumber) + +const core = require('@actions/core'); +const github = require('@actions/github'); + +async function updateAggregatedComment(report, packageName, prNumber) { + if (!report) { + core.info('No report provided, skipping comment update.'); + return; + } + + // repo info (owner/repo) available from context + const owner = github.context.repo.owner; + const repo = github.context.repo.repo; + + // Allow caller to pass the PR number (from workflow / another action). If not provided, + // resolve PR number from the GitHub context. For push events there is no pull_request + // payload, so we attempt to find an associated PR by commit SHA as a fallback. + let pr; + + if (prNumber) { + const parsed = Number(prNumber); + if (!Number.isNaN(parsed) && parsed > 0) { + pr = parsed; + } + } + + if (!pr) { + pr = (github.context.payload.pull_request && github.context.payload.pull_request.number) || github.context.issue.number; + } + + if (!pr) { + const sha = github.context.sha; + if (sha) { + try { + const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); + if (assoc && Array.isArray(assoc.data) && assoc.data.length > 0) { + // Prefer an open PR if available + const prObj = assoc.data.find(p => p.state === 'open') || assoc.data[0]; + pr = prObj.number; + } + } catch (e) { + core.info('Error while searching for PR associated with commit: ' + String(e)); + } + } + } + + if (!pr) { + core.info('No pull request found in context or associated with the commit; skipping comment step.'); + return; + } + + // list comments on the PR (first page) + const res = await github.rest.issues.listComments({ owner, repo, issue_number: Number(pr), per_page: 100 }); + const comments = res && res.data ? res.data : []; + + const globalStart = ``; + const globalEnd = ``; + + // find existing global comment containing the aggregated markers + let existing = comments.find(c => c.body && c.body.includes(globalStart) && c.body.includes(globalEnd)); + + if (!existing) { + // create a new global comment that will hold all package sections + const body = `${globalStart}\n${report}\n${globalEnd}`; + await github.rest.issues.createComment({ owner, repo, issue_number: Number(pr), body }); + core.info('Created new aggregated PR comment with report.'); + return; + } + + // Update existing aggregated comment: replace package section if present, otherwise insert before global end marker + const body = existing.body || ''; + const pkgStart = ``; + const pkgEnd = ``; + const psi = body.indexOf(pkgStart); + const pei = body.indexOf(pkgEnd); + let newBody; + + if (psi !== -1 && pei !== -1 && pei > psi) { + // replace existing package section + const before = body.substring(0, psi); + const after = body.substring(pei + pkgEnd.length); + + newBody = before + report + after; + } else { + // insert the package section just before the global end marker + const gi = body.indexOf(globalEnd); + + if (gi === -1) { + // malformed existing comment, append at end + newBody = body + '\n\n' + report; + } else { + newBody = body.substring(0, gi) + report + '\n' + body.substring(gi); + } + } + + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: newBody }); + core.info('Updated aggregated PR comment with report.'); +} + +module.exports = { updateAggregatedComment }; diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index 82af2c4a..ab671e31 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -46,3 +46,18 @@ jobs: uses: ./.github/actions/build with: package-name: '@editorjs/collaboration-manager' + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/collaboration-manager' + diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 6aaaf7fd..a2ac6b22 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -52,3 +52,17 @@ jobs: package-name: '@editorjs/core' working-directory: './packages/core' stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [tests, mutation-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/core' diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index eba7f2ff..83fa4b4b 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -56,3 +56,44 @@ jobs: package-name: '@editorjs/dom-adapters' working-directory: './packages/dom-adapters' stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [tests, mutation-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/dom-adapters' + + mutation-tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Build dependencies + uses: ./.github/actions/build + with: + package-name: '@editorjs/dom-adapters' + + - name: Run mutation tests for changed files + if: ${{ github.event_name == 'pull_request' }} + uses: ./.github/actions/mutation-tests-changed-files + with: + package-name: '@editorjs/dom-adapters' + working-directory: './packages/dom-adapters' + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + + - name: Run mutation tests for all files + if: ${{ github.event_name == 'merge_group' }} + uses: ./.github/actions/mutation-tests-all-files + with: + package-name: '@editorjs/dom-adapters' + working-directory: './packages/dom-adapters' + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index 1e29c373..e4e23a1d 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -52,3 +52,17 @@ jobs: package-name: '@editorjs/model' working-directory: './packages/model' stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [tests, mutation-tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/model' diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index 36d1d63a..389d6b2b 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -46,3 +46,18 @@ jobs: uses: ./.github/actions/build with: package-name: '@editorjs/ot-server' + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [tests] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/ot-server' + diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 9985de18..3b575c4a 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -19,4 +19,19 @@ jobs: - name: Build the package uses: ./.github/actions/build with: - package-name: '@editorjs/sdk' \ No newline at end of file + package-name: '@editorjs/sdk' + + report: + name: Aggregate and post report + if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + needs: [build] + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: '@editorjs/sdk' + diff --git a/.gitignore b/.gitignore index f91aca7a..ccbe8ec4 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,7 @@ dist/* # tests coverage/ reports/ +jest-report.json # stryker temp files .stryker-tmp diff --git a/packages/collaboration-manager/package.json b/packages/collaboration-manager/package.json index f60cde50..0dc27648 100644 --- a/packages/collaboration-manager/package.json +++ b/packages/collaboration-manager/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", "test:mutations": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/core/stryker.conf.mjs b/packages/core/stryker.conf.mjs index 83093f77..34e79b4a 100644 --- a/packages/core/stryker.conf.mjs +++ b/packages/core/stryker.conf.mjs @@ -11,6 +11,7 @@ const config = { allowEmojis: true, }, reporters: [ + 'json', 'html', 'clear-text', 'progress', diff --git a/packages/model/stryker.conf.mjs b/packages/model/stryker.conf.mjs index 63c04dfb..9b1f4ca1 100644 --- a/packages/model/stryker.conf.mjs +++ b/packages/model/stryker.conf.mjs @@ -12,6 +12,7 @@ const config = { allowEmojis: true, }, reporters: [ + "json", "html", "clear-text", "progress", From 88f1dd47c024a9d8ac5d9144b117b8fc7261d33e Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:23:11 +0100 Subject: [PATCH 02/44] update all actions --- .github/actions/aggregate-report/action.yml | 57 +++---------------- .github/actions/base-coverage/action.yml | 6 +- .github/actions/build/action.yml | 2 +- .github/actions/lint/action.yml | 2 +- .../mutation-tests-all-files/action.yml | 2 +- .../mutation-tests-changed-files/action.yml | 8 +-- .github/actions/unit-tests/action.yml | 4 +- .../workflows/build-and-push-docker-image.yml | 2 +- .github/workflows/collaboration-manager.yml | 8 +-- .github/workflows/core.yml | 10 ++-- .github/workflows/dom-adapters.yml | 12 ++-- .github/workflows/model.yml | 10 ++-- .github/workflows/npm-publish.yml | 4 +- .github/workflows/ot-server.yml | 8 +-- .github/workflows/playground.yml | 4 +- .github/workflows/sdk.yml | 6 +- 16 files changed, 49 insertions(+), 96 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index c5cebd8c..8d6caa75 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -12,7 +12,7 @@ runs: using: 'composite' steps: - name: Download artifacts - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v8 - name: Process coverage reports uses: actions/github-script@v9 @@ -49,59 +49,16 @@ runs: core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); - - name: Resolve pull request number - id: resolve_pr - uses: actions/github-script@v9 - with: - script: | - const core = require('@actions/core'); - const github = require('@actions/github'); - (async () => { - const owner = github.context.repo.owner; - const repo = github.context.repo.repo; - let pr = (github.context.payload.pull_request && github.context.payload.pull_request.number) || github.context.issue.number; + - name: Find current PR's number + uses: jwalton/gh-find-current-pr@v1 + id: findPr - // try commit-associated PRs if not in context - if (!pr) { - const sha = github.context.sha; - if (sha) { - try { - const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); - if (assoc && Array.isArray(assoc.data) && assoc.data.length > 0) { - const prObj = assoc.data.find(p => p.state === 'open') || assoc.data[0]; - pr = prObj.number; - } - } catch (e) { - core.info('Error while searching for PR associated with commit: ' + String(e)); - } - } - } - - // fallback: try to find an open PR by branch ref (refs/heads/) - if (!pr) { - const ref = github.context.ref || ''; - const headsPrefix = 'refs/heads/'; - if (ref.startsWith(headsPrefix)) { - const branch = ref.slice(headsPrefix.length); - try { - const pulls = await github.rest.pulls.list({ owner, repo, head: `${owner}:${branch}`, state: 'open' }); - if (pulls && Array.isArray(pulls.data) && pulls.data.length > 0) { - pr = pulls.data[0].number; - } - } catch (e) { - core.info('Error while searching for PR by branch ref: ' + String(e)); - } - } - } - - core.setOutput('pr-number', pr ? String(pr) : ''); - })(); - name: Acquire comment-update lock id: acquire_lock uses: softprops/turnstyle@v2 with: - key: aggregated-comment-${{ steps.resolve_pr.outputs.pr-number || github.ref_name || github.ref }} + key: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} timeout: 600 - name: Create or update PR comment with report @@ -109,7 +66,7 @@ runs: env: REPORT: ${{ steps.section-report.outputs.report }} PACKAGE: ${{ inputs.package-name }} - PR_NUMBER: ${{ steps.resolve_pr.outputs.pr-number }} + PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | const core = require('@actions/core'); @@ -131,5 +88,5 @@ runs: if: ${{ always() }} uses: softprops/turnstyle@v2 with: - key: aggregated-comment-${{ steps.resolve_pr.outputs.pr-number || github.ref_name || github.ref }} + key: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} action: 'release' diff --git a/.github/actions/base-coverage/action.yml b/.github/actions/base-coverage/action.yml index 8f788598..9be8656d 100644 --- a/.github/actions/base-coverage/action.yml +++ b/.github/actions/base-coverage/action.yml @@ -12,13 +12,13 @@ runs: using: "composite" steps: - name: Checkout base ref - uses: actions/checkout@v4 + uses: actions/checkout@v6 with: ref: ${{ steps.resolve_base.outputs.base_ref }} fetch-depth: 0 - name: Setup Node - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -33,7 +33,7 @@ runs: working-directory: ${{ inputs.working-directory }} - name: Upload base coverage artifact - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: test-report-base path: | diff --git a/.github/actions/build/action.yml b/.github/actions/build/action.yml index 7ce0aca8..5b06b560 100644 --- a/.github/actions/build/action.yml +++ b/.github/actions/build/action.yml @@ -7,7 +7,7 @@ runs: using: "composite" steps: - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/actions/lint/action.yml b/.github/actions/lint/action.yml index 765cd6f6..9dfc4998 100644 --- a/.github/actions/lint/action.yml +++ b/.github/actions/lint/action.yml @@ -7,7 +7,7 @@ runs: using: "composite" steps: - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/actions/mutation-tests-all-files/action.yml b/.github/actions/mutation-tests-all-files/action.yml index 7f0bfa52..fc922387 100644 --- a/.github/actions/mutation-tests-all-files/action.yml +++ b/.github/actions/mutation-tests-all-files/action.yml @@ -12,7 +12,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index 4bd60457..fb558fac 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -15,7 +15,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc @@ -47,10 +47,6 @@ runs: run: yarn workspace ${{ inputs.package-name }} test:mutations --mutate ${{format('''{0}''', steps.changed-files.outputs.src_all_changed_files)}} --dashboard.module ${{ inputs.package-name }} continue-on-error: true - - name: Find current PR's number - uses: jwalton/gh-find-current-pr@v1 - id: findPr - - name: Save mutated files list if: steps.changed-files.outputs.src_any_changed == 'true' shell: bash @@ -64,7 +60,7 @@ runs: - name: Upload mutation report artifact if: steps.changed-files.outputs.src_any_changed == 'true' - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: mutation-report path: | diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml index a2ff4332..4d3b9452 100644 --- a/.github/actions/unit-tests/action.yml +++ b/.github/actions/unit-tests/action.yml @@ -10,7 +10,7 @@ inputs: runs: using: "composite" steps: - - uses: actions/setup-node@v3 + - uses: actions/setup-node@v6 with: node-version-file: .nvmrc - uses: ./.github/actions/setup @@ -27,7 +27,7 @@ runs: - name: Upload coverage artifact (PRs only) if: ${{ github.event_name == 'pull_request' }} - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v7 with: name: test-report path: | diff --git a/.github/workflows/build-and-push-docker-image.yml b/.github/workflows/build-and-push-docker-image.yml index 7ddf2fb4..ecdd4c3b 100644 --- a/.github/workflows/build-and-push-docker-image.yml +++ b/.github/workflows/build-and-push-docker-image.yml @@ -14,7 +14,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout repository - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Login to GitHub registry uses: docker/login-action@v3 diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index ab671e31..3ed15ae3 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -7,7 +7,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: yarn @@ -24,7 +24,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: yarn @@ -41,7 +41,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index a2ac6b22..d7c7e239 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -6,7 +6,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run ESLint check uses: ./.github/actions/lint with: @@ -15,7 +15,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run unit tests uses: ./.github/actions/unit-tests @@ -26,7 +26,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -35,7 +35,7 @@ jobs: mutation-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run mutation tests for changed files if: ${{ github.event_name == 'pull_request' }} @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index 83fa4b4b..fbb01c2d 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -6,7 +6,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run ESLint check uses: ./.github/actions/lint with: @@ -15,7 +15,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run unit tests uses: ./.github/actions/unit-tests with: @@ -25,7 +25,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -34,7 +34,7 @@ jobs: mutation-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build dependencies uses: ./.github/actions/build @@ -64,7 +64,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report @@ -74,7 +74,7 @@ jobs: mutation-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build dependencies uses: ./.github/actions/build diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index e4e23a1d..81a7c4e7 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -7,7 +7,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run ESLint check uses: ./.github/actions/lint with: @@ -16,7 +16,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run unit tests uses: ./.github/actions/unit-tests @@ -26,7 +26,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -35,7 +35,7 @@ jobs: mutation-tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run mutation tests for changed files if: ${{ github.event_name == 'pull_request' }} @@ -60,7 +60,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report diff --git a/.github/workflows/npm-publish.yml b/.github/workflows/npm-publish.yml index 979b6e9d..2b1d47a2 100644 --- a/.github/workflows/npm-publish.yml +++ b/.github/workflows/npm-publish.yml @@ -10,9 +10,9 @@ jobs: publish: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: node-version-file: '.nvmrc' registry-url: https://registry.npmjs.org/ diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index 389d6b2b..926ebbe2 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -7,7 +7,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: yarn @@ -24,7 +24,7 @@ jobs: tests: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - run: yarn @@ -41,7 +41,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -54,7 +54,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index 4811f740..b8c4f18a 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -7,7 +7,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run ESLint check uses: ./.github/actions/lint with: @@ -16,7 +16,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 3b575c4a..58f3c298 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -6,7 +6,7 @@ jobs: lint: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Run ESLint check uses: ./.github/actions/lint with: @@ -15,7 +15,7 @@ jobs: build: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 - name: Build the package uses: ./.github/actions/build with: @@ -28,7 +28,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@v6 - name: Generate aggregated report and comment uses: ./.github/actions/aggregate-report From c175508ae9c56b531816ee944f29b370ecdbc6bc Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:26:39 +0100 Subject: [PATCH 03/44] Remove core require --- .github/actions/aggregate-report/action.yml | 4 ---- 1 file changed, 4 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 8d6caa75..f53e6259 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -19,7 +19,6 @@ runs: id: process_test_report with: script: | - const core = require('@actions/core'); const processReports = require('./.github/scripts/compare-coverage.js') core.setOutput('message', processReports('test-report', 'test-report-base')); @@ -29,7 +28,6 @@ runs: id: process_mutation_report with: script: | - const core = require('@actions/core'); const processReports = require('./.github/scripts/process-mutation.js') core.setOutput('message', processReports('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); @@ -43,7 +41,6 @@ runs: PACKAGE: ${{ inputs.package-name }} with: script: | - const core = require('@actions/core'); const generateReport = require('./.github/scripts/generate-report.js').generateReport const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; @@ -69,7 +66,6 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | - const core = require('@actions/core'); const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { From 8b117ee8779328f2639e7c9c10a604c86ac40018 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:29:01 +0100 Subject: [PATCH 04/44] build packages for base branch test run --- .github/actions/base-coverage/action.yml | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/.github/actions/base-coverage/action.yml b/.github/actions/base-coverage/action.yml index 9be8656d..f78c4871 100644 --- a/.github/actions/base-coverage/action.yml +++ b/.github/actions/base-coverage/action.yml @@ -22,10 +22,13 @@ runs: with: node-version-file: .nvmrc - - name: Install dependencies - run: yarn --frozen-lockfile - working-directory: ${{ inputs.working-directory }} + - name: Setup environment + uses: ./.github/actions/setup + + - name: Build the package shell: bash + run: yarn workspace ${{ inputs.package-name }} run build + working-directory: ${{ inputs.working-directory }} - name: Run coverage shell: bash From 8904eb5ceb6ac26a2d606919a661b666174926ac Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:33:02 +0100 Subject: [PATCH 05/44] Replace require with import --- .github/actions/aggregate-report/action.yml | 9 +++++---- .github/scripts/compare-coverage.js | 4 ++-- .github/scripts/generate-report.js | 12 +++++------- .github/scripts/process-mutation.js | 20 +------------------- .github/scripts/update-aggregated-comment.js | 10 +++------- 5 files changed, 16 insertions(+), 39 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index f53e6259..6e5da5e1 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -19,7 +19,7 @@ runs: id: process_test_report with: script: | - const processReports = require('./.github/scripts/compare-coverage.js') + import processReports from './.github/scripts/compare-coverage.js'; core.setOutput('message', processReports('test-report', 'test-report-base')); @@ -28,7 +28,7 @@ runs: id: process_mutation_report with: script: | - const processReports = require('./.github/scripts/process-mutation.js') + import processReports from './.github/scripts/process-mutation.js'; core.setOutput('message', processReports('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); @@ -41,7 +41,8 @@ runs: PACKAGE: ${{ inputs.package-name }} with: script: | - const generateReport = require('./.github/scripts/generate-report.js').generateReport + import { generateReport } from './.github/scripts/generate-report.js'; + const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); @@ -66,7 +67,7 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | - const updater = require('./.github/scripts/update-aggregated-comment.js'); + import updater from './.github/scripts/update-aggregated-comment.js'; (async () => { try { diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index f93cc675..ee417c8e 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; function findCoverageJson(dir) { if (!dir) return null; diff --git a/.github/scripts/generate-report.js b/.github/scripts/generate-report.js index 20ced1ba..267b94f1 100644 --- a/.github/scripts/generate-report.js +++ b/.github/scripts/generate-report.js @@ -1,10 +1,8 @@ -#!/usr/bin/env node - -function generateReport(package, testMessage, mutationMessage) { +function generateReport(pkg, testMessage, mutationMessage) { let md = ''; - md += `\n`; - md += `## ${package}\n\n`; + md += `\n`; + md += `## ${pkg}\n\n`; md += `### Unit tests report\n ${testMessage}\n\n`; @@ -13,10 +11,10 @@ function generateReport(package, testMessage, mutationMessage) { } - md += `\n`; + md += `\n`; return md; } -module.exports = { generateReport }; +export { generateReport }; diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 39fcff38..4cde8da8 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -1,22 +1,4 @@ -const fs = require('fs'); -const path = require('path'); - -function normalizeStatusToCanonical(s) { - if (s == null) return ''; - const st = String(s).toLowerCase(); - if (st.includes('surviv')) return 'Survived'; - if (st.includes('no') && st.includes('coverage')) return 'NoCoverage'; - if (st.includes('nocoverage')) return 'NoCoverage'; - if (st.includes('killed')) return 'Killed'; - if (st.includes('timeout')) return 'Timeout'; - if (st.includes('runtime')) return 'RuntimeError'; - if (st.includes('compile')) return 'CompileError'; - if (st.includes('ignored') || st.includes('skip')) return 'Ignored'; - if (st === 'survived') return 'Survived'; - if (st === 'killed') return 'Killed'; - // return original-ish with capitalization - return String(s); -} +import fs from 'fs'; function getMetrics(obj) { const mutants = []; diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 36aa68ba..2fe1e307 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -1,9 +1,5 @@ -#!/usr/bin/env node -// .github/scripts/update-aggregated-comment.js -// Exports: updateAggregatedComment(report, packageName, prNumber) - -const core = require('@actions/core'); -const github = require('@actions/github'); +import core from '@actions/core'; +import github from '@actions/github'; async function updateAggregatedComment(report, packageName, prNumber) { if (!report) { @@ -100,4 +96,4 @@ async function updateAggregatedComment(report, packageName, prNumber) { core.info('Updated aggregated PR comment with report.'); } -module.exports = { updateAggregatedComment }; +export { updateAggregatedComment }; From 2f21c6c1258fed0dec841b4b76a7ccdd81119118 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:37:07 +0100 Subject: [PATCH 06/44] Revert "Replace require with import" This reverts commit 8904eb5ceb6ac26a2d606919a661b666174926ac. --- .github/actions/aggregate-report/action.yml | 9 ++++----- .github/scripts/compare-coverage.js | 4 ++-- .github/scripts/generate-report.js | 12 +++++++----- .github/scripts/process-mutation.js | 20 +++++++++++++++++++- .github/scripts/update-aggregated-comment.js | 10 +++++++--- 5 files changed, 39 insertions(+), 16 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 6e5da5e1..f53e6259 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -19,7 +19,7 @@ runs: id: process_test_report with: script: | - import processReports from './.github/scripts/compare-coverage.js'; + const processReports = require('./.github/scripts/compare-coverage.js') core.setOutput('message', processReports('test-report', 'test-report-base')); @@ -28,7 +28,7 @@ runs: id: process_mutation_report with: script: | - import processReports from './.github/scripts/process-mutation.js'; + const processReports = require('./.github/scripts/process-mutation.js') core.setOutput('message', processReports('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); @@ -41,8 +41,7 @@ runs: PACKAGE: ${{ inputs.package-name }} with: script: | - import { generateReport } from './.github/scripts/generate-report.js'; - + const generateReport = require('./.github/scripts/generate-report.js').generateReport const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); @@ -67,7 +66,7 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | - import updater from './.github/scripts/update-aggregated-comment.js'; + const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { try { diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index ee417c8e..f93cc675 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -1,5 +1,5 @@ -import fs from 'fs'; -import path from 'path'; +const fs = require('fs'); +const path = require('path'); function findCoverageJson(dir) { if (!dir) return null; diff --git a/.github/scripts/generate-report.js b/.github/scripts/generate-report.js index 267b94f1..20ced1ba 100644 --- a/.github/scripts/generate-report.js +++ b/.github/scripts/generate-report.js @@ -1,8 +1,10 @@ -function generateReport(pkg, testMessage, mutationMessage) { +#!/usr/bin/env node + +function generateReport(package, testMessage, mutationMessage) { let md = ''; - md += `\n`; - md += `## ${pkg}\n\n`; + md += `\n`; + md += `## ${package}\n\n`; md += `### Unit tests report\n ${testMessage}\n\n`; @@ -11,10 +13,10 @@ function generateReport(pkg, testMessage, mutationMessage) { } - md += `\n`; + md += `\n`; return md; } -export { generateReport }; +module.exports = { generateReport }; diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 4cde8da8..39fcff38 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -1,4 +1,22 @@ -import fs from 'fs'; +const fs = require('fs'); +const path = require('path'); + +function normalizeStatusToCanonical(s) { + if (s == null) return ''; + const st = String(s).toLowerCase(); + if (st.includes('surviv')) return 'Survived'; + if (st.includes('no') && st.includes('coverage')) return 'NoCoverage'; + if (st.includes('nocoverage')) return 'NoCoverage'; + if (st.includes('killed')) return 'Killed'; + if (st.includes('timeout')) return 'Timeout'; + if (st.includes('runtime')) return 'RuntimeError'; + if (st.includes('compile')) return 'CompileError'; + if (st.includes('ignored') || st.includes('skip')) return 'Ignored'; + if (st === 'survived') return 'Survived'; + if (st === 'killed') return 'Killed'; + // return original-ish with capitalization + return String(s); +} function getMetrics(obj) { const mutants = []; diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 2fe1e307..36aa68ba 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -1,5 +1,9 @@ -import core from '@actions/core'; -import github from '@actions/github'; +#!/usr/bin/env node +// .github/scripts/update-aggregated-comment.js +// Exports: updateAggregatedComment(report, packageName, prNumber) + +const core = require('@actions/core'); +const github = require('@actions/github'); async function updateAggregatedComment(report, packageName, prNumber) { if (!report) { @@ -96,4 +100,4 @@ async function updateAggregatedComment(report, packageName, prNumber) { core.info('Updated aggregated PR comment with report.'); } -export { updateAggregatedComment }; +module.exports = { updateAggregatedComment }; From 66426965e3e1678efadcca87ec7abad05ddfad0f Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:39:19 +0100 Subject: [PATCH 07/44] Replace require only inside js scripts --- .github/scripts/compare-coverage.js | 4 ++-- .github/scripts/generate-report.js | 12 +++++------- .github/scripts/process-mutation.js | 6 +++--- .github/scripts/update-aggregated-comment.js | 6 +++--- 4 files changed, 13 insertions(+), 15 deletions(-) diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index f93cc675..ee417c8e 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; function findCoverageJson(dir) { if (!dir) return null; diff --git a/.github/scripts/generate-report.js b/.github/scripts/generate-report.js index 20ced1ba..267b94f1 100644 --- a/.github/scripts/generate-report.js +++ b/.github/scripts/generate-report.js @@ -1,10 +1,8 @@ -#!/usr/bin/env node - -function generateReport(package, testMessage, mutationMessage) { +function generateReport(pkg, testMessage, mutationMessage) { let md = ''; - md += `\n`; - md += `## ${package}\n\n`; + md += `\n`; + md += `## ${pkg}\n\n`; md += `### Unit tests report\n ${testMessage}\n\n`; @@ -13,10 +11,10 @@ function generateReport(package, testMessage, mutationMessage) { } - md += `\n`; + md += `\n`; return md; } -module.exports = { generateReport }; +export { generateReport }; diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 39fcff38..e8f5bc29 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -1,5 +1,5 @@ -const fs = require('fs'); -const path = require('path'); +import fs from 'fs'; +import path from 'path'; function normalizeStatusToCanonical(s) { if (s == null) return ''; @@ -82,7 +82,7 @@ function processMutationReport(reportPath, changedFilesPath) { return message; } -module.exports = { processMutationReport }; +export { processMutationReport }; // If invoked directly from CLI, read the first arg as the report path and print JSON if (require.main === module) { diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 36aa68ba..974ff0cf 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -2,8 +2,8 @@ // .github/scripts/update-aggregated-comment.js // Exports: updateAggregatedComment(report, packageName, prNumber) -const core = require('@actions/core'); -const github = require('@actions/github'); +import core from '@actions/core'; +import github from '@actions/github'; async function updateAggregatedComment(report, packageName, prNumber) { if (!report) { @@ -100,4 +100,4 @@ async function updateAggregatedComment(report, packageName, prNumber) { core.info('Updated aggregated PR comment with report.'); } -module.exports = { updateAggregatedComment }; +export { updateAggregatedComment }; From 1898afaf70b2a488905488f74befabb00b671229 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:43:53 +0100 Subject: [PATCH 08/44] Fixes --- .github/actions/aggregate-report/action.yml | 8 ++++---- .github/scripts/compare-coverage.js | 2 +- .github/workflows/ot-server.yml | 1 + .github/workflows/sdk.yml | 20 +++++++++++++++++++- 4 files changed, 25 insertions(+), 6 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index f53e6259..2a765e08 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -19,7 +19,7 @@ runs: id: process_test_report with: script: | - const processReports = require('./.github/scripts/compare-coverage.js') + const processReports = require('./.github/scripts/compare-coverage.js').processReports; core.setOutput('message', processReports('test-report', 'test-report-base')); @@ -28,9 +28,9 @@ runs: id: process_mutation_report with: script: | - const processReports = require('./.github/scripts/process-mutation.js') + const processMutationReport = require('./.github/scripts/process-mutation.js').processMutationReport; - core.setOutput('message', processReports('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); + core.setOutput('message', processMutationReport('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); - name: Generate markdown report id: section-report @@ -41,7 +41,7 @@ runs: PACKAGE: ${{ inputs.package-name }} with: script: | - const generateReport = require('./.github/scripts/generate-report.js').generateReport + const generateReport = require('./.github/scripts/generate-report.js').generateReport; const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index ee417c8e..087b2ed4 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -80,7 +80,7 @@ function compute(headSummary, baseSummary) { return out; } -export default function processReports(headDir, baseDir) { +export function processReports(headDir, baseDir) { const headCov = findCoverageJson(headDir); const baseCov = findCoverageJson(baseDir); const headJest = findJestJsonResults(headDir); diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index 926ebbe2..f1c53fc0 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -38,6 +38,7 @@ jobs: with: package-name: '@editorjs/ot-server' working-directory: './packages/ot-server' + build: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 58f3c298..6d10b814 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -21,10 +21,28 @@ jobs: with: package-name: '@editorjs/sdk' + tests: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - run: yarn + + - name: Build the package + uses: ./.github/actions/build + with: + package-name: '@editorjs/sdk' + + - name: Run unit tests + uses: ./.github/actions/unit-tests + with: + package-name: '@editorjs/sdk' + working-directory: './packages/sdk' + report: name: Aggregate and post report if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} - needs: [build] + needs: [tests] runs-on: ubuntu-latest steps: - name: Checkout From 1b629a010122f1a7ad45baf3945c4ca26a496bcb Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:46:59 +0100 Subject: [PATCH 09/44] Remove unused code --- .github/scripts/process-mutation.js | 26 ----------------------- .github/workflows/sdk.yml | 32 ----------------------------- 2 files changed, 58 deletions(-) diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index e8f5bc29..7eb9fe59 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -1,22 +1,4 @@ import fs from 'fs'; -import path from 'path'; - -function normalizeStatusToCanonical(s) { - if (s == null) return ''; - const st = String(s).toLowerCase(); - if (st.includes('surviv')) return 'Survived'; - if (st.includes('no') && st.includes('coverage')) return 'NoCoverage'; - if (st.includes('nocoverage')) return 'NoCoverage'; - if (st.includes('killed')) return 'Killed'; - if (st.includes('timeout')) return 'Timeout'; - if (st.includes('runtime')) return 'RuntimeError'; - if (st.includes('compile')) return 'CompileError'; - if (st.includes('ignored') || st.includes('skip')) return 'Ignored'; - if (st === 'survived') return 'Survived'; - if (st === 'killed') return 'Killed'; - // return original-ish with capitalization - return String(s); -} function getMetrics(obj) { const mutants = []; @@ -83,11 +65,3 @@ function processMutationReport(reportPath, changedFilesPath) { } export { processMutationReport }; - -// If invoked directly from CLI, read the first arg as the report path and print JSON -if (require.main === module) { - const reportPath = process.argv[2]; - const res = processMutationReport(reportPath); - console.log(JSON.stringify(res, null, 2)); -} - diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index 6d10b814..f2321b08 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -21,35 +21,3 @@ jobs: with: package-name: '@editorjs/sdk' - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - run: yarn - - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/sdk' - - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - package-name: '@editorjs/sdk' - working-directory: './packages/sdk' - - report: - name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} - needs: [tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/sdk' - From f6382d4c51dea44901d3976a07404903280f691b Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:54:52 +0100 Subject: [PATCH 10/44] Pass core and github as parameters --- .github/actions/aggregate-report/action.yml | 4 +++- .github/scripts/update-aggregated-comment.js | 9 +-------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 2a765e08..0cac6114 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -66,6 +66,8 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | + const core = require('@actions/core'); + const github = require('@actions/github'); const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { @@ -73,7 +75,7 @@ runs: const report = process.env.REPORT || ''; const packageName = process.env.PACKAGE; const pr = process.env.PR_NUMBER || undefined; - await updater.updateAggregatedComment(report, packageName, pr); + await updater.updateAggregatedComment(report, packageName, pr, core, github); } catch (err) { core.error(err && err.stack ? err.stack : String(err)); throw err; diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 974ff0cf..4265b043 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -1,11 +1,4 @@ -#!/usr/bin/env node -// .github/scripts/update-aggregated-comment.js -// Exports: updateAggregatedComment(report, packageName, prNumber) - -import core from '@actions/core'; -import github from '@actions/github'; - -async function updateAggregatedComment(report, packageName, prNumber) { +async function updateAggregatedComment(report, packageName, prNumber, core, github) { if (!report) { core.info('No report provided, skipping comment update.'); return; From 6834c3e86454a2dbf2cee482205c33b56d131b5a Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 20:57:15 +0100 Subject: [PATCH 11/44] Remove require core and github --- .github/actions/aggregate-report/action.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 0cac6114..169c739f 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -66,8 +66,6 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | - const core = require('@actions/core'); - const github = require('@actions/github'); const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { From 1cca3ed36459c85c7501ccc1b37de50a18d7d5dc Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 21:00:13 +0100 Subject: [PATCH 12/44] revert github require --- .github/actions/aggregate-report/action.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 169c739f..2afb4e33 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -66,6 +66,7 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | + const github = require('@actions/github'); const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { From e962bcc3de66ee3da2b42d3b82e38402b33577db Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 21:14:53 +0100 Subject: [PATCH 13/44] Pass context separately --- .github/actions/aggregate-report/action.yml | 17 +++------ .github/scripts/update-aggregated-comment.js | 38 ++------------------ 2 files changed, 8 insertions(+), 47 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 2afb4e33..af327876 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -53,10 +53,10 @@ runs: - name: Acquire comment-update lock id: acquire_lock - uses: softprops/turnstyle@v2 + uses: softprops/turnstyle@v3 with: - key: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} - timeout: 600 + queue-name: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} + initial-wait-seconds: 600 - name: Create or update PR comment with report uses: actions/github-script@v6 @@ -66,7 +66,6 @@ runs: PR_NUMBER: ${{ steps.findPr.outputs.number }} with: script: | - const github = require('@actions/github'); const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { @@ -74,16 +73,10 @@ runs: const report = process.env.REPORT || ''; const packageName = process.env.PACKAGE; const pr = process.env.PR_NUMBER || undefined; - await updater.updateAggregatedComment(report, packageName, pr, core, github); + // Pass the injected core, github and context objects + await updater.updateAggregatedComment(report, packageName, pr, core, github, context); } catch (err) { core.error(err && err.stack ? err.stack : String(err)); throw err; } })(); - - - name: Release comment-update lock - if: ${{ always() }} - uses: softprops/turnstyle@v2 - with: - key: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} - action: 'release' diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 4265b043..4c7bc7fe 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -1,44 +1,12 @@ -async function updateAggregatedComment(report, packageName, prNumber, core, github) { +async function updateAggregatedComment(report, packageName, pr, core, github, context) { if (!report) { core.info('No report provided, skipping comment update.'); return; } // repo info (owner/repo) available from context - const owner = github.context.repo.owner; - const repo = github.context.repo.repo; - - // Allow caller to pass the PR number (from workflow / another action). If not provided, - // resolve PR number from the GitHub context. For push events there is no pull_request - // payload, so we attempt to find an associated PR by commit SHA as a fallback. - let pr; - - if (prNumber) { - const parsed = Number(prNumber); - if (!Number.isNaN(parsed) && parsed > 0) { - pr = parsed; - } - } - - if (!pr) { - pr = (github.context.payload.pull_request && github.context.payload.pull_request.number) || github.context.issue.number; - } - - if (!pr) { - const sha = github.context.sha; - if (sha) { - try { - const assoc = await github.rest.repos.listPullRequestsAssociatedWithCommit({ owner, repo, commit_sha: sha }); - if (assoc && Array.isArray(assoc.data) && assoc.data.length > 0) { - // Prefer an open PR if available - const prObj = assoc.data.find(p => p.state === 'open') || assoc.data[0]; - pr = prObj.number; - } - } catch (e) { - core.info('Error while searching for PR associated with commit: ' + String(e)); - } - } - } + const owner = context.repo.owner; + const repo = context.repo.repo; if (!pr) { core.info('No pull request found in context or associated with the commit; skipping comment step.'); From 7910489e0a775a4c12cc1550e0ed3a6a75a0bffe Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 21:22:10 +0100 Subject: [PATCH 14/44] Remove turnstyle initial wait, update cache action --- .github/actions/aggregate-report/action.yml | 1 - .github/actions/setup/action.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index af327876..f2076899 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -56,7 +56,6 @@ runs: uses: softprops/turnstyle@v3 with: queue-name: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} - initial-wait-seconds: 600 - name: Create or update PR comment with report uses: actions/github-script@v6 diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 076996d2..b4fc78a0 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -8,7 +8,7 @@ runs: shell: bash - name: Restore yarn cache folder - uses: actions/cache@v3 + uses: actions/cache@v5 id: yarn-cache with: path: ${{ steps.yarn-cache-dir-path.outputs.dir }} From d8bf2c1815ba86067046d8e99fd66f40255f0607 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 21:46:09 +0100 Subject: [PATCH 15/44] Add reporting for unit tests to each package, update changed-files action --- .github/actions/mutation-tests-all-files/action.yml | 2 +- .github/actions/mutation-tests-changed-files/action.yml | 2 +- packages/core/package.json | 2 +- packages/dom-adapters/package.json | 2 +- packages/model/package.json | 2 +- packages/ot-server/package.json | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/actions/mutation-tests-all-files/action.yml b/.github/actions/mutation-tests-all-files/action.yml index fc922387..87e8bb5e 100644 --- a/.github/actions/mutation-tests-all-files/action.yml +++ b/.github/actions/mutation-tests-all-files/action.yml @@ -24,7 +24,7 @@ runs: package-name: ${{ inputs.package-name }} - name: Get changed files - uses: tj-actions/changed-files@v39.0.0 + uses: tj-actions/changed-files@v47 id: changed-files with: files_yaml: | diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index fb558fac..fc2c0573 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -27,7 +27,7 @@ runs: package-name: ${{ inputs.package-name }} - name: Get changed files - uses: tj-actions/changed-files@v39.0.0 + uses: tj-actions/changed-files@v47 id: changed-files with: files_yaml: | diff --git a/packages/core/package.json b/packages/core/package.json index c165f2bd..ae1ee8a8 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,7 @@ "lint:ci": "eslint --config ./eslint.config.mjs ./src --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", "test:mutations": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/dom-adapters/package.json b/packages/dom-adapters/package.json index e5138dfa..e04d5666 100644 --- a/packages/dom-adapters/package.json +++ b/packages/dom-adapters/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true", + "test:coverage": "yarn test --coverage=true ", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/model/package.json b/packages/model/package.json index 4b68365e..5f4a0231 100644 --- a/packages/model/package.json +++ b/packages/model/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/ot-server/package.json b/packages/ot-server/package.json index ec00d36f..5567f615 100644 --- a/packages/ot-server/package.json +++ b/packages/ot-server/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, "dependencies": { From 9197c9b7cdda2456e421b3c78f492e302fc1404a Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 21:58:54 +0100 Subject: [PATCH 16/44] Use coverage json-summary reporter --- .github/actions/aggregate-report/action.yml | 4 ++-- .github/scripts/compare-coverage.js | 2 +- packages/collaboration-manager/jest.config.ts | 1 + packages/core/jest.config.ts | 1 + packages/dom-adapters/jest.config.ts | 1 + packages/model/jest.config.ts | 1 + packages/ot-server/jest.config.ts | 1 + packages/sdk/jest.config.ts | 1 + 8 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index f2076899..0b7ee789 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -34,7 +34,7 @@ runs: - name: Generate markdown report id: section-report - uses: actions/github-script@v6 + uses: actions/github-script@v9 env: TESTS_MESSAGE: ${{ steps.process_test_report.outputs.message }} MUTATION_MESSAGE: ${{ steps.process_mutation_report.outputs.message }} @@ -58,7 +58,7 @@ runs: queue-name: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} - name: Create or update PR comment with report - uses: actions/github-script@v6 + uses: actions/github-script@v9 env: REPORT: ${{ steps.section-report.outputs.report }} PACKAGE: ${{ inputs.package-name }} diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index 087b2ed4..cf8812b7 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -3,7 +3,7 @@ import path from 'path'; function findCoverageJson(dir) { if (!dir) return null; - const report = path.join(dir, 'coverage', 'coverage-final.json'); + const report = path.join(dir, 'coverage', 'coverage-summary.json'); if (fs.existsSync(report)) { diff --git a/packages/collaboration-manager/jest.config.ts b/packages/collaboration-manager/jest.config.ts index 940d6f69..98e86574 100644 --- a/packages/collaboration-manager/jest.config.ts +++ b/packages/collaboration-manager/jest.config.ts @@ -8,6 +8,7 @@ export default { tsconfig: '/tsconfig.test.json', }, }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], testMatch: ['/src/**/*.spec.ts'], modulePathIgnorePatterns: ['/.*/__mocks__', '/.*/mocks'], extensionsToTreatAsEsm: ['.ts'], diff --git a/packages/core/jest.config.ts b/packages/core/jest.config.ts index 098e06d9..f409e005 100644 --- a/packages/core/jest.config.ts +++ b/packages/core/jest.config.ts @@ -9,6 +9,7 @@ export default { moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], transform: { '^.+\\.tsx?$': [ 'ts-jest', diff --git a/packages/dom-adapters/jest.config.ts b/packages/dom-adapters/jest.config.ts index 77c0e2ac..999ea6af 100644 --- a/packages/dom-adapters/jest.config.ts +++ b/packages/dom-adapters/jest.config.ts @@ -9,6 +9,7 @@ export default { moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], transform: { // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` diff --git a/packages/model/jest.config.ts b/packages/model/jest.config.ts index caf62a9d..e8a9f307 100644 --- a/packages/model/jest.config.ts +++ b/packages/model/jest.config.ts @@ -9,6 +9,7 @@ export default { moduleNameMapper: { '^(\\.{1,2}/.*)\\.js$': '$1', }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], transform: { // '^.+\\.[tj]sx?$' to process js/ts with `ts-jest` // '^.+\\.m?[tj]sx?$' to process js/ts/mjs/mts with `ts-jest` diff --git a/packages/ot-server/jest.config.ts b/packages/ot-server/jest.config.ts index 0a49adc9..5bf9a48d 100644 --- a/packages/ot-server/jest.config.ts +++ b/packages/ot-server/jest.config.ts @@ -11,6 +11,7 @@ export default { '^(\\.{1,2}/.*)\\.js$': '$1', '^codex-tooltip$': '/test/mocks/codex-tooltip.ts', }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], transform: { ...createDefaultEsmPreset().transform, }, diff --git a/packages/sdk/jest.config.ts b/packages/sdk/jest.config.ts index 22791c2c..6269e54d 100644 --- a/packages/sdk/jest.config.ts +++ b/packages/sdk/jest.config.ts @@ -8,6 +8,7 @@ export default { '^(\\.{1,2}/.*)\\.js$': '$1', '^@/(.*)$': '/src/$1', }, + coverageReporters: ['lcov', 'json-summary', 'text-summary'], transform: { '^.+\\.tsx?$': [ 'ts-jest', From ff2acc82d3629bfe8db5fc659a5a5abc7770ba91 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:03:42 +0100 Subject: [PATCH 17/44] Upload mutation artifact even if no files changed --- .github/actions/mutation-tests-changed-files/action.yml | 1 - .github/scripts/compare-coverage.js | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index fc2c0573..80b9ac06 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -59,7 +59,6 @@ runs: run: echo "[]" > changed-files.json - name: Upload mutation report artifact - if: steps.changed-files.outputs.src_any_changed == 'true' uses: actions/upload-artifact@v7 with: name: mutation-report diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index cf8812b7..f24ec80d 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -97,7 +97,7 @@ export function processReports(headDir, baseDir) { let message = `${tests.passedTests} tests passed in ${tests.passedSuites} suites.\n` - message += `Branches coverage: ${categories.branches.head}\n`; + message += `Branches coverage: ${categories.branches.head}%\n`; let warning = ''; From 7aa89ef09c07048e8d2b3807093cba39e55038b3 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:04:37 +0100 Subject: [PATCH 18/44] Send message about files not found if the files list is empty --- .github/scripts/process-mutation.js | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 7eb9fe59..fa4aad73 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -30,18 +30,19 @@ function getMetrics(obj) { function processMutationReport(reportPath, changedFilesPath) { - if (!fs.existsSync(reportPath)) { + if (!fs.existsSync(reportPath) && !fs.existsSync(changedFilesPath)) { return ''; } - - const raw = fs.readFileSync(reportPath, 'utf8'); - const obj = JSON.parse(raw); const changedFiles = JSON.parse(fs.readFileSync(changedFilesPath, 'utf8')); if (changedFiles.length === 0) { return 'No files to mutate found.'; } + const raw = fs.readFileSync(reportPath, 'utf8'); + const obj = JSON.parse(raw); + + const metrics = getMetrics(obj); let message = `Mutation tests run with mutation score ${metrics.score}%.\n`; From 00051da3cb61597d80c4ca495ef8c5ecf3099de0 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:11:20 +0100 Subject: [PATCH 19/44] Add concurrency --- .github/workflows/collaboration-manager.yml | 4 ++++ .github/workflows/core.yml | 4 ++++ .github/workflows/dom-adapters.yml | 4 ++++ .github/workflows/model.yml | 4 ++++ .github/workflows/ot-server.yml | 4 ++++ .github/workflows/playground.yml | 4 ++++ .github/workflows/sdk.yml | 4 ++++ 7 files changed, 28 insertions(+) diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index 3ed15ae3..c6d43f71 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -3,6 +3,10 @@ on: pull_request: merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index d7c7e239..daeb409d 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -2,6 +2,10 @@ name: Core check on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index fbb01c2d..bcfb45b0 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -2,6 +2,10 @@ name: Dom-adapters check on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index 81a7c4e7..2b0d0a88 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -3,6 +3,10 @@ on: pull_request: merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index f1c53fc0..5291c582 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -3,6 +3,10 @@ on: pull_request: merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/playground.yml b/.github/workflows/playground.yml index b8c4f18a..d652ab1f 100644 --- a/.github/workflows/playground.yml +++ b/.github/workflows/playground.yml @@ -3,6 +3,10 @@ on: pull_request: merge_group: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest diff --git a/.github/workflows/sdk.yml b/.github/workflows/sdk.yml index f2321b08..0bca6549 100644 --- a/.github/workflows/sdk.yml +++ b/.github/workflows/sdk.yml @@ -2,6 +2,10 @@ name: SDK check on: pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + jobs: lint: runs-on: ubuntu-latest From acb8efdf7cd69af694b6158ab63b23f514d3e4c6 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:11:40 +0100 Subject: [PATCH 20/44] Test coverage drop and mutation tests report --- .../src/CaretManagement/CaretManager.spec.ts | 210 +++++++++--------- .../model/src/entities/BlockNode/index.ts | 2 + 2 files changed, 107 insertions(+), 105 deletions(-) diff --git a/packages/model/src/CaretManagement/CaretManager.spec.ts b/packages/model/src/CaretManagement/CaretManager.spec.ts index f4376a3a..93976263 100644 --- a/packages/model/src/CaretManagement/CaretManager.spec.ts +++ b/packages/model/src/CaretManagement/CaretManager.spec.ts @@ -10,109 +10,109 @@ import { CaretManager } from './CaretManager.js'; import { jest } from '@jest/globals'; describe('CaretManager', () => { - it('should create new caret', () => { - const manager = new CaretManager(); - - const caret = manager.createCaret('userId'); - - expect(manager.getCaret(caret.userId)).toBe(caret); - }); - - it('should create new caret with passed index', () => { - const manager = new CaretManager(); - const index = new Index(); - - const caret = manager.createCaret('userId', index); - - expect(caret.index).toBe(index); - }); - - it('should dispatch caret added event on caret creation', () => { - const manager = new CaretManager(); - const handler = jest.fn(); - - manager.addEventListener(EventType.CaretManagerUpdated, handler); - - const index = new Index(); - const caret = manager.createCaret('userId', index); - - expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretAddedEvent)); - expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - detail: { - userId: caret.userId, - index: index.serialize(), - }, - })); - }); - - it('should update caret', () => { - const manager = new CaretManager(); - const caret = manager.createCaret('userId'); - - const index = new Index(); - - caret.update(index); - - expect(manager.getCaret(caret.userId)?.index).toBe(index); - }); - - it('should dispatch caret updated event on caret update', () => { - const manager = new CaretManager(); - const caret = manager.createCaret('userId'); - const handler = jest.fn(); - - manager.addEventListener(EventType.CaretManagerUpdated, handler); - - const index = new Index(); - - caret.update(index); - - expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretUpdatedEvent)); - expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - detail: { - userId: caret.userId, - index: index.serialize(), - }, - })); - }); - - it('should remove caret', () => { - const manager = new CaretManager(); - const caret = manager.createCaret('userId'); - - manager.removeCaret(caret); - - expect(manager.getCaret(caret.userId)).toBeUndefined(); - }); - - it('should dispatch caret removed event on caret removal', () => { - const manager = new CaretManager(); - const caret = manager.createCaret('userId'); - const handler = jest.fn(); - - manager.addEventListener(EventType.CaretManagerUpdated, handler); - - manager.removeCaret(caret); - - expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretRemovedEvent)); - expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - detail: { - userId: caret.userId, - index: null, - }, - })); - }); - - it('should not dispatch caret removed event if caret is not in the registry', () => { - const manager = new CaretManager(); - const caret = new Caret('userId'); - - const handler = jest.fn(); - - manager.addEventListener(EventType.CaretManagerUpdated, handler); - - manager.removeCaret(caret); - - expect(handler).not.toHaveBeenCalled(); - }); + // it('should create new caret', () => { + // const manager = new CaretManager(); + // + // const caret = manager.createCaret('userId'); + // + // expect(manager.getCaret(caret.userId)).toBe(caret); + // }); + // + // it('should create new caret with passed index', () => { + // const manager = new CaretManager(); + // const index = new Index(); + // + // const caret = manager.createCaret('userId', index); + // + // expect(caret.index).toBe(index); + // }); + // + // it('should dispatch caret added event on caret creation', () => { + // const manager = new CaretManager(); + // const handler = jest.fn(); + // + // manager.addEventListener(EventType.CaretManagerUpdated, handler); + // + // const index = new Index(); + // const caret = manager.createCaret('userId', index); + // + // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretAddedEvent)); + // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + // detail: { + // userId: caret.userId, + // index: index.serialize(), + // }, + // })); + // }); + // + // it('should update caret', () => { + // const manager = new CaretManager(); + // const caret = manager.createCaret('userId'); + // + // const index = new Index(); + // + // caret.update(index); + // + // expect(manager.getCaret(caret.userId)?.index).toBe(index); + // }); + // + // it('should dispatch caret updated event on caret update', () => { + // const manager = new CaretManager(); + // const caret = manager.createCaret('userId'); + // const handler = jest.fn(); + // + // manager.addEventListener(EventType.CaretManagerUpdated, handler); + // + // const index = new Index(); + // + // caret.update(index); + // + // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretUpdatedEvent)); + // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + // detail: { + // userId: caret.userId, + // index: index.serialize(), + // }, + // })); + // }); + // + // it('should remove caret', () => { + // const manager = new CaretManager(); + // const caret = manager.createCaret('userId'); + // + // manager.removeCaret(caret); + // + // expect(manager.getCaret(caret.userId)).toBeUndefined(); + // }); + // + // it('should dispatch caret removed event on caret removal', () => { + // const manager = new CaretManager(); + // const caret = manager.createCaret('userId'); + // const handler = jest.fn(); + // + // manager.addEventListener(EventType.CaretManagerUpdated, handler); + // + // manager.removeCaret(caret); + // + // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretRemovedEvent)); + // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + // detail: { + // userId: caret.userId, + // index: null, + // }, + // })); + // }); + // + // it('should not dispatch caret removed event if caret is not in the registry', () => { + // const manager = new CaretManager(); + // const caret = new Caret('userId'); + // + // const handler = jest.fn(); + // + // manager.addEventListener(EventType.CaretManagerUpdated, handler); + // + // manager.removeCaret(caret); + // + // expect(handler).not.toHaveBeenCalled(); + // }); }); diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index 82648d86..e38cf269 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,6 +89,8 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); + console.log('test'); + this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null; From e41ee6e4b0454ca6ebd4814a9af9a51d004944bd Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:13:40 +0100 Subject: [PATCH 21/44] Add at least one test --- .../src/CaretManagement/CaretManager.spec.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/model/src/CaretManagement/CaretManager.spec.ts b/packages/model/src/CaretManagement/CaretManager.spec.ts index 93976263..2b7e3dda 100644 --- a/packages/model/src/CaretManagement/CaretManager.spec.ts +++ b/packages/model/src/CaretManagement/CaretManager.spec.ts @@ -10,14 +10,14 @@ import { CaretManager } from './CaretManager.js'; import { jest } from '@jest/globals'; describe('CaretManager', () => { - // it('should create new caret', () => { - // const manager = new CaretManager(); - // - // const caret = manager.createCaret('userId'); - // - // expect(manager.getCaret(caret.userId)).toBe(caret); - // }); - // + it('should create new caret', () => { + const manager = new CaretManager(); + + const caret = manager.createCaret('userId'); + + expect(manager.getCaret(caret.userId)).toBe(caret); + }); + // it('should create new caret with passed index', () => { // const manager = new CaretManager(); // const index = new Index(); From b3be4516b5757507dc673f3008b2c70c6e6a0fb7 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Sun, 31 May 2026 22:26:42 +0100 Subject: [PATCH 22/44] fixes --- .github/actions/aggregate-report/action.yml | 7 ++++- .../mutation-tests-changed-files/action.yml | 3 ++- .github/actions/unit-tests/action.yml | 1 + .github/workflows/collaboration-manager.yml | 1 + .github/workflows/core.yml | 1 + .github/workflows/dom-adapters.yml | 26 ------------------- .github/workflows/model.yml | 1 + .github/workflows/ot-server.yml | 1 + 8 files changed, 13 insertions(+), 28 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 0b7ee789..cdc45755 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -4,6 +4,9 @@ inputs: package-name: description: 'Full package name (workspace)' required: true + working-directory: + description: 'Path to the ./packages/name_of_your_package_folder' + required: true outputs: report: description: 'Markdown report section generated by the action' @@ -26,11 +29,13 @@ runs: - name: Process mutation report uses: actions/github-script@v9 id: process_mutation_report + env: + WORKING_DIR: ${{ inputs.working-directory }} with: script: | const processMutationReport = require('./.github/scripts/process-mutation.js').processMutationReport; - core.setOutput('message', processMutationReport('mutation-report/mutation/mutation.json', 'mutation-report/changed-files.json')); + core.setOutput('message', processMutationReport(`${process.env.WORKING_DIR}/mutation-report/mutation/mutation.json`, 'mutation-report/changed-files.json')); - name: Generate markdown report id: section-report diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index 80b9ac06..20406940 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -51,7 +51,7 @@ runs: if: steps.changed-files.outputs.src_any_changed == 'true' shell: bash run: | - echo "${{ format('[{0}]', format('''{0}''', steps.changed-files.outputs.src_all_changed_files)) }}" > changed-files.json + echo "${{ format('[{0}]', format('"{0}"', steps.changed-files.outputs.src_all_changed_files)) }}" > changed-files.json - name: Save empty list if no files changed if: steps.changed-files.outputs.src_any_changed == 'false' @@ -65,6 +65,7 @@ runs: path: | ${{ inputs.working-directory }}/reports/mutation/mutation.json changed-files.json + retention-days: 3 # reporting will be triggered by workflow_run on workflow completion diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml index 4d3b9452..2eaf9299 100644 --- a/.github/actions/unit-tests/action.yml +++ b/.github/actions/unit-tests/action.yml @@ -33,6 +33,7 @@ runs: path: | ${{ inputs.working-directory }}/coverage ${{ inputs.working-directory }}/jest-report.json + retention-days: 3 - name: Run base coverage action uses: ./.github/actions/base-coverage diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index c6d43f71..b9013fe8 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -64,4 +64,5 @@ jobs: uses: ./.github/actions/aggregate-report with: package-name: '@editorjs/collaboration-manager' + working-directory: './packages/collaboration-manager' diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index daeb409d..a528f21b 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -70,3 +70,4 @@ jobs: uses: ./.github/actions/aggregate-report with: package-name: '@editorjs/core' + working-directory: './packages/core' diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index bcfb45b0..b274cbaf 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -74,30 +74,4 @@ jobs: uses: ./.github/actions/aggregate-report with: package-name: '@editorjs/dom-adapters' - - mutation-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Build dependencies - uses: ./.github/actions/build - with: - package-name: '@editorjs/dom-adapters' - - - name: Run mutation tests for changed files - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/mutation-tests-changed-files - with: - package-name: '@editorjs/dom-adapters' working-directory: './packages/dom-adapters' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - - name: Run mutation tests for all files - if: ${{ github.event_name == 'merge_group' }} - uses: ./.github/actions/mutation-tests-all-files - with: - package-name: '@editorjs/dom-adapters' - working-directory: './packages/dom-adapters' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index 2b0d0a88..ff1adfa2 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -70,3 +70,4 @@ jobs: uses: ./.github/actions/aggregate-report with: package-name: '@editorjs/model' + working-directory: './packages/model' diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index 5291c582..b2f63284 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -65,4 +65,5 @@ jobs: uses: ./.github/actions/aggregate-report with: package-name: '@editorjs/ot-server' + working-directory: './packages/ot-server' From 3865c3c975e1a1a3e4ec0c6acc5443ddf0562cd2 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 13:20:41 +0100 Subject: [PATCH 23/44] update files list formatting --- .github/actions/mutation-tests-changed-files/action.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/actions/mutation-tests-changed-files/action.yml b/.github/actions/mutation-tests-changed-files/action.yml index 20406940..fee294eb 100644 --- a/.github/actions/mutation-tests-changed-files/action.yml +++ b/.github/actions/mutation-tests-changed-files/action.yml @@ -35,7 +35,7 @@ runs: - 'src/**/*.ts' - '!src/**/*.spec.ts' - '!src/**/__mocks__/**' - separator: "','" + separator: ',' path: ${{ inputs.working-directory }} - name: Run mutation tests @@ -44,14 +44,14 @@ runs: STRYKER_DASHBOARD_API_KEY: ${{ inputs.stryker_dashboard_api_key }} shell: bash id: run-mutation-tests - run: yarn workspace ${{ inputs.package-name }} test:mutations --mutate ${{format('''{0}''', steps.changed-files.outputs.src_all_changed_files)}} --dashboard.module ${{ inputs.package-name }} + run: yarn workspace ${{ inputs.package-name }} test:mutations --mutate "${{ steps.changed-files.outputs.src_all_changed_files }}" --dashboard.module ${{ inputs.package-name }} continue-on-error: true - name: Save mutated files list if: steps.changed-files.outputs.src_any_changed == 'true' shell: bash run: | - echo "${{ format('[{0}]', format('"{0}"', steps.changed-files.outputs.src_all_changed_files)) }}" > changed-files.json + echo "${{ steps.changed-files.outputs.src_all_changed_files }}" | sed 's/,/","/g' | sed 's/^/["/;s/$/"]/' > changed-files.json - name: Save empty list if no files changed if: steps.changed-files.outputs.src_any_changed == 'false' From 389e2b5de1be7ae839f512d0182c5167c7b7e707 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 13:25:32 +0100 Subject: [PATCH 24/44] Update artifact paths --- .github/actions/aggregate-report/action.yml | 2 +- .github/scripts/process-mutation.js | 12 ++++++++---- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index cdc45755..d1f2107e 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -35,7 +35,7 @@ runs: script: | const processMutationReport = require('./.github/scripts/process-mutation.js').processMutationReport; - core.setOutput('message', processMutationReport(`${process.env.WORKING_DIR}/mutation-report/mutation/mutation.json`, 'mutation-report/changed-files.json')); + core.setOutput('message', processMutationReport('mutation-report', `${process.env.WORKING_DIR}/reports/mutation/mutation.json`, 'changed-files.json')); - name: Generate markdown report id: section-report diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index fa4aad73..6dd1d7ec 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -1,4 +1,5 @@ import fs from 'fs'; +import path from 'path'; function getMetrics(obj) { const mutants = []; @@ -29,17 +30,20 @@ function getMetrics(obj) { } -function processMutationReport(reportPath, changedFilesPath) { - if (!fs.existsSync(reportPath) && !fs.existsSync(changedFilesPath)) { +function processMutationReport(artitfactName, reportPath, changedFilesPath) { + const reportFile = path.join(artitfactName, reportPath); + const changedFilesFile = path.join(artitfactName, changedFilesPath); + + if (!fs.existsSync(reportFile) && !fs.existsSync(changedFilesFile)) { return ''; } - const changedFiles = JSON.parse(fs.readFileSync(changedFilesPath, 'utf8')); + const changedFiles = JSON.parse(fs.readFileSync(changedFilesFile, 'utf8')); if (changedFiles.length === 0) { return 'No files to mutate found.'; } - const raw = fs.readFileSync(reportPath, 'utf8'); + const raw = fs.readFileSync(reportFile, 'utf8'); const obj = JSON.parse(raw); From d5e785f9e9be6a515fa5faa272e22a4aacafd547 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 13:33:06 +0100 Subject: [PATCH 25/44] Test coverage drop --- .../src/CaretManagement/CaretManager.spec.ts | 194 +++++++++--------- .../BlockNode/BlockNode.integration.spec.ts | 2 +- .../src/entities/BlockNode/BlockNode.spec.ts | 2 +- 3 files changed, 99 insertions(+), 99 deletions(-) diff --git a/packages/model/src/CaretManagement/CaretManager.spec.ts b/packages/model/src/CaretManagement/CaretManager.spec.ts index 2b7e3dda..f4376a3a 100644 --- a/packages/model/src/CaretManagement/CaretManager.spec.ts +++ b/packages/model/src/CaretManagement/CaretManager.spec.ts @@ -18,101 +18,101 @@ describe('CaretManager', () => { expect(manager.getCaret(caret.userId)).toBe(caret); }); - // it('should create new caret with passed index', () => { - // const manager = new CaretManager(); - // const index = new Index(); - // - // const caret = manager.createCaret('userId', index); - // - // expect(caret.index).toBe(index); - // }); - // - // it('should dispatch caret added event on caret creation', () => { - // const manager = new CaretManager(); - // const handler = jest.fn(); - // - // manager.addEventListener(EventType.CaretManagerUpdated, handler); - // - // const index = new Index(); - // const caret = manager.createCaret('userId', index); - // - // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretAddedEvent)); - // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - // detail: { - // userId: caret.userId, - // index: index.serialize(), - // }, - // })); - // }); - // - // it('should update caret', () => { - // const manager = new CaretManager(); - // const caret = manager.createCaret('userId'); - // - // const index = new Index(); - // - // caret.update(index); - // - // expect(manager.getCaret(caret.userId)?.index).toBe(index); - // }); - // - // it('should dispatch caret updated event on caret update', () => { - // const manager = new CaretManager(); - // const caret = manager.createCaret('userId'); - // const handler = jest.fn(); - // - // manager.addEventListener(EventType.CaretManagerUpdated, handler); - // - // const index = new Index(); - // - // caret.update(index); - // - // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretUpdatedEvent)); - // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - // detail: { - // userId: caret.userId, - // index: index.serialize(), - // }, - // })); - // }); - // - // it('should remove caret', () => { - // const manager = new CaretManager(); - // const caret = manager.createCaret('userId'); - // - // manager.removeCaret(caret); - // - // expect(manager.getCaret(caret.userId)).toBeUndefined(); - // }); - // - // it('should dispatch caret removed event on caret removal', () => { - // const manager = new CaretManager(); - // const caret = manager.createCaret('userId'); - // const handler = jest.fn(); - // - // manager.addEventListener(EventType.CaretManagerUpdated, handler); - // - // manager.removeCaret(caret); - // - // expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretRemovedEvent)); - // expect(handler).toHaveBeenCalledWith(expect.objectContaining({ - // detail: { - // userId: caret.userId, - // index: null, - // }, - // })); - // }); - // - // it('should not dispatch caret removed event if caret is not in the registry', () => { - // const manager = new CaretManager(); - // const caret = new Caret('userId'); - // - // const handler = jest.fn(); - // - // manager.addEventListener(EventType.CaretManagerUpdated, handler); - // - // manager.removeCaret(caret); - // - // expect(handler).not.toHaveBeenCalled(); - // }); + it('should create new caret with passed index', () => { + const manager = new CaretManager(); + const index = new Index(); + + const caret = manager.createCaret('userId', index); + + expect(caret.index).toBe(index); + }); + + it('should dispatch caret added event on caret creation', () => { + const manager = new CaretManager(); + const handler = jest.fn(); + + manager.addEventListener(EventType.CaretManagerUpdated, handler); + + const index = new Index(); + const caret = manager.createCaret('userId', index); + + expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretAddedEvent)); + expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + detail: { + userId: caret.userId, + index: index.serialize(), + }, + })); + }); + + it('should update caret', () => { + const manager = new CaretManager(); + const caret = manager.createCaret('userId'); + + const index = new Index(); + + caret.update(index); + + expect(manager.getCaret(caret.userId)?.index).toBe(index); + }); + + it('should dispatch caret updated event on caret update', () => { + const manager = new CaretManager(); + const caret = manager.createCaret('userId'); + const handler = jest.fn(); + + manager.addEventListener(EventType.CaretManagerUpdated, handler); + + const index = new Index(); + + caret.update(index); + + expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretUpdatedEvent)); + expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + detail: { + userId: caret.userId, + index: index.serialize(), + }, + })); + }); + + it('should remove caret', () => { + const manager = new CaretManager(); + const caret = manager.createCaret('userId'); + + manager.removeCaret(caret); + + expect(manager.getCaret(caret.userId)).toBeUndefined(); + }); + + it('should dispatch caret removed event on caret removal', () => { + const manager = new CaretManager(); + const caret = manager.createCaret('userId'); + const handler = jest.fn(); + + manager.addEventListener(EventType.CaretManagerUpdated, handler); + + manager.removeCaret(caret); + + expect(handler).toHaveBeenCalledWith(expect.any(CaretManagerCaretRemovedEvent)); + expect(handler).toHaveBeenCalledWith(expect.objectContaining({ + detail: { + userId: caret.userId, + index: null, + }, + })); + }); + + it('should not dispatch caret removed event if caret is not in the registry', () => { + const manager = new CaretManager(); + const caret = new Caret('userId'); + + const handler = jest.fn(); + + manager.addEventListener(EventType.CaretManagerUpdated, handler); + + manager.removeCaret(caret); + + expect(handler).not.toHaveBeenCalled(); + }); }); diff --git a/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts b/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts index 871f84d6..bf9ebb02 100644 --- a/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts +++ b/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts @@ -5,7 +5,7 @@ import type { InlineFragment } from '../inline-fragments/index.js'; import { createInlineToolName } from '../inline-fragments/index.js'; import { ValueNode } from '../ValueNode/index.js'; -describe('BlockNode integration tests', () => { +describe.skip('BlockNode integration tests', () => { it('should create ValueNode by primitive value', () => { const value = 'value'; const newValue = 'updated value'; diff --git a/packages/model/src/entities/BlockNode/BlockNode.spec.ts b/packages/model/src/entities/BlockNode/BlockNode.spec.ts index 0de84617..a6567e8e 100644 --- a/packages/model/src/entities/BlockNode/BlockNode.spec.ts +++ b/packages/model/src/entities/BlockNode/BlockNode.spec.ts @@ -42,7 +42,7 @@ const createBlockNodeWithData = (data: BlockNodeDataSerialized, tunes: Record { +describe.skip('BlockNode', () => { describe('NonExistingKeyError', () => { it('should format message with key', () => { const key = createDataKey('k'); From 7b9bb7e64bf6a103391d6d842e62d18e661e9060 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 13:45:21 +0100 Subject: [PATCH 26/44] Always run report, add dashboard url to mutation report --- .github/actions/aggregate-report/action.yml | 18 ++++++++++-------- .github/scripts/process-mutation.js | 6 +++++- .github/workflows/collaboration-manager.yml | 2 +- .github/workflows/core.yml | 2 +- .github/workflows/dom-adapters.yml | 2 +- .github/workflows/model.yml | 2 +- .github/workflows/ot-server.yml | 2 +- packages/model/src/entities/BlockNode/index.ts | 2 -- 8 files changed, 20 insertions(+), 16 deletions(-) diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index d1f2107e..2985355a 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -17,6 +17,10 @@ runs: - name: Download artifacts uses: actions/download-artifact@v8 + - name: Find current PR's number + uses: jwalton/gh-find-current-pr@v1 + id: findPr + - name: Process coverage reports uses: actions/github-script@v9 id: process_test_report @@ -31,11 +35,13 @@ runs: id: process_mutation_report env: WORKING_DIR: ${{ inputs.working-directory }} + PR_NUMBER: ${{ steps.findPr.outputs.number }} + PACKAGE_NAME: ${{ inputs.package-name }} with: script: | const processMutationReport = require('./.github/scripts/process-mutation.js').processMutationReport; - core.setOutput('message', processMutationReport('mutation-report', `${process.env.WORKING_DIR}/reports/mutation/mutation.json`, 'changed-files.json')); + core.setOutput('message', processMutationReport('mutation-report', `${process.env.WORKING_DIR}/reports/mutation/mutation.json`, 'changed-files.json', process.env.PR_NUMBER, process.env.PACKAGE_NAME)); - name: Generate markdown report id: section-report @@ -46,14 +52,10 @@ runs: PACKAGE: ${{ inputs.package-name }} with: script: | - const generateReport = require('./.github/scripts/generate-report.js').generateReport; - const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; + const generateReport = require('./.github/scripts/generate-report.js').generateReport; + const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; - core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); - - - name: Find current PR's number - uses: jwalton/gh-find-current-pr@v1 - id: findPr + core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); - name: Acquire comment-update lock diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 6dd1d7ec..0d830f93 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -30,7 +30,7 @@ function getMetrics(obj) { } -function processMutationReport(artitfactName, reportPath, changedFilesPath) { +function processMutationReport(artitfactName, reportPath, changedFilesPath, prNumber, packageName) { const reportFile = path.join(artitfactName, reportPath); const changedFilesFile = path.join(artitfactName, changedFilesPath); @@ -65,6 +65,10 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath) { message += `> [!WARNING]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; } + if (prNumber && packageName) { + const encodedPackageName = encodeURIComponent(packageName); + message += `\nDashboard URL: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${prNumber}?module=${encodedPackageName}`; + } return message; } diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index b9013fe8..e098dfdf 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -53,7 +53,7 @@ jobs: report: name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} needs: [tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index a528f21b..f47d249b 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -59,7 +59,7 @@ jobs: report: name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index b274cbaf..44774bd1 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -63,7 +63,7 @@ jobs: report: name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index ff1adfa2..98457a4c 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -59,7 +59,7 @@ jobs: report: name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index b2f63284..b77a8e13 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -54,7 +54,7 @@ jobs: report: name: Aggregate and post report - if: ${{ github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} needs: [tests] runs-on: ubuntu-latest steps: diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index e38cf269..82648d86 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,8 +89,6 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); - console.log('test'); - this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null; From 952d1e8215a73e84eec10e8df5d6ca481fcfb593 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 13:53:32 +0100 Subject: [PATCH 27/44] fix base branch tests checkout --- .github/actions/base-coverage/action.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/actions/base-coverage/action.yml b/.github/actions/base-coverage/action.yml index f78c4871..066e82c0 100644 --- a/.github/actions/base-coverage/action.yml +++ b/.github/actions/base-coverage/action.yml @@ -14,7 +14,7 @@ runs: - name: Checkout base ref uses: actions/checkout@v6 with: - ref: ${{ steps.resolve_base.outputs.base_ref }} + ref: ${{ github.event.pull_request.base.ref }} fetch-depth: 0 - name: Setup Node @@ -42,4 +42,3 @@ runs: path: | ${{ inputs.working-directory }}/coverage ${{ inputs.working-directory }}/jest-report.json - From eca9bfe7c0b76b0df70ccbb07cc56f26721a747f Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 14:02:43 +0100 Subject: [PATCH 28/44] Test dashboard url --- .../model/src/entities/BlockNode/BlockNode.integration.spec.ts | 2 +- packages/model/src/entities/BlockNode/BlockNode.spec.ts | 2 +- packages/model/src/entities/BlockNode/index.ts | 2 ++ 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts b/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts index bf9ebb02..871f84d6 100644 --- a/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts +++ b/packages/model/src/entities/BlockNode/BlockNode.integration.spec.ts @@ -5,7 +5,7 @@ import type { InlineFragment } from '../inline-fragments/index.js'; import { createInlineToolName } from '../inline-fragments/index.js'; import { ValueNode } from '../ValueNode/index.js'; -describe.skip('BlockNode integration tests', () => { +describe('BlockNode integration tests', () => { it('should create ValueNode by primitive value', () => { const value = 'value'; const newValue = 'updated value'; diff --git a/packages/model/src/entities/BlockNode/BlockNode.spec.ts b/packages/model/src/entities/BlockNode/BlockNode.spec.ts index a6567e8e..0de84617 100644 --- a/packages/model/src/entities/BlockNode/BlockNode.spec.ts +++ b/packages/model/src/entities/BlockNode/BlockNode.spec.ts @@ -42,7 +42,7 @@ const createBlockNodeWithData = (data: BlockNodeDataSerialized, tunes: Record { +describe('BlockNode', () => { describe('NonExistingKeyError', () => { it('should format message with key', () => { const key = createDataKey('k'); diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index 82648d86..e38cf269 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,6 +89,8 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); + console.log('test'); + this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null; From 72a3a1c829c6198347e58d3a8f4b06688a1dee07 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 14:52:40 +0100 Subject: [PATCH 29/44] Add json report for dom-adapters --- packages/dom-adapters/package.json | 2 +- packages/model/src/entities/BlockNode/index.ts | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/dom-adapters/package.json b/packages/dom-adapters/package.json index e04d5666..4778a29b 100644 --- a/packages/dom-adapters/package.json +++ b/packages/dom-adapters/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true ", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json ", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index e38cf269..82648d86 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,8 +89,6 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); - console.log('test'); - this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null; From 9da66ece80efb1c0519998c31c82b7c99427bf62 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 14:55:08 +0100 Subject: [PATCH 30/44] Mutation score to percent conversion --- .github/scripts/process-mutation.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 0d830f93..f01b0679 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -49,7 +49,7 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu const metrics = getMetrics(obj); - let message = `Mutation tests run with mutation score ${metrics.score}%.\n`; + let message = `Mutation tests run with mutation score ${(metrics.score * 100).toFixed(2)}%.\n`; if (metrics.survived) { message += `Survived mutants: ${metrics.survived}\n`; From 1685a8c02eeb8eddce93525ac8b76dc059d9810a Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 14:57:24 +0100 Subject: [PATCH 31/44] Switch warning and caution --- .github/scripts/process-mutation.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index f01b0679..f8bfce76 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -60,9 +60,9 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu } if (metrics.score < metrics.thresholds.low) { - message += `> [!CAUTION]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; + message += `> [!WARNING]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; } else if (metrics.score < metrics.thresholds.high) { - message += `> [!WARNING]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; + message += `> [!CAUTION]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; } if (prNumber && packageName) { From b30e1fdfe9b5207829cb3391225a31ac01f62a6d Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 15:20:52 +0100 Subject: [PATCH 32/44] resolve review comments --- .github/actions/unit-tests/action.yml | 1 + .github/scripts/compare-coverage.js | 4 +-- .github/scripts/process-mutation.js | 10 +++++--- .github/workflows/collaboration-manager.yml | 2 +- .github/workflows/core.yml | 2 +- .github/workflows/dom-adapters.yml | 2 +- .github/workflows/model.yml | 2 +- .github/workflows/ot-server.yml | 2 +- packages/dom-adapters/package.json | 2 +- packages/dom-adapters/stryker.conf.mjs | 27 +++++++++++---------- 10 files changed, 30 insertions(+), 24 deletions(-) diff --git a/.github/actions/unit-tests/action.yml b/.github/actions/unit-tests/action.yml index 2eaf9299..d28bc017 100644 --- a/.github/actions/unit-tests/action.yml +++ b/.github/actions/unit-tests/action.yml @@ -36,6 +36,7 @@ runs: retention-days: 3 - name: Run base coverage action + if: ${{ github.event_name == 'pull_request' }} uses: ./.github/actions/base-coverage with: package-name: ${{ inputs.package-name }} diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index f24ec80d..e38923f4 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -103,12 +103,12 @@ export function processReports(headDir, baseDir) { for (const cat in categories) { if (categories[cat].delta < 0) { - warning += `Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; + warning += `\n> Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; } } if (warning.length > 0) { - message += `> [!WARNING]\n> ${message}\n`; + message += `> [!WARNING]${warning}`; } return message; diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index f8bfce76..6905f07b 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -34,7 +34,7 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu const reportFile = path.join(artitfactName, reportPath); const changedFilesFile = path.join(artitfactName, changedFilesPath); - if (!fs.existsSync(reportFile) && !fs.existsSync(changedFilesFile)) { + if (!fs.existsSync(changedFilesFile)) { return ''; } const changedFiles = JSON.parse(fs.readFileSync(changedFilesFile, 'utf8')); @@ -43,6 +43,10 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu return 'No files to mutate found.'; } + if (!fs.existsSync(reportFile)) { + return 'Report artifact not found.'; + } + const raw = fs.readFileSync(reportFile, 'utf8'); const obj = JSON.parse(raw); @@ -59,9 +63,9 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu message += `Not covered mutants: ${metrics.notCovered}\n`; } - if (metrics.score < metrics.thresholds.low) { + if (metrics.score * 100 < metrics.thresholds.low) { message += `> [!WARNING]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; - } else if (metrics.score < metrics.thresholds.high) { + } else if (metrics.score * 100 < metrics.thresholds.high) { message += `> [!CAUTION]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; } diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index e098dfdf..ea8b31fb 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -53,7 +53,7 @@ jobs: report: name: Aggregate and post report - if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.event_name == 'pull_request' }} needs: [tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index f47d249b..4740207d 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -59,7 +59,7 @@ jobs: report: name: Aggregate and post report - if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.event_name == 'pull_request' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index 44774bd1..206a284a 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -63,7 +63,7 @@ jobs: report: name: Aggregate and post report - if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.event_name == 'pull_request' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index 98457a4c..9769c0d6 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -59,7 +59,7 @@ jobs: report: name: Aggregate and post report - if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.event_name == 'pull_request' }} needs: [tests, mutation-tests] runs-on: ubuntu-latest steps: diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index b77a8e13..0e376073 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -54,7 +54,7 @@ jobs: report: name: Aggregate and post report - if: ${{ always() && github.ref != 'refs/heads/main' && github.head_ref != 'main' }} + if: ${{ always() && github.event_name == 'pull_request' }} needs: [tests] runs-on: ubuntu-latest steps: diff --git a/packages/dom-adapters/package.json b/packages/dom-adapters/package.json index 4778a29b..050e5109 100644 --- a/packages/dom-adapters/package.json +++ b/packages/dom-adapters/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json ", + "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/dom-adapters/stryker.conf.mjs b/packages/dom-adapters/stryker.conf.mjs index 53d6167b..e8addcef 100644 --- a/packages/dom-adapters/stryker.conf.mjs +++ b/packages/dom-adapters/stryker.conf.mjs @@ -2,29 +2,30 @@ /** @type {import('@stryker-mutator/api/core').PartialStrykerOptions} */ const config = { _comment: - "This config was generated using 'stryker init'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.", - packageManager: "yarn", + 'This config was generated using \'stryker init\'. Please take a look at: https://stryker-mutator.io/docs/stryker-js/configuration/ for more information.', + packageManager: 'yarn', thresholds: { break: 0, }, - thresholds_comment: "Minimum required coverage. Increase once we're closer to 100%.", + thresholds_comment: 'Minimum required coverage. Increase once we\'re closer to 100%.', clearTextReporter: { allowEmojis: true, }, reporters: [ - "html", - "clear-text", - "progress", - "dashboard", + 'json', + 'html', + 'clear-text', + 'progress', + 'dashboard', ], - testRunner: "jest", + testRunner: 'jest', testRunner_comment: - "Take a look at https://stryker-mutator.io/docs/stryker-js/jest-runner for information about the jest plugin.", - coverageAnalysis: "perTest", - tsconfigFile: "tsconfig.json", - checkers: ["typescript"], + 'Take a look at https://stryker-mutator.io/docs/stryker-js/jest-runner for information about the jest plugin.', + coverageAnalysis: 'perTest', + tsconfigFile: 'tsconfig.json', + checkers: ['typescript'], timeoutMS: 10000, - mutate: ["./src/**/*.ts", "!./src/**/__mocks__/*.ts", "!./src/**/*.spec.ts"], + mutate: ['./src/**/*.ts', '!./src/**/__mocks__/*.ts', '!./src/**/*.spec.ts'], /* * In some cases PRs might not have any unit-tests */ From 488bf4c8f4181d739dc7ff69335adeafd947541c Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 17:34:06 +0100 Subject: [PATCH 33/44] Extract duplication to a common workflow. Run workflow only if package or dependencies have changed --- .../mutation-tests-all-files/action.yml | 4 +- .github/workflows/collaboration-manager.yml | 69 ++-------- .github/workflows/core.yml | 75 ++-------- .github/workflows/dom-adapters.yml | 79 ++--------- .github/workflows/model.yml | 74 ++-------- .github/workflows/ot-server.yml | 70 ++-------- .github/workflows/package-check.yml | 128 ++++++++++++++++++ scripts/should-run-ci.sh | 57 ++++++++ 8 files changed, 228 insertions(+), 328 deletions(-) create mode 100644 .github/workflows/package-check.yml create mode 100644 scripts/should-run-ci.sh diff --git a/.github/actions/mutation-tests-all-files/action.yml b/.github/actions/mutation-tests-all-files/action.yml index 87e8bb5e..a0b2c52d 100644 --- a/.github/actions/mutation-tests-all-files/action.yml +++ b/.github/actions/mutation-tests-all-files/action.yml @@ -8,7 +8,9 @@ inputs: stryker_dashboard_api_key: description: 'Stryker dashboard api key' required: true - + working-directory: + description: 'Path to the ./packages/name_of_your_package_folder' + required: true runs: using: "composite" steps: diff --git a/.github/workflows/collaboration-manager.yml b/.github/workflows/collaboration-manager.yml index ea8b31fb..28409812 100644 --- a/.github/workflows/collaboration-manager.yml +++ b/.github/workflows/collaboration-manager.yml @@ -3,66 +3,13 @@ on: pull_request: merge_group: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - run: yarn - - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/collaboration-manager' - - - name: Run ESLint check - uses: ./.github/actions/lint - with: - package-name: '@editorjs/collaboration-manager' - - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - run: yarn - - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/collaboration-manager' - - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - package-name: '@editorjs/collaboration-manager' - working-directory: './packages/collaboration-manager' - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/collaboration-manager' - - report: - name: Aggregate and post report - if: ${{ always() && github.event_name == 'pull_request' }} - needs: [tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/collaboration-manager' - working-directory: './packages/collaboration-manager' + package-check: + uses: ./.github/workflows/package-check.yml + with: + package-name: '@editorjs/collaboration-manager' + working-directory: './packages/collaboration-manager' + include-mutations: false + secrets: + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/.github/workflows/core.yml b/.github/workflows/core.yml index 4740207d..0904e81a 100644 --- a/.github/workflows/core.yml +++ b/.github/workflows/core.yml @@ -2,72 +2,13 @@ name: Core check on: pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Run ESLint check - uses: ./.github/actions/lint - with: - package-name: '@editorjs/core' - - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - package-name: '@editorjs/core' - working-directory: './packages/core' - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/core' - - mutation-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Run mutation tests for changed files - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/mutation-tests-changed-files - with: - package-name: '@editorjs/core' - working-directory: './packages/core' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - - name: Run mutation tests for all files - if: ${{ github.event_name == 'merge_group' }} - uses: ./.github/actions/mutation-tests-all-files - with: - package-name: '@editorjs/core' - working-directory: './packages/core' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - report: - name: Aggregate and post report - if: ${{ always() && github.event_name == 'pull_request' }} - needs: [tests, mutation-tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 + package-check: + uses: ./.github/workflows/package-check.yml + with: + package-name: '@editorjs/core' + working-directory: './packages/core' + include-mutations: true + secrets: + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/core' - working-directory: './packages/core' diff --git a/.github/workflows/dom-adapters.yml b/.github/workflows/dom-adapters.yml index 206a284a..eb2c8dc7 100644 --- a/.github/workflows/dom-adapters.yml +++ b/.github/workflows/dom-adapters.yml @@ -2,76 +2,13 @@ name: Dom-adapters check on: pull_request: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Run ESLint check - uses: ./.github/actions/lint - with: - package-name: '@editorjs/dom-adapters' - - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - working-directory: './packages/dom-adapters' - package-name: '@editorjs/dom-adapters' - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/dom-adapters' - - mutation-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Build dependencies - uses: ./.github/actions/build - with: - package-name: '@editorjs/dom-adapters' - - - name: Run mutation tests for changed files - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/mutation-tests-changed-files - with: - package-name: '@editorjs/dom-adapters' - working-directory: './packages/dom-adapters' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - - name: Run mutation tests for all files - if: ${{ github.event_name == 'merge_group' }} - uses: ./.github/actions/mutation-tests-all-files - with: - package-name: '@editorjs/dom-adapters' - working-directory: './packages/dom-adapters' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - report: - name: Aggregate and post report - if: ${{ always() && github.event_name == 'pull_request' }} - needs: [tests, mutation-tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 + package-check: + uses: ./.github/workflows/package-check.yml + with: + package-name: '@editorjs/dom-adapters' + working-directory: './packages/dom-adapters' + include-mutations: true + secrets: + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/dom-adapters' - working-directory: './packages/dom-adapters' diff --git a/.github/workflows/model.yml b/.github/workflows/model.yml index 9769c0d6..685744f2 100644 --- a/.github/workflows/model.yml +++ b/.github/workflows/model.yml @@ -3,71 +3,13 @@ on: pull_request: merge_group: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Run ESLint check - uses: ./.github/actions/lint - with: - package-name: '@editorjs/model' - - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - package-name: '@editorjs/model' - working-directory: './packages/model' - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/model' - - mutation-tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - name: Run mutation tests for changed files - if: ${{ github.event_name == 'pull_request' }} - uses: ./.github/actions/mutation-tests-changed-files - with: - package-name: '@editorjs/model' - working-directory: './packages/model' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - - name: Run mutation tests for all files - if: ${{ github.event_name == 'merge_group' }} - uses: ./.github/actions/mutation-tests-all-files - with: - package-name: '@editorjs/model' - working-directory: './packages/model' - stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - report: - name: Aggregate and post report - if: ${{ always() && github.event_name == 'pull_request' }} - needs: [tests, mutation-tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 + package-check: + uses: ./.github/workflows/package-check.yml + with: + package-name: '@editorjs/model' + working-directory: './packages/model' + include-mutations: true + secrets: + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/model' - working-directory: './packages/model' diff --git a/.github/workflows/ot-server.yml b/.github/workflows/ot-server.yml index 0e376073..2b102881 100644 --- a/.github/workflows/ot-server.yml +++ b/.github/workflows/ot-server.yml @@ -3,67 +3,13 @@ on: pull_request: merge_group: -concurrency: - group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} - cancel-in-progress: true - jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - run: yarn - - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/ot-server' - - - name: Run ESLint check - uses: ./.github/actions/lint - with: - package-name: '@editorjs/ot-server' - - tests: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - - run: yarn - - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/ot-server' - - - name: Run unit tests - uses: ./.github/actions/unit-tests - with: - package-name: '@editorjs/ot-server' - working-directory: './packages/ot-server' - - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v6 - - name: Build the package - uses: ./.github/actions/build - with: - package-name: '@editorjs/ot-server' - - report: - name: Aggregate and post report - if: ${{ always() && github.event_name == 'pull_request' }} - needs: [tests] - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - - - name: Generate aggregated report and comment - uses: ./.github/actions/aggregate-report - with: - package-name: '@editorjs/ot-server' - working-directory: './packages/ot-server' + package-check: + uses: ./.github/workflows/package-check.yml + with: + package-name: '@editorjs/ot-server' + working-directory: './packages/ot-server' + include-mutations: false + secrets: + stryker_dashboard_api_key: ${{ secrets.STRYKER_DASHBOARD_API_KEY }} diff --git a/.github/workflows/package-check.yml b/.github/workflows/package-check.yml new file mode 100644 index 00000000..4493c579 --- /dev/null +++ b/.github/workflows/package-check.yml @@ -0,0 +1,128 @@ +name: Package check +on: + workflow_call: + inputs: + package-name: + description: 'Full package name (e.g., @editorjs/core)' + required: true + type: string + working-directory: + description: 'Package working directory (e.g., ./packages/core)' + required: true + type: string + include-mutations: + description: 'Whether to include mutation tests' + required: false + type: boolean + default: false + secrets: + stryker_dashboard_api_key: + description: 'Stryker dashboard API key for mutation tests' + required: false + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +jobs: + should-run: + name: Check if package needs CI + runs-on: ubuntu-latest + outputs: + run: ${{ steps.check.outputs.run }} + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 0 + + - name: Determine if CI should run + id: check + run: | + # Always run for merge_group + if [ "${{ github.event_name }}" != "pull_request" ]; then + echo "run=true" >> $GITHUB_OUTPUT + exit 0 + fi + + # Check if package or dependencies changed (returns 0 if yes, 1 if no) + if bash scripts/should-run-ci.sh "${{ github.base_ref }}" "${{ inputs.working-directory }}"; then + echo "run=true" >> $GITHUB_OUTPUT + else + echo "run=false" >> $GITHUB_OUTPUT + fi + + lint: + needs: should-run + if: ${{ needs.should-run.outputs.run == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Run ESLint check + uses: ./.github/actions/lint + with: + package-name: ${{ inputs.package-name }} + + tests: + needs: should-run + if: ${{ needs.should-run.outputs.run == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Run unit tests + uses: ./.github/actions/unit-tests + with: + package-name: ${{ inputs.package-name }} + working-directory: ${{ inputs.working-directory }} + + build: + needs: should-run + if: ${{ needs.should-run.outputs.run == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - name: Build the package + uses: ./.github/actions/build + with: + package-name: ${{ inputs.package-name }} + + mutation-tests: + needs: should-run + if: ${{ inputs.include-mutations && needs.should-run.outputs.run == 'true' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Run mutation tests for changed files + if: ${{ github.event_name == 'pull_request' }} + uses: ./.github/actions/mutation-tests-changed-files + with: + package-name: ${{ inputs.package-name }} + working-directory: ${{ inputs.working-directory }} + stryker_dashboard_api_key: ${{ secrets.stryker_dashboard_api_key }} + + - name: Run mutation tests for all files + if: ${{ github.event_name == 'merge_group' }} + uses: ./.github/actions/mutation-tests-all-files + with: + package-name: ${{ inputs.package-name }} + working-directory: ${{ inputs.working-directory }} + stryker_dashboard_api_key: ${{ secrets.stryker_dashboard_api_key }} + + report: + name: Aggregate and post report + needs: [tests, mutation-tests] + if: ${{ always() && github.event_name == 'pull_request' && (needs.tests.result != 'skipped' || needs.mutation-tests.result != 'skipped') }} + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v6 + + - name: Generate aggregated report and comment + uses: ./.github/actions/aggregate-report + with: + package-name: ${{ inputs.package-name }} + working-directory: ${{ inputs.working-directory }} + + + diff --git a/scripts/should-run-ci.sh b/scripts/should-run-ci.sh new file mode 100644 index 00000000..e328562d --- /dev/null +++ b/scripts/should-run-ci.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +# Detect if a package needs CI to run +# Checks if the package or any of its dependencies (direct or transitive) have changed +# Usage: ./scripts/should-run-ci.sh + +BASE_REF="${1}" +PKG_DIR="${2}" + +if [ -z "$BASE_REF" ] || [ -z "$PKG_DIR" ]; then + echo "Usage: ./scripts/should-run-ci.sh " + exit 1 +fi + +# Helper function to get all dependencies (direct and transitive) +get_all_deps() { + local pkg=$1 + local visited=$2 + + # Avoid infinite loops + if echo "$visited" | grep -q "^$pkg$"; then + return + fi + + visited="$visited $pkg" + + # Get direct dependencies + local deps=$(grep -o '"@editorjs/[^"]*"' "packages/$pkg/package.json" 2>/dev/null | sed 's/"//g' | sed 's/@editorjs\///' || true) + + echo "$deps" + + # Recursively get dependencies of dependencies + for dep in $deps; do + get_all_deps "$dep" "$visited" + done +} + +# Check if this package changed +if git diff --name-only "$BASE_REF...HEAD" | grep -q "^${PKG_DIR}/"; then + exit 0 +fi + +# Get ALL dependencies (direct and transitive) +PKG_NAME=$(basename "$PKG_DIR") +ALL_DEPS=$(get_all_deps "$PKG_NAME" "" | sort -u) + +# Check if any dependency (direct or indirect) changed +for dep in $ALL_DEPS; do + # Map package name to directory - try exact match and underscore variant + DEP_DIR=$(find packages -maxdepth 1 -type d \( -name "$dep" -o -name "$(echo $dep | sed 's/-/_/g')" \) 2>/dev/null | head -1) + if [ -n "$DEP_DIR" ] && git diff --name-only "$BASE_REF...HEAD" | grep -q "^$DEP_DIR/"; then + exit 0 + fi +done + +exit 1 + From 8f2da60905b0cc1f3af1d84776ed360ec3274963 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 17:37:56 +0100 Subject: [PATCH 34/44] resolve base ref for shallow clone --- scripts/should-run-ci.sh | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/should-run-ci.sh b/scripts/should-run-ci.sh index e328562d..242b026f 100644 --- a/scripts/should-run-ci.sh +++ b/scripts/should-run-ci.sh @@ -12,6 +12,21 @@ if [ -z "$BASE_REF" ] || [ -z "$PKG_DIR" ]; then exit 1 fi +# Handle different base ref formats +# Try to resolve the base ref - could be just "main" or "origin/main" +RESOLVED_BASE_REF="$BASE_REF" + +# If base ref doesn't exist, try with origin prefix +if ! git rev-parse --verify "$RESOLVED_BASE_REF" >/dev/null 2>&1; then + RESOLVED_BASE_REF="origin/$BASE_REF" +fi + +# If still doesn't exist, try to fetch it +if ! git rev-parse --verify "$RESOLVED_BASE_REF" >/dev/null 2>&1; then + git fetch origin "$BASE_REF:$BASE_REF" 2>/dev/null || true + RESOLVED_BASE_REF="$BASE_REF" +fi + # Helper function to get all dependencies (direct and transitive) get_all_deps() { local pkg=$1 @@ -36,7 +51,7 @@ get_all_deps() { } # Check if this package changed -if git diff --name-only "$BASE_REF...HEAD" | grep -q "^${PKG_DIR}/"; then +if git diff --name-only "$RESOLVED_BASE_REF...HEAD" | grep -q "^${PKG_DIR}/"; then exit 0 fi @@ -48,7 +63,7 @@ ALL_DEPS=$(get_all_deps "$PKG_NAME" "" | sort -u) for dep in $ALL_DEPS; do # Map package name to directory - try exact match and underscore variant DEP_DIR=$(find packages -maxdepth 1 -type d \( -name "$dep" -o -name "$(echo $dep | sed 's/-/_/g')" \) 2>/dev/null | head -1) - if [ -n "$DEP_DIR" ] && git diff --name-only "$BASE_REF...HEAD" | grep -q "^$DEP_DIR/"; then + if [ -n "$DEP_DIR" ] && git diff --name-only "$RESOLVED_BASE_REF...HEAD" | grep -q "^$DEP_DIR/"; then exit 0 fi done From fbe7bd8ec7193ff61580b1f90580acdf0c1a734a Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 17:59:47 +0100 Subject: [PATCH 35/44] Update script --- .github/scripts/should-run-ci.sh | 142 +++++++++++++++++++++++++++++++ scripts/should-run-ci.sh | 72 ---------------- 2 files changed, 142 insertions(+), 72 deletions(-) create mode 100644 .github/scripts/should-run-ci.sh delete mode 100644 scripts/should-run-ci.sh diff --git a/.github/scripts/should-run-ci.sh b/.github/scripts/should-run-ci.sh new file mode 100644 index 00000000..ebe72991 --- /dev/null +++ b/.github/scripts/should-run-ci.sh @@ -0,0 +1,142 @@ +#!/bin/bash + +# Detect if a package needs CI to run +# Checks if the package or any of its dependencies (direct or transitive) have changed +# Usage: bash .github/scripts/should-run-ci.sh [--verbose] + +# Don't exit on error - we'll handle errors explicitly +set +e + +BASE_REF="${1}" +PKG_DIR="${2}" +VERBOSE="${3}" + +if [ -z "$BASE_REF" ] || [ -z "$PKG_DIR" ]; then + echo "Usage: bash .github/scripts/should-run-ci.sh [--verbose]" + exit 1 +fi + +# Function for verbose logging +debug() { + if [ "$VERBOSE" = "--verbose" ] || [ "$VERBOSE" = "-v" ]; then + echo "[DEBUG] $@" >&2 + fi +} + +# Try to resolve the base ref - try multiple variants +RESOLVED_BASE_REF="" + +# Try 1: As-is (for local branches like "main") +if git rev-parse --verify "$BASE_REF" >/dev/null 2>&1; then + RESOLVED_BASE_REF="$BASE_REF" + debug "Resolved base ref as: $BASE_REF" +fi + +# Try 2: With origin/ prefix (for remote-tracking branches) +if [ -z "$RESOLVED_BASE_REF" ] && git rev-parse --verify "origin/$BASE_REF" >/dev/null 2>&1; then + RESOLVED_BASE_REF="origin/$BASE_REF" + debug "Resolved base ref as: origin/$BASE_REF" +fi + +# Try 3: Hardcoded main/master (for GitHub Actions or when base_ref not provided properly) +if [ -z "$RESOLVED_BASE_REF" ]; then + if git rev-parse --verify "origin/main" >/dev/null 2>&1; then + RESOLVED_BASE_REF="origin/main" + debug "Resolved base ref as: origin/main" + elif git rev-parse --verify "main" >/dev/null 2>&1; then + RESOLVED_BASE_REF="main" + debug "Resolved base ref as: main" + elif git rev-parse --verify "origin/master" >/dev/null 2>&1; then + RESOLVED_BASE_REF="origin/master" + debug "Resolved base ref as: origin/master" + elif git rev-parse --verify "master" >/dev/null 2>&1; then + RESOLVED_BASE_REF="master" + debug "Resolved base ref as: master" + fi +fi + +# If still couldn't resolve, assume all changes are relevant (safe default for CI) +if [ -z "$RESOLVED_BASE_REF" ]; then + debug "Warning: Could not resolve base ref '$BASE_REF'" + debug "Assuming all changes are relevant (safe for CI)" + exit 0 +fi + +# Helper function to get all dependencies (direct and transitive) +get_all_deps() { + local pkg=$1 + local visited=$2 + local depth=${3:-0} + + # Limit recursion depth to prevent infinite loops + if [ "$depth" -gt 10 ]; then + return + fi + + # Avoid infinite loops + if echo "$visited" | grep -q "^$pkg$"; then + return + fi + + visited="$visited $pkg" + + # Get direct dependencies (if package.json exists) + if [ ! -f "packages/$pkg/package.json" ]; then + return + fi + + local deps=$(grep -o '"@editorjs/[^"]*"' "packages/$pkg/package.json" 2>/dev/null | sed 's/"//g' | sed 's/@editorjs\///' || true) + + echo "$deps" + + # Recursively get dependencies of dependencies + for dep in $deps; do + get_all_deps "$dep" "$visited" $((depth + 1)) + done +} + +# Check if this package changed +# Normalize the package directory path for comparison (remove leading ./) +NORMALIZED_PKG_DIR=$(echo "$PKG_DIR" | sed 's|^\.\/||') + +# Get the package name for dependency checking +PKG_NAME=$(basename "$PKG_DIR") + +debug "Checking package: $PKG_NAME (dir: $PKG_DIR)" +debug "Base ref: $RESOLVED_BASE_REF" + +GIT_DIFF_OUTPUT=$(git diff --name-only "$RESOLVED_BASE_REF...HEAD" -- "$PKG_DIR" 2>/dev/null) + +if echo "$GIT_DIFF_OUTPUT" | grep -q "^$NORMALIZED_PKG_DIR/"; then + debug "✓ Package $PKG_NAME has changed" + exit 0 +fi + +debug "Package $PKG_NAME has not changed, checking dependencies..." + +# Get ALL dependencies (direct and transitive) +ALL_DEPS=$(get_all_deps "$PKG_NAME" "" 0 | sort -u) + +if [ -n "$ALL_DEPS" ]; then + debug "Dependencies: $(echo $ALL_DEPS | tr '\n' ' ')" +else + debug "No dependencies found" +fi + +# Check if any dependency (direct or indirect) changed +for dep in $ALL_DEPS; do + # Map package name to directory - try exact match and underscore variant + DEP_DIR=$(find packages -maxdepth 1 -type d \( -name "$dep" -o -name "$(echo $dep | sed 's/-/_/g')" \) 2>/dev/null | head -1) + if [ -n "$DEP_DIR" ]; then + # Normalize for git diff output (remove leading ./) + NORMALIZED_DEP_DIR=$(echo "$DEP_DIR" | sed 's|^\.\/||') + if git diff --name-only "$RESOLVED_BASE_REF...HEAD" -- "$DEP_DIR" 2>/dev/null | grep -q "^$NORMALIZED_DEP_DIR/"; then + debug "✓ Dependency $dep (in $DEP_DIR) has changed" + exit 0 + fi + fi +done + +debug "✗ No changes detected in $PKG_NAME or its dependencies" +exit 1 + diff --git a/scripts/should-run-ci.sh b/scripts/should-run-ci.sh deleted file mode 100644 index 242b026f..00000000 --- a/scripts/should-run-ci.sh +++ /dev/null @@ -1,72 +0,0 @@ -#!/bin/bash - -# Detect if a package needs CI to run -# Checks if the package or any of its dependencies (direct or transitive) have changed -# Usage: ./scripts/should-run-ci.sh - -BASE_REF="${1}" -PKG_DIR="${2}" - -if [ -z "$BASE_REF" ] || [ -z "$PKG_DIR" ]; then - echo "Usage: ./scripts/should-run-ci.sh " - exit 1 -fi - -# Handle different base ref formats -# Try to resolve the base ref - could be just "main" or "origin/main" -RESOLVED_BASE_REF="$BASE_REF" - -# If base ref doesn't exist, try with origin prefix -if ! git rev-parse --verify "$RESOLVED_BASE_REF" >/dev/null 2>&1; then - RESOLVED_BASE_REF="origin/$BASE_REF" -fi - -# If still doesn't exist, try to fetch it -if ! git rev-parse --verify "$RESOLVED_BASE_REF" >/dev/null 2>&1; then - git fetch origin "$BASE_REF:$BASE_REF" 2>/dev/null || true - RESOLVED_BASE_REF="$BASE_REF" -fi - -# Helper function to get all dependencies (direct and transitive) -get_all_deps() { - local pkg=$1 - local visited=$2 - - # Avoid infinite loops - if echo "$visited" | grep -q "^$pkg$"; then - return - fi - - visited="$visited $pkg" - - # Get direct dependencies - local deps=$(grep -o '"@editorjs/[^"]*"' "packages/$pkg/package.json" 2>/dev/null | sed 's/"//g' | sed 's/@editorjs\///' || true) - - echo "$deps" - - # Recursively get dependencies of dependencies - for dep in $deps; do - get_all_deps "$dep" "$visited" - done -} - -# Check if this package changed -if git diff --name-only "$RESOLVED_BASE_REF...HEAD" | grep -q "^${PKG_DIR}/"; then - exit 0 -fi - -# Get ALL dependencies (direct and transitive) -PKG_NAME=$(basename "$PKG_DIR") -ALL_DEPS=$(get_all_deps "$PKG_NAME" "" | sort -u) - -# Check if any dependency (direct or indirect) changed -for dep in $ALL_DEPS; do - # Map package name to directory - try exact match and underscore variant - DEP_DIR=$(find packages -maxdepth 1 -type d \( -name "$dep" -o -name "$(echo $dep | sed 's/-/_/g')" \) 2>/dev/null | head -1) - if [ -n "$DEP_DIR" ] && git diff --name-only "$RESOLVED_BASE_REF...HEAD" | grep -q "^$DEP_DIR/"; then - exit 0 - fi -done - -exit 1 - From 9533feec58c38e81ea8b70775acceecb1298aee6 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 18:11:24 +0100 Subject: [PATCH 36/44] Update path to script --- .github/workflows/package-check.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/package-check.yml b/.github/workflows/package-check.yml index 4493c579..0c5be432 100644 --- a/.github/workflows/package-check.yml +++ b/.github/workflows/package-check.yml @@ -45,7 +45,7 @@ jobs: fi # Check if package or dependencies changed (returns 0 if yes, 1 if no) - if bash scripts/should-run-ci.sh "${{ github.base_ref }}" "${{ inputs.working-directory }}"; then + if bash .github/scripts/should-run-ci.sh "${{ github.base_ref }}" "${{ inputs.working-directory }}"; then echo "run=true" >> $GITHUB_OUTPUT else echo "run=false" >> $GITHUB_OUTPUT From 0d24791f123df4725e8b84370aa5050253e0952c Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 18:35:35 +0100 Subject: [PATCH 37/44] Remove some lint warnings --- .github/scripts/should-run-ci.sh | 6 --- .../src/UndoRedoManager.spec.ts | 2 - .../src/client/OTClient.spec.ts | 6 +-- yarn.lock | 40 +++++++++++++++++-- 4 files changed, 39 insertions(+), 15 deletions(-) diff --git a/.github/scripts/should-run-ci.sh b/.github/scripts/should-run-ci.sh index ebe72991..d39a79c9 100644 --- a/.github/scripts/should-run-ci.sh +++ b/.github/scripts/should-run-ci.sh @@ -46,12 +46,6 @@ if [ -z "$RESOLVED_BASE_REF" ]; then elif git rev-parse --verify "main" >/dev/null 2>&1; then RESOLVED_BASE_REF="main" debug "Resolved base ref as: main" - elif git rev-parse --verify "origin/master" >/dev/null 2>&1; then - RESOLVED_BASE_REF="origin/master" - debug "Resolved base ref as: origin/master" - elif git rev-parse --verify "master" >/dev/null 2>&1; then - RESOLVED_BASE_REF="master" - debug "Resolved base ref as: master" fi fi diff --git a/packages/collaboration-manager/src/UndoRedoManager.spec.ts b/packages/collaboration-manager/src/UndoRedoManager.spec.ts index a0ef5f0f..aaa4f341 100644 --- a/packages/collaboration-manager/src/UndoRedoManager.spec.ts +++ b/packages/collaboration-manager/src/UndoRedoManager.spec.ts @@ -222,9 +222,7 @@ describe('UndoRedoManager', () => { .addDocumentId(doc) .addBlockIndex(0) .addDataKey(createDataKey('text')) - /* eslint-disable @typescript-eslint/no-magic-numbers */ .addTextRange([0, 0]) - /* eslint-enable @typescript-eslint/no-magic-numbers */ .build(), { payload: 'xx' }, 'remote' diff --git a/packages/collaboration-manager/src/client/OTClient.spec.ts b/packages/collaboration-manager/src/client/OTClient.spec.ts index 0bce6234..18c9a6c4 100644 --- a/packages/collaboration-manager/src/client/OTClient.spec.ts +++ b/packages/collaboration-manager/src/client/OTClient.spec.ts @@ -24,13 +24,13 @@ describe('OTClient', () => { let OriginalWebSocket: typeof WebSocket; beforeEach(() => { - OriginalWebSocket = globalThis.WebSocket; // eslint-disable-line no-undef - globalThis.WebSocket = MockWebSocket as unknown as typeof WebSocket; // eslint-disable-line no-undef + OriginalWebSocket = globalThis.WebSocket; + globalThis.WebSocket = MockWebSocket as unknown as typeof WebSocket; MockWebSocket.lastInstance = null; }); afterEach(() => { - globalThis.WebSocket = OriginalWebSocket; // eslint-disable-line no-undef + globalThis.WebSocket = OriginalWebSocket; MockWebSocket.lastInstance = null; }); diff --git a/yarn.lock b/yarn.lock index b1d6018d..9c6c0d57 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2789,13 +2789,20 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.6.1": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.11.0, @eslint-community/regexpp@npm:^4.12.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: c08f1dd7dd18fbb60bdd0d85820656d1374dd898af9be7f82cb00451313402a22d5e30569c150315b4385907cdbca78c22389b2a72ab78883b3173be317620cc languageName: node linkType: hard +"@eslint-community/regexpp@npm:^4.6.1": + version: 4.12.2 + resolution: "@eslint-community/regexpp@npm:4.12.2" + checksum: 049b280fddf71dd325514e0a520024969431dc3a8b02fa77476e6820e9122f28ab4c9168c11821f91a27982d2453bcd7a66193356ea84e84fb7c8d793be1ba0c + languageName: node + linkType: hard + "@eslint/compat@npm:^1.1.1": version: 1.2.8 resolution: "@eslint/compat@npm:1.2.8" @@ -4311,7 +4318,14 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0, @ungap/structured-clone@npm:^1.3.0": +"@ungap/structured-clone@npm:^1.2.0": + version: 1.3.1 + resolution: "@ungap/structured-clone@npm:1.3.1" + checksum: 64df206f50aef71c176f9059c1b29e1694821419c6728c446ecf39c80a811eeef156668bf51421b676494a12fd0129ccf09a44f0c641f13c27f50d5f0db6de4e + languageName: node + linkType: hard + +"@ungap/structured-clone@npm:^1.3.0": version: 1.3.0 resolution: "@ungap/structured-clone@npm:1.3.0" checksum: 80d6910946f2b1552a2406650051c91bbd1f24a6bf854354203d84fe2714b3e8ce4618f49cc3410494173a1c1e8e9777372fe68dce74bd45faf0a7a1a6ccf448 @@ -6844,7 +6858,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.4.0, esquery@npm:^1.4.2, esquery@npm:^1.5.0, esquery@npm:^1.6.0": +"esquery@npm:^1.4.0, esquery@npm:^1.5.0, esquery@npm:^1.6.0": version: 1.6.0 resolution: "esquery@npm:1.6.0" dependencies: @@ -6853,6 +6867,15 @@ __metadata: languageName: node linkType: hard +"esquery@npm:^1.4.2": + version: 1.7.0 + resolution: "esquery@npm:1.7.0" + dependencies: + estraverse: "npm:^5.1.0" + checksum: 4afaf3089367e1f5885caa116ef386dffd8bfd64da21fd3d0e56e938d2667cfb2e5400ab4a825aa70e799bb3741e5b5d63c0b94d86e2d4cf3095c9e64b2f5a15 + languageName: node + linkType: hard + "esrecurse@npm:^4.3.0": version: 4.3.0 resolution: "esrecurse@npm:4.3.0" @@ -9134,7 +9157,7 @@ __metadata: languageName: node linkType: hard -"minimatch@npm:^3.0.4, minimatch@npm:^3.0.5, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": +"minimatch@npm:^3.0.4, minimatch@npm:^3.1.1, minimatch@npm:^3.1.2": version: 3.1.2 resolution: "minimatch@npm:3.1.2" dependencies: @@ -9143,6 +9166,15 @@ __metadata: languageName: node linkType: hard +"minimatch@npm:^3.0.5": + version: 3.1.5 + resolution: "minimatch@npm:3.1.5" + dependencies: + brace-expansion: "npm:^1.1.7" + checksum: b11a7ee5773cd34c1a0c8436cdbe910901018fb4b6cb47aa508a18d567f6efd2148507959e35fba798389b161b8604a2d704ccef751ea36bd4582f9852b7d63f + languageName: node + linkType: hard + "minimatch@npm:^5.0.1": version: 5.1.6 resolution: "minimatch@npm:5.1.6" From 962b1b0e0a70b38ee859f20d596306f5cd934cb7 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:25:46 +0100 Subject: [PATCH 38/44] Update format --- .github/actions/aggregate-report/action.yml | 34 ++--- .github/scripts/compare-coverage.js | 68 ++-------- .github/scripts/generate-report.js | 20 --- .github/scripts/process-mutation.js | 45 +++---- .github/scripts/update-aggregated-comment.js | 124 +++++++++++++----- .../collaboration-manager/eslint.config.mjs | 2 + packages/collaboration-manager/package.json | 2 +- packages/core/package.json | 2 +- packages/dom-adapters/package.json | 2 +- packages/model/package.json | 2 +- packages/ot-server/eslint.config.mjs | 2 + packages/ot-server/package.json | 2 +- 12 files changed, 146 insertions(+), 159 deletions(-) delete mode 100644 .github/scripts/generate-report.js diff --git a/.github/actions/aggregate-report/action.yml b/.github/actions/aggregate-report/action.yml index 2985355a..9567d802 100644 --- a/.github/actions/aggregate-report/action.yml +++ b/.github/actions/aggregate-report/action.yml @@ -19,23 +19,25 @@ runs: - name: Find current PR's number uses: jwalton/gh-find-current-pr@v1 - id: findPr + id: find_pr - name: Process coverage reports uses: actions/github-script@v9 id: process_test_report + env: + PACKAGE_NAME: ${{ inputs.package-name }} with: script: | const processReports = require('./.github/scripts/compare-coverage.js').processReports; - core.setOutput('message', processReports('test-report', 'test-report-base')); + core.setOutput('message', processReports(process.env.PACKAGE_NAME, 'test-report', 'test-report-base')); - name: Process mutation report uses: actions/github-script@v9 id: process_mutation_report env: WORKING_DIR: ${{ inputs.working-directory }} - PR_NUMBER: ${{ steps.findPr.outputs.number }} + PR_NUMBER: ${{ steps.find_pr.outputs.number }} PACKAGE_NAME: ${{ inputs.package-name }} with: script: | @@ -43,44 +45,32 @@ runs: core.setOutput('message', processMutationReport('mutation-report', `${process.env.WORKING_DIR}/reports/mutation/mutation.json`, 'changed-files.json', process.env.PR_NUMBER, process.env.PACKAGE_NAME)); - - name: Generate markdown report - id: section-report - uses: actions/github-script@v9 - env: - TESTS_MESSAGE: ${{ steps.process_test_report.outputs.message }} - MUTATION_MESSAGE: ${{ steps.process_mutation_report.outputs.message }} - PACKAGE: ${{ inputs.package-name }} - with: - script: | - const generateReport = require('./.github/scripts/generate-report.js').generateReport; - const { TESTS_MESSAGE, MUTATION_MESSAGE, PACKAGE } = process.env; - - core.setOutput('report', generateReport(PACKAGE, TESTS_MESSAGE, MUTATION_MESSAGE)); - - - name: Acquire comment-update lock id: acquire_lock uses: softprops/turnstyle@v3 with: - queue-name: aggregated-comment-${{ steps.findPr.outputs.number || github.ref_name || github.ref }} + queue-name: aggregated-comment-${{ steps.find_pr.outputs.number || github.ref_name || github.ref }}}} - name: Create or update PR comment with report uses: actions/github-script@v9 env: REPORT: ${{ steps.section-report.outputs.report }} PACKAGE: ${{ inputs.package-name }} - PR_NUMBER: ${{ steps.findPr.outputs.number }} + PR_NUMBER: ${{ steps.find_pr.outputs.number }} + MUTATION_MESSAGE: ${{ steps.process_mutation_report.outputs.message }} + UNIT_TEST_MESSAGE: ${{ steps.process_test_report.outputs.message }} with: script: | const updater = require('./.github/scripts/update-aggregated-comment.js'); (async () => { try { - const report = process.env.REPORT || ''; const packageName = process.env.PACKAGE; + const mutationRow = process.env.MUTATION_MESSAGE; + const unitTestRow = process.env.UNIT_TEST_MESSAGE; const pr = process.env.PR_NUMBER || undefined; // Pass the injected core, github and context objects - await updater.updateAggregatedComment(report, packageName, pr, core, github, context); + await updater.updateAggregatedComment(unitTestRow, mutationRow, packageName, pr, core, github, context); } catch (err) { core.error(err && err.stack ? err.stack : String(err)); throw err; diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index e38923f4..6eb18fdc 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -10,34 +10,13 @@ function findCoverageJson(dir) { try { return JSON.parse(fs.readFileSync(report, 'utf8')); } catch (report) { - // continue + console.error(report); } } return null; } -function findJestJsonResults(dir) { - if (!dir) return null; - - const report = path.join(dir, 'jest-report.json') - - try { - const raw = fs.readFileSync(report, 'utf8'); - const obj = JSON.parse(raw); - - if (typeof obj === 'object' && obj !== null) { - const hasTests = ('numPassedTests' in obj) || ('numPassedTestSuites' in obj) || ('numPassedAssertions' in obj); - const hasSuites = ('numPassedTestSuites' in obj) || ('testResults' in obj && Array.isArray(obj.testResults)); - - if (hasTests || hasSuites) return obj; - } - } catch (e) { - // ignore parse errors - } - - return null; -} function pctFromCoverageSummary(obj, category) { if (!obj) return null; @@ -49,17 +28,6 @@ function pctFromCoverageSummary(obj, category) { return null; } -function extractPassedCounts(jestJson) { - if (!jestJson) return { passedTests: null, passedSuites: null }; - const passedTests = ('numPassedTests' in jestJson) ? Number(jestJson.numPassedTests) : (jestJson.numPassedAssertions ? Number(jestJson.numPassedAssertions) : null); - let passedSuites = null; - if ('numPassedTestSuites' in jestJson) passedSuites = Number(jestJson.numPassedTestSuites); - else if (Array.isArray(jestJson.testResults)) { - passedSuites = jestJson.testResults.filter(r => r.status === 'passed').length; - } - return { passedTests: Number.isFinite(passedTests) ? passedTests : null, passedSuites: Number.isFinite(passedSuites) ? passedSuites : null }; -} - function compute(headSummary, baseSummary) { const categories = ['statements', 'branches', 'functions', 'lines']; @@ -80,38 +48,26 @@ function compute(headSummary, baseSummary) { return out; } -export function processReports(headDir, baseDir) { +export function processReports(pkg, headDir, baseDir) { const headCov = findCoverageJson(headDir); const baseCov = findCoverageJson(baseDir); - const headJest = findJestJsonResults(headDir); const categories = compute(headCov, baseCov); - const headCounts = extractPassedCounts(headJest); - -// prefer head counts; if missing, fallback to base counts; otherwise null - const tests = { - passedTests: headCounts.passedTests != null ? headCounts.passedTests : 'N/A', - passedSuites: headCounts.passedSuites != null ? headCounts.passedSuites : 'N/A' - }; - - let message = `${tests.passedTests} tests passed in ${tests.passedSuites} suites.\n` + let delta = categories.branches.delta; - message += `Branches coverage: ${categories.branches.head}%\n`; - - let warning = ''; - - for (const cat in categories) { - if (categories[cat].delta < 0) { - warning += `\n> Coverage for ${cat} dropped by ${categories[cat].delta.toFixed(2)}%\n`; - } + if (delta < 0) { + delta = `+${delta}% 🟢`; + } else if (delta > 0) { + delta = `-${delta}% 🔴`; + } else if (delta === 0) { + delta = `0% ⚪️`; } - if (warning.length > 0) { - message += `> [!WARNING]${warning}`; - } + // | Package | Branches coverage | Delta | + const tableRow = `| ${pkg} | ${baseCov.branches.pct}% | ${delta}`; - return message; + return tableRow; } diff --git a/.github/scripts/generate-report.js b/.github/scripts/generate-report.js deleted file mode 100644 index 267b94f1..00000000 --- a/.github/scripts/generate-report.js +++ /dev/null @@ -1,20 +0,0 @@ -function generateReport(pkg, testMessage, mutationMessage) { - let md = ''; - - md += `\n`; - md += `## ${pkg}\n\n`; - - md += `### Unit tests report\n ${testMessage}\n\n`; - - if (mutationMessage !== '') { - md += `### Mutation tests report\n${mutationMessage}\n\n`; - } - - - md += `\n`; - - return md; -} - -export { generateReport }; - diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 6905f07b..a5431d2c 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -40,11 +40,11 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu const changedFiles = JSON.parse(fs.readFileSync(changedFilesFile, 'utf8')); if (changedFiles.length === 0) { - return 'No files to mutate found.'; + return `| ${packageName} | No files to mutate found. | |`; } if (!fs.existsSync(reportFile)) { - return 'Report artifact not found.'; + return `| ${packageName} | Report artifact not found. | |`; } const raw = fs.readFileSync(reportFile, 'utf8'); @@ -53,28 +53,29 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu const metrics = getMetrics(obj); - let message = `Mutation tests run with mutation score ${(metrics.score * 100).toFixed(2)}%.\n`; - - if (metrics.survived) { - message += `Survived mutants: ${metrics.survived}\n`; - } - - if (metrics.notCovered) { - message += `Not covered mutants: ${metrics.notCovered}\n`; - } - - if (metrics.score * 100 < metrics.thresholds.low) { - message += `> [!WARNING]\n> Mutation score is below the low threshold of ${metrics.thresholds.low}%\n`; - } else if (metrics.score * 100 < metrics.thresholds.high) { - message += `> [!CAUTION]\n> Mutation score is below the high threshold of ${metrics.thresholds.low}%\n`; - } - - if (prNumber && packageName) { - const encodedPackageName = encodeURIComponent(packageName); - message += `\nDashboard URL: https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${prNumber}?module=${encodedPackageName}`; + const encodedPackageName = encodeURIComponent(packageName); + const dashboardUrl = `[Dashboard](https://dashboard.stryker-mutator.io/reports/github.com/editor-js/document-model/PR-${prNumber}?module=${encodedPackageName})`; + + const score = (metrics.score * 100).toFixed(2); + let scoreMessage = `${score}%`; + + switch (true) { + case (score < metrics.thresholds.break): + scoreMessage += ``; + break; + case (score < metrics.thresholds.low): + scoreMessage += `🔴`; + break; + case (score < metrics.thresholds.high): + scoreMessage += `🟡`; + break; + case (score > metrics.thresholds.high): + scoreMessage += `🟢`; + break; } - return message; + // | Package | Mutation score | Dashboard URL | + return `| ${packageName} | ${scoreMessage} | ${dashboardUrl}`; } export { processMutationReport }; diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index 4c7bc7fe..f602825c 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -1,9 +1,4 @@ -async function updateAggregatedComment(report, packageName, pr, core, github, context) { - if (!report) { - core.info('No report provided, skipping comment update.'); - return; - } - +async function updateAggregatedComment(unitTestRow, mutationRow, packageName, pr, core, github, context) { // repo info (owner/repo) available from context const owner = context.repo.owner; const repo = context.repo.repo; @@ -20,45 +15,106 @@ async function updateAggregatedComment(report, packageName, pr, core, github, co const globalStart = ``; const globalEnd = ``; + // Table section markers + const unitTestsStart = ``; + const unitTestsEnd = ``; + const mutationTestsStart = ``; + const mutationTestsEnd = ``; + + // Table headers and separators + const unitTestsHeader = `| Package | Coverage | Delta |`; + const mutationTestsHeader = `| Package | Mutation score | Dashboard URL |`; + const separator = `| --- | --- | --- |`; + // find existing global comment containing the aggregated markers let existing = comments.find(c => c.body && c.body.includes(globalStart) && c.body.includes(globalEnd)); + let commentBody; + if (!existing) { - // create a new global comment that will hold all package sections - const body = `${globalStart}\n${report}\n${globalEnd}`; - await github.rest.issues.createComment({ owner, repo, issue_number: Number(pr), body }); - core.info('Created new aggregated PR comment with report.'); + // create a new global comment with both tables + const unitTestsTable = `${unitTestsStart}\n${unitTestsHeader}\n${separator}\n${unitTestRow}\n${unitTestsEnd}`; + const mutationTestsTable = `${mutationTestsStart}\n${mutationTestsHeader}\n${separator}\n${mutationRow}\n${mutationTestsEnd}`; + + commentBody = `${globalStart}\n## Unit Tests\n${unitTestsTable}\n\n## Mutation Tests\n${mutationTestsTable}\n${globalEnd}`; + await github.rest.issues.createComment({ owner, repo, issue_number: Number(pr), body: commentBody }); + core.info('Created new aggregated PR comment with test tables.'); return; } - // Update existing aggregated comment: replace package section if present, otherwise insert before global end marker - const body = existing.body || ''; - const pkgStart = ``; - const pkgEnd = ``; - const psi = body.indexOf(pkgStart); - const pei = body.indexOf(pkgEnd); - let newBody; - - if (psi !== -1 && pei !== -1 && pei > psi) { - // replace existing package section - const before = body.substring(0, psi); - const after = body.substring(pei + pkgEnd.length); - - newBody = before + report + after; - } else { - // insert the package section just before the global end marker - const gi = body.indexOf(globalEnd); - - if (gi === -1) { - // malformed existing comment, append at end - newBody = body + '\n\n' + report; + // Update existing comment + commentBody = existing.body || ''; + + // Helper function to update or create a table section + function updateTableSection(body, sectionStart, sectionEnd, header, separator, newRow, pkgName) { + const sectionStartIdx = body.indexOf(sectionStart); + + if (sectionStartIdx === -1) { + // Table section doesn't exist, create it with header, separator, and new row + const tableContent = `${sectionStart}\n${header}\n${separator}\n${newRow}\n${sectionEnd}`; + + // Insert before the global end marker + const globalEndIdx = body.indexOf(globalEnd); + if (globalEndIdx === -1) { + return body + '\n\n' + tableContent; + } + return body.substring(0, globalEndIdx) + '\n' + tableContent + '\n' + body.substring(globalEndIdx); + } + + // Table section exists, find and update or append row + const sectionEndIdx = body.indexOf(sectionEnd, sectionStartIdx); + const beforeSection = body.substring(0, sectionStartIdx + sectionStart.length); + const afterSection = body.substring(sectionEndIdx); + const sectionContent = body.substring(sectionStartIdx + sectionStart.length, sectionEndIdx); + + // Split into lines and find the data row for this package + const lines = sectionContent.split('\n'); + let packageRowLineIdx = -1; + + for (let i = 0; i < lines.length; i++) { + const line = lines[i]; + // Data rows: contain pipes, contain package name, and don't contain dashes (separator indicator) + if (line.includes('|') && line.includes(pkgName) && !line.includes('---')) { + packageRowLineIdx = i; + break; + } + } + + if (packageRowLineIdx !== -1) { + // Replace existing package row + lines[packageRowLineIdx] = newRow; } else { - newBody = body.substring(0, gi) + report + '\n' + body.substring(gi); + // Append new package row + lines.push(newRow); } + + return beforeSection + lines.join('\n') + afterSection; } - await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: newBody }); - core.info('Updated aggregated PR comment with report.'); + // Update unit tests table + commentBody = updateTableSection( + commentBody, + unitTestsStart, + unitTestsEnd, + unitTestsHeader, + separator, + unitTestRow, + packageName + ); + + // Update mutation tests table + commentBody = updateTableSection( + commentBody, + mutationTestsStart, + mutationTestsEnd, + mutationTestsHeader, + separator, + mutationRow, + packageName + ); + + await github.rest.issues.updateComment({ owner, repo, comment_id: existing.id, body: commentBody }); + core.info('Updated aggregated PR comment with test tables.'); } export { updateAggregatedComment }; diff --git a/packages/collaboration-manager/eslint.config.mjs b/packages/collaboration-manager/eslint.config.mjs index 13f36895..27268759 100644 --- a/packages/collaboration-manager/eslint.config.mjs +++ b/packages/collaboration-manager/eslint.config.mjs @@ -18,6 +18,8 @@ export default [ 'n/no-unpublished-import': ['error', { allowModules: [ 'eslint-config-codex', + '@editorjs/model', + '@editorjs/sdk', ], ignoreTypeImport: true, }], diff --git a/packages/collaboration-manager/package.json b/packages/collaboration-manager/package.json index 0dc27648..f60cde50 100644 --- a/packages/collaboration-manager/package.json +++ b/packages/collaboration-manager/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", + "test:coverage": "yarn test --coverage=true", "test:mutations": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/core/package.json b/packages/core/package.json index ae1ee8a8..c165f2bd 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -13,7 +13,7 @@ "lint:ci": "eslint --config ./eslint.config.mjs ./src --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", + "test:coverage": "yarn test --coverage=true", "test:mutations": "NODE_OPTIONS=\"$NODE_OPTIONS --experimental-vm-modules\" stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/dom-adapters/package.json b/packages/dom-adapters/package.json index 050e5109..e5138dfa 100644 --- a/packages/dom-adapters/package.json +++ b/packages/dom-adapters/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", + "test:coverage": "yarn test --coverage=true", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/model/package.json b/packages/model/package.json index 5f4a0231..4b68365e 100644 --- a/packages/model/package.json +++ b/packages/model/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "jest", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", + "test:coverage": "yarn test --coverage=true", "test:mutations": "stryker run", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, diff --git a/packages/ot-server/eslint.config.mjs b/packages/ot-server/eslint.config.mjs index 66fdff6e..d1fb99db 100644 --- a/packages/ot-server/eslint.config.mjs +++ b/packages/ot-server/eslint.config.mjs @@ -18,6 +18,8 @@ export default [ 'n/no-unpublished-import': ['error', { allowModules: [ 'eslint-config-codex', + '@editorjs/model', + '@editorjs/collaboration-manager', ], ignoreTypeImport: true, }], diff --git a/packages/ot-server/package.json b/packages/ot-server/package.json index 5567f615..ec00d36f 100644 --- a/packages/ot-server/package.json +++ b/packages/ot-server/package.json @@ -13,7 +13,7 @@ "lint:ci": "yarn lint --max-warnings 0", "lint:fix": "yarn lint --fix", "test": "node --experimental-vm-modules $(yarn bin jest)", - "test:coverage": "yarn test --coverage=true --json --outputFile=jest-report.json", + "test:coverage": "yarn test --coverage=true", "clear": "rm -rf ./dist && rm -rf ./tsconfig.build.tsbuildinfo" }, "dependencies": { From a08ce4a7e6c9f560469703a2f0087ee36d82805c Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:30:37 +0100 Subject: [PATCH 39/44] Fix typo --- .github/scripts/compare-coverage.js | 4 +--- packages/collaboration-manager/eslint.config.mjs | 6 +++++- packages/ot-server/eslint.config.mjs | 6 +++++- 3 files changed, 11 insertions(+), 5 deletions(-) diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index 6eb18fdc..ea32e68f 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -65,9 +65,7 @@ export function processReports(pkg, headDir, baseDir) { } // | Package | Branches coverage | Delta | - const tableRow = `| ${pkg} | ${baseCov.branches.pct}% | ${delta}`; - - return tableRow; + return `| ${pkg} | ${headCov.branches.pct}% | ${delta}`; } diff --git a/packages/collaboration-manager/eslint.config.mjs b/packages/collaboration-manager/eslint.config.mjs index 27268759..0ea434ea 100644 --- a/packages/collaboration-manager/eslint.config.mjs +++ b/packages/collaboration-manager/eslint.config.mjs @@ -18,10 +18,14 @@ export default [ 'n/no-unpublished-import': ['error', { allowModules: [ 'eslint-config-codex', + ], + ignoreTypeImport: true, + }], + 'n/no-missing-import': ['error', { + allowModules: [ '@editorjs/model', '@editorjs/sdk', ], - ignoreTypeImport: true, }], // @todo: remove when we setup eslint to correctly handle the types '@typescript-eslint/no-unsafe-call': 'off', diff --git a/packages/ot-server/eslint.config.mjs b/packages/ot-server/eslint.config.mjs index d1fb99db..965e3471 100644 --- a/packages/ot-server/eslint.config.mjs +++ b/packages/ot-server/eslint.config.mjs @@ -18,10 +18,14 @@ export default [ 'n/no-unpublished-import': ['error', { allowModules: [ 'eslint-config-codex', + ], + ignoreTypeImport: true, + }], + 'n/no-missing-import': ['error', { + allowModules: [ '@editorjs/model', '@editorjs/collaboration-manager', ], - ignoreTypeImport: true, }], 'n/no-unsupported-features/node-builtins': ['error', { version: '>=24.0.0', From d0bb75a1a5b0f09b86c3c8969cafcffdbdda9eb4 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:36:31 +0100 Subject: [PATCH 40/44] Get info from computed data --- .github/scripts/compare-coverage.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index ea32e68f..5177a99c 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -65,7 +65,7 @@ export function processReports(pkg, headDir, baseDir) { } // | Package | Branches coverage | Delta | - return `| ${pkg} | ${headCov.branches.pct}% | ${delta}`; + return `| ${pkg} | ${categories.branches.head}% | ${delta}`; } From 6461226457d3dd27ff749bc55c948de54ac1e9b8 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:42:49 +0100 Subject: [PATCH 41/44] Add pipes at the end of the rows --- .github/scripts/compare-coverage.js | 2 +- .github/scripts/process-mutation.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/scripts/compare-coverage.js b/.github/scripts/compare-coverage.js index 5177a99c..31e861a6 100644 --- a/.github/scripts/compare-coverage.js +++ b/.github/scripts/compare-coverage.js @@ -65,7 +65,7 @@ export function processReports(pkg, headDir, baseDir) { } // | Package | Branches coverage | Delta | - return `| ${pkg} | ${categories.branches.head}% | ${delta}`; + return `| ${pkg} | ${categories.branches.head}% | ${delta} |`; } diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index a5431d2c..66cc2374 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -75,7 +75,7 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu } // | Package | Mutation score | Dashboard URL | - return `| ${packageName} | ${scoreMessage} | ${dashboardUrl}`; + return `| ${packageName} | ${scoreMessage} | ${dashboardUrl} |`; } export { processMutationReport }; From 1515928e8c31abc1641eb24e4362266cdb8c9cee Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:48:35 +0100 Subject: [PATCH 42/44] Remove empty lines between rows --- .github/actions/base-coverage/action.yml | 1 + .github/scripts/update-aggregated-comment.js | 6 +++--- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/.github/actions/base-coverage/action.yml b/.github/actions/base-coverage/action.yml index 066e82c0..9f6c0336 100644 --- a/.github/actions/base-coverage/action.yml +++ b/.github/actions/base-coverage/action.yml @@ -42,3 +42,4 @@ runs: path: | ${{ inputs.working-directory }}/coverage ${{ inputs.working-directory }}/jest-report.json + retention-days: 3 diff --git a/.github/scripts/update-aggregated-comment.js b/.github/scripts/update-aggregated-comment.js index f602825c..bb6199d8 100644 --- a/.github/scripts/update-aggregated-comment.js +++ b/.github/scripts/update-aggregated-comment.js @@ -67,8 +67,8 @@ async function updateAggregatedComment(unitTestRow, mutationRow, packageName, pr const afterSection = body.substring(sectionEndIdx); const sectionContent = body.substring(sectionStartIdx + sectionStart.length, sectionEndIdx); - // Split into lines and find the data row for this package - const lines = sectionContent.split('\n'); + // Split into lines, trim, and filter out empty lines + let lines = sectionContent.split('\n').map(l => l.trim()).filter(l => l.length > 0); let packageRowLineIdx = -1; for (let i = 0; i < lines.length; i++) { @@ -88,7 +88,7 @@ async function updateAggregatedComment(unitTestRow, mutationRow, packageName, pr lines.push(newRow); } - return beforeSection + lines.join('\n') + afterSection; + return beforeSection + '\n' + lines.join('\n') + '\n' + afterSection; } // Update unit tests table From c13ff8528f32c5df645702525e02aa687472c818 Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:52:27 +0100 Subject: [PATCH 43/44] test updates and mutation report --- packages/model/src/entities/BlockNode/index.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index 82648d86..e38cf269 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,6 +89,8 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); + console.log('test'); + this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null; From 605d146343d735e8fd6d29bb86a1889a3f7e880d Mon Sep 17 00:00:00 2001 From: gohabereg Date: Mon, 1 Jun 2026 19:56:43 +0100 Subject: [PATCH 44/44] Revert test --- .github/scripts/process-mutation.js | 8 ++++---- packages/model/src/entities/BlockNode/index.ts | 2 -- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/scripts/process-mutation.js b/.github/scripts/process-mutation.js index 66cc2374..4c043125 100644 --- a/.github/scripts/process-mutation.js +++ b/.github/scripts/process-mutation.js @@ -61,16 +61,16 @@ function processMutationReport(artitfactName, reportPath, changedFilesPath, prNu switch (true) { case (score < metrics.thresholds.break): - scoreMessage += ``; + scoreMessage += ` `; break; case (score < metrics.thresholds.low): - scoreMessage += `🔴`; + scoreMessage += ` 🔴`; break; case (score < metrics.thresholds.high): - scoreMessage += `🟡`; + scoreMessage += ` 🟡`; break; case (score > metrics.thresholds.high): - scoreMessage += `🟢`; + scoreMessage += ` 🟢`; break; } diff --git a/packages/model/src/entities/BlockNode/index.ts b/packages/model/src/entities/BlockNode/index.ts index e38cf269..82648d86 100644 --- a/packages/model/src/entities/BlockNode/index.ts +++ b/packages/model/src/entities/BlockNode/index.ts @@ -89,8 +89,6 @@ export class BlockNode extends EventBus { }: BlockNodeConstructorParameters) { super(); - console.log('test'); - this.#id = id !== undefined ? createBlockId(id) : generateBlockId(); this.#name = createBlockToolName(name); this.#parent = parent ?? null;