Add SafePay ZK mini app #7
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Mini App PR Summary | |
| on: | |
| pull_request_target: | |
| paths: | |
| - 'src/data/nimiq-mini-apps.json' | |
| permissions: | |
| contents: read | |
| issues: write | |
| pull-requests: write | |
| jobs: | |
| comment: | |
| runs-on: ubuntu-latest | |
| steps: | |
| - name: Comment mini app changes | |
| uses: actions/github-script@v7 | |
| with: | |
| script: | | |
| const marker = '<!-- mini-app-summary -->' | |
| const miniAppsPath = 'src/data/nimiq-mini-apps.json' | |
| const pr = context.payload.pull_request | |
| if (!pr) { | |
| core.info('No pull request found in context.') | |
| return | |
| } | |
| async function readJsonFromRepo({ owner, repo, ref }) { | |
| const response = await github.rest.repos.getContent({ | |
| owner, | |
| repo, | |
| path: miniAppsPath, | |
| ref, | |
| }) | |
| if (Array.isArray(response.data) || response.data.type !== 'file') { | |
| throw new Error(`${miniAppsPath} is not a file at ${owner}/${repo}@${ref}`) | |
| } | |
| const content = Buffer.from(response.data.content, response.data.encoding).toString('utf8') | |
| return JSON.parse(content) | |
| } | |
| function escapeHtml(value) { | |
| return String(value) | |
| .replaceAll('&', '&') | |
| .replaceAll('<', '<') | |
| .replaceAll('>', '>') | |
| .replaceAll('"', '"') | |
| } | |
| function escapeTable(value) { | |
| return escapeHtml(value ?? '') | |
| .replaceAll('|', '|') | |
| .replaceAll('\r', ' ') | |
| .replaceAll('\n', '<br>') | |
| } | |
| function formatLink(value) { | |
| if (!value) | |
| return '' | |
| const escapedUrl = escapeHtml(value) | |
| const escapedText = escapeTable(value) | |
| return `<a href="${escapedUrl}">${escapedText}</a>` | |
| } | |
| function normalizeLogoPath(logo) { | |
| return logo.replace(/^\.\//, '').replace(/^\/+/, '') | |
| } | |
| function formatLogo(app, source) { | |
| if (!app.logo) | |
| return 'No logo' | |
| const logo = String(app.logo) | |
| let src = logo | |
| if (!/^https?:\/\//.test(logo)) { | |
| const normalizedLogo = normalizeLogoPath(logo) | |
| src = `https://raw.githubusercontent.com/${source.owner}/${source.repo}/${source.ref}/src/data/${normalizedLogo}` | |
| } | |
| return `<img src="${escapeHtml(src)}" width="48" alt="Logo for ${escapeHtml(app.name)}">` | |
| } | |
| function comparableApp(app) { | |
| return { | |
| name: app.name ?? '', | |
| url: app.url ?? '', | |
| type: app.type ?? '', | |
| description: app.description ?? '', | |
| logo: app.logo ?? '', | |
| source: app.source ?? null, | |
| developer: app.developer ?? null, | |
| featured: app.featured ?? false, | |
| } | |
| } | |
| function hasAppChanged(baseApp, headApp) { | |
| return JSON.stringify(comparableApp(baseApp)) !== JSON.stringify(comparableApp(headApp)) | |
| } | |
| function formatRow(change) { | |
| const app = change.app | |
| return [ | |
| change.status, | |
| escapeTable(app.name), | |
| formatLink(app.url), | |
| escapeTable(app.type), | |
| escapeTable(app.description), | |
| formatLogo(app, change.source), | |
| app.source ? formatLink(app.source) : '', | |
| app.developer ? escapeTable(app.developer) : '', | |
| app.featured ? 'true' : 'false', | |
| ].join(' | ') | |
| } | |
| const baseSource = { | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: pr.base.sha, | |
| } | |
| const headSource = { | |
| owner: pr.head.repo.owner.login, | |
| repo: pr.head.repo.name, | |
| ref: pr.head.sha, | |
| } | |
| const baseApps = await readJsonFromRepo({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| ref: pr.base.sha, | |
| }) | |
| const headApps = await readJsonFromRepo({ | |
| owner: pr.head.repo.owner.login, | |
| repo: pr.head.repo.name, | |
| ref: pr.head.sha, | |
| }) | |
| const baseAppsByName = new Map(baseApps.map(app => [app.name, app])) | |
| const headAppsByName = new Map(headApps.map(app => [app.name, app])) | |
| const addedOrEditedChanges = headApps | |
| .map((app) => { | |
| const baseApp = baseAppsByName.get(app.name) | |
| if (!baseApp) | |
| return { status: 'Added', app, source: headSource } | |
| if (hasAppChanged(baseApp, app)) | |
| return { status: 'Edited', app, source: headSource } | |
| return null | |
| }) | |
| .filter(Boolean) | |
| const removedChanges = baseApps | |
| .filter(app => !headAppsByName.has(app.name)) | |
| .map(app => ({ status: 'Removed', app, source: baseSource })) | |
| const changes = [ | |
| ...addedOrEditedChanges, | |
| ...removedChanges, | |
| ] | |
| const table = changes.length > 0 | |
| ? [ | |
| '| Status | Name | URL | Type | Description | Logo | Source | Developer | Featured |', | |
| '| --- | --- | --- | --- | --- | --- | --- | --- | --- |', | |
| ...changes.map(formatRow).map(row => `| ${row} |`), | |
| ].join('\n') | |
| : 'No added, edited, or removed mini app entries found.' | |
| const body = [ | |
| marker, | |
| '## Mini App Submission Summary', | |
| '', | |
| table, | |
| ].join('\n') | |
| const comments = await github.rest.issues.listComments({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| per_page: 100, | |
| }) | |
| const existingComment = comments.data.find(comment => comment.body?.includes(marker)) | |
| if (existingComment) { | |
| await github.rest.issues.updateComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| comment_id: existingComment.id, | |
| body, | |
| }) | |
| } | |
| else { | |
| await github.rest.issues.createComment({ | |
| owner: context.repo.owner, | |
| repo: context.repo.repo, | |
| issue_number: pr.number, | |
| body, | |
| }) | |
| } |