diff --git a/.github/scripts/post-coverage.cjs b/.github/scripts/post-coverage.cjs new file mode 100644 index 0000000..e41f2fd --- /dev/null +++ b/.github/scripts/post-coverage.cjs @@ -0,0 +1,35 @@ +/** + * Posts JMAP spec coverage report as a PR comment. + * + * @param {object} params + * @param {import('@actions/github/lib/utils').GitHub} params.github + * @param {import('@actions/github').context} params.context + */ +module.exports = async ({ github, context }) => { + const report = process.env.COVERAGE_REPORT; + + if (!report) { + console.log('No coverage report found in COVERAGE_REPORT env var'); + return; + } + + const body = `## JMAP Spec Coverage + +${report} +`; + + const prNumber = context.payload.pull_request?.number; + if (!prNumber) { + console.log('Not a pull request context, skipping comment'); + return; + } + + await github.rest.issues.createComment({ + owner: context.repo.owner, + repo: context.repo.repo, + issue_number: prNumber, + body + }); + + console.log('Coverage comment posted successfully'); +}; diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 60e4a3b..8bd7fa8 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -129,3 +129,47 @@ jobs: - name: Stop test server if: always() run: pnpm test:server:stop + + spec-coverage: + name: Spec Coverage + runs-on: ubuntu-latest + if: github.event_name == 'pull_request' + permissions: + pull-requests: write + steps: + - uses: actions/checkout@v4 + with: + submodules: true + + - uses: pnpm/action-setup@v4 + with: + version: 10.17.0 + + - uses: actions/setup-node@v4 + with: + node-version: "24" + cache: "pnpm" + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run spec tests + run: pnpm test:spec + + - name: Generate coverage report + id: coverage + run: | + { + echo 'report<> "$GITHUB_OUTPUT" + + - name: Post coverage comment + uses: actions/github-script@v7 + env: + COVERAGE_REPORT: ${{ steps.coverage.outputs.report }} + with: + script: | + const script = require('./.github/scripts/post-coverage.cjs') + await script({ github, context }) diff --git a/scripts/spec-coverage.ts b/scripts/spec-coverage.ts index b728fe0..c2620a3 100644 --- a/scripts/spec-coverage.ts +++ b/scripts/spec-coverage.ts @@ -21,6 +21,7 @@ import { interface CliArgs { json: boolean byType: boolean + markdown: boolean help: boolean } @@ -29,6 +30,7 @@ function parseArgs(): CliArgs { return { json: args.includes('--json'), byType: args.includes('--by-type'), + markdown: args.includes('--markdown'), help: args.includes('--help') || args.includes('-h') } } @@ -45,12 +47,14 @@ Usage: Options: --json Output as JSON --by-type Group coverage by object type + --markdown Output as GitHub-flavored markdown table --help, -h Show this help message Examples: pnpm coverage:spec # Show coverage table pnpm coverage:spec --by-type # Show coverage grouped by type pnpm coverage:spec --json # Output as JSON for CI/scripts + pnpm coverage:spec --markdown # Output markdown for PR comments `) } @@ -145,6 +149,31 @@ function printJson() { console.log(JSON.stringify(result, null, 2)) } +function printMarkdown() { + const byType = getCompletenessByType() + const completeness = getCompleteness() + + // Header with overall progress + console.log(`**Overall: ${completeness.implemented}/${completeness.total} methods (${completeness.percentage.toFixed(1)}%)**`) + console.log('') + + // Table for each object type + for (const [type, data] of Object.entries(byType).sort((a, b) => a[0].localeCompare(b[0]))) { + console.log(`### ${type} (${data.implemented}/${data.total})`) + console.log('') + console.log('| Method | Status |') + console.log('|--------|--------|') + + for (const method of data.methods) { + const name = method.name.split('/')[1] + const status = method.implemented ? '✅' : '⬚' + console.log(`| ${name} | ${status} |`) + } + + console.log('') + } +} + function main() { const args = parseArgs() @@ -158,6 +187,11 @@ function main() { return } + if (args.markdown) { + printMarkdown() + return + } + if (args.byType) { printByType() return