diff --git a/.github/actions/constants.cjs b/.github/actions/constants.cjs new file mode 100644 index 00000000..aeb9b973 --- /dev/null +++ b/.github/actions/constants.cjs @@ -0,0 +1,22 @@ +const BADGE = + 'Badge' +const BROWSERS = ['chrome', 'firefox', 'opera', 'edge'] +const COLORS = { + green: '3fb950', + red: 'd73a49', +} +const TEMPLATE_VARS = { + tableBody: '{{ TABLE_BODY }}', + sha: '{{ SHA }}', + conslusion: '{{ CONCLUSION }}', + badgeColor: '{{ BADGE_COLOR }}', + badgeLabel: '{{ BADGE_LABEL }}', + jobLogs: '{{ JOB_LOGS }}', +} + +module.exports = { + BADGE, + BROWSERS, + COLORS, + TEMPLATE_VARS, +} diff --git a/.github/actions/delete-artifacts.cjs b/.github/actions/delete-artifacts.cjs new file mode 100644 index 00000000..216bc033 --- /dev/null +++ b/.github/actions/delete-artifacts.cjs @@ -0,0 +1,67 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-console */ +const { BROWSERS } = require('./constants.cjs') + +async function getBrowserArfifacts({ github, owner, repo, name }) { + const artifacts = [] + const result = await github.rest.actions.listArtifactsForRepo({ + owner, + repo, + name, + }) + + for (let i = 0; i < result.data.total_count; i++) { + artifacts.push(result.data.artifacts[i].id) + } + + return artifacts +} + +async function getPRArtifacts({ github, owner, repo, prNumber }) { + const promises = [] + const artifacts = [] + + BROWSERS.forEach(browser => + promises.push( + getBrowserArfifacts({ + github, + owner, + repo, + name: `${prNumber}-${browser}`, + }), + ), + ) + + const data = await Promise.all(promises) + + for (let i = 0; i < data.length; i++) { + artifacts.push.apply(artifacts, data[i]) + } + + return artifacts +} + +module.exports = async ({ github, context, core }) => { + if (context.payload.action !== 'closed') { + core.setFailed('This action only works on closed PRs.') + } + + const { owner, repo } = context.repo + const prNumber = context.payload.number + const promises = [] + + const artifacts = await getPRArtifacts({ github, owner, repo, prNumber }) + + for (let i = 0; i < artifacts.length; i++) { + promises.push( + github.rest.actions.deleteArtifact({ + owner, + repo, + artifact_id: artifacts[i], + }), + ) + } + + await Promise.all(promises) + console.log(`Deleted ${artifacts.length} artifacts for PR #${prNumber}.`) +} diff --git a/.github/actions/get-workflow-artifacts.cjs b/.github/actions/get-workflow-artifacts.cjs new file mode 100644 index 00000000..e6ada883 --- /dev/null +++ b/.github/actions/get-workflow-artifacts.cjs @@ -0,0 +1,96 @@ +/* eslint-disable @typescript-eslint/no-var-requires */ +/* eslint-disable no-console */ +const fs = require('node:fs/promises') +const { COLORS, TEMPLATE_VARS, BADGE } = require('./constants.cjs') + +const ARTIFACTS_DATA = { + chrome: { + name: 'Chrome', + url: null, + size: null, + }, + firefox: { + name: 'Firefox', + url: null, + size: null, + }, + opera: { + name: 'Opera', + url: null, + size: null, + }, + edge: { + name: 'Edge', + url: null, + size: null, + }, +} + +function getBadge(conclusion, badgeColor, badgeLabel) { + return BADGE.replace(TEMPLATE_VARS.conslusion, conclusion) + .replace(TEMPLATE_VARS.badgeColor, badgeColor) + .replace(TEMPLATE_VARS.badgeLabel, badgeLabel) +} + +function formatBytes(bytes, decimals = 2) { + if (!Number(bytes)) return '0B' + const k = 1024 + const dm = decimals < 0 ? 0 : decimals + const sizes = ['B', 'KB', 'MB', 'GB'] + const i = Math.floor(Math.log(bytes) / Math.log(k)) + return `${parseFloat((bytes / Math.pow(k, i)).toFixed(dm))}${sizes[i]}` +} + +module.exports = async ({ github, context, core }) => { + const { owner, repo } = context.repo + const baseUrl = context.payload.repository.html_url + const suiteId = context.payload.workflow_run.check_suite_id + const runId = context.payload.workflow_run.id + const conclusion = context.payload.workflow_run.conclusion + const sha = context.payload.workflow_run.pull_requests[0].head.sha + const prNumber = context.payload.workflow_run.pull_requests[0].number + const jobLogsUrl = `${baseUrl}/actions/runs/${context.payload.workflow_run.id}` + const template = await fs.readFile('./.github/actions/templates/build-status.md', 'utf8') + const tableRows = [] + + core.setOutput('conclusion', conclusion) + + if (conclusion === 'cancelled') { + return + } + + const artifacts = await github.rest.actions.listWorkflowRunArtifacts({ + owner, + repo, + run_id: runId, + }) + + artifacts.data.artifacts.forEach(artifact => { + const [, key] = artifact.name.split('-') + ARTIFACTS_DATA[key].url = `${baseUrl}/suites/${suiteId}/artifacts/${artifact.id}` + ARTIFACTS_DATA[key].size = formatBytes(artifact.size_in_bytes) + }) + + Object.keys(ARTIFACTS_DATA).forEach(k => { + const { name, url, size } = ARTIFACTS_DATA[k] + if (url === null && size === null) { + const badgeUrl = getBadge('failure', COLORS.red, name) + tableRows.push(`${badgeUrl}N/A`) + } else { + const badgeUrl = getBadge('success', COLORS.green, `${name} (${size})`) + tableRows.push( + `${badgeUrl}Download`, + ) + } + }) + + const tableBody = tableRows.join('') + const commentBody = template + .replace(TEMPLATE_VARS.conslusion, conclusion) + .replace(TEMPLATE_VARS.sha, sha) + .replace(TEMPLATE_VARS.jobLogs, `Run #${runId}`) + .replace(TEMPLATE_VARS.tableBody, tableBody) + + core.setOutput('comment_body', commentBody) + core.setOutput('pr_number', prNumber) +} diff --git a/.github/actions/templates/build-status.md b/.github/actions/templates/build-status.md new file mode 100644 index 00000000..cc3aa774 --- /dev/null +++ b/.github/actions/templates/build-status.md @@ -0,0 +1,22 @@ + +

Extension builds preview

+ + + + + + + + + + + + + + + + + + {{ TABLE_BODY }} + +
NameLink
Latest commit{{ SHA }}
Latest job logs{{ JOB_LOGS }}
diff --git a/.github/workflows/build-previews.yml b/.github/workflows/build-previews.yml new file mode 100644 index 00000000..bb48de36 --- /dev/null +++ b/.github/workflows/build-previews.yml @@ -0,0 +1,44 @@ +name: Build previews + +on: + workflow_run: + types: + - 'completed' + workflows: + - 'PR Checks' + +jobs: + comment: + name: Add comment with extension preview builds + runs-on: ubuntu-22.04 + if: github.repository == 'interledger/web-monetization-extension' + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Get workflow artifacts + uses: actions/github-script@v7 + id: get-workflow-artifacts + with: + script: | + const script = require('./.github/actions/get-workflow-artifacts.cjs') + await script({ github, context, core }) + + - name: Find comment + if: ${{ steps.get-workflow-artifacts.outputs.conclusion != 'cancelled' }} + uses: peter-evans/find-comment@v2 + id: find-comment + with: + issue-number: ${{ steps.get-workflow-artifacts.outputs.pr_number }} + comment-author: 'raducristianpopa' + body-includes: '' + + - name: Add/Update comment + if: ${{ steps.get-workflow-artifacts.outputs.conclusion != 'cancelled' }} + uses: peter-evans/create-or-update-comment@v2 + with: + token: ${{ secrets.PAT }} + comment-id: ${{ steps.find-comment.outputs.comment-id }} + issue-number: ${{ steps.get-workflow-artifacts.outputs.pr_number }} + body: ${{ steps.get-workflow-artifacts.outputs.comment_body }} + edit-mode: replace diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml deleted file mode 100644 index dbc4b9de..00000000 --- a/.github/workflows/ci.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: CI - -on: - push: - branches: - - main - - pull_request: - branches: - - main - -jobs: - build: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v3 - - uses: actions/setup-node@v3 - with: - node-version: 16.x - cache: 'yarn' - - - name: Install dependencies - run: rm -rf node_modules && yarn install --frozen-lockfile - - - name: Build - run: yarn build:chrome diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml index a8c3c19a..ec2b07b0 100644 --- a/.github/workflows/codeql-analysis.yml +++ b/.github/workflows/codeql-analysis.yml @@ -23,7 +23,7 @@ on: jobs: analyze: name: Analyze - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 permissions: actions: read contents: read diff --git a/.github/workflows/delete-pr-artifacts.yml b/.github/workflows/delete-pr-artifacts.yml new file mode 100644 index 00000000..3608e4b3 --- /dev/null +++ b/.github/workflows/delete-pr-artifacts.yml @@ -0,0 +1,22 @@ +name: Delete PR artifacts + +on: + pull_request: + types: + - closed + +jobs: + delete: + name: Delete artifacts + if: github.repository == 'interledger/web-monetization-extension' + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Delete arfiacts + uses: actions/github-script@v7 + with: + script: | + const script = require('./.github/actions/delete-artifacts.cjs') + await script({ github, context, core }) diff --git a/.github/workflows/pr-checks.yml b/.github/workflows/pr-checks.yml new file mode 100644 index 00000000..eda6a7eb --- /dev/null +++ b/.github/workflows/pr-checks.yml @@ -0,0 +1,45 @@ +name: PR Checks + +on: + pull_request: + types: + - opened + - reopened + - synchronize + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +# TODO(@raducristianpopa): add lint/format checks and tests + +jobs: + build: + name: Build + strategy: + fail-fast: false + matrix: + browser: [chrome, firefox, opera, edge] + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18 + cache: 'yarn' + + - name: Install dependencies + run: rm -rf node_modules && yarn install --frozen-lockfile + + - name: Build + run: yarn build:${{ matrix.browser }} + + - name: Upload artifacts + uses: actions/upload-artifact@v3.1.2 + with: + name: ${{ github.event.pull_request.number }}-${{ matrix.browser }} + path: dist/${{ matrix.browser }}/${{ matrix.browser }}.zip + if-no-files-found: error diff --git a/.github/workflows/pr-title-check.yml b/.github/workflows/pr-title-check.yml index 94ec0501..6624f974 100644 --- a/.github/workflows/pr-title-check.yml +++ b/.github/workflows/pr-title-check.yml @@ -2,13 +2,13 @@ name: Check PR title on: pull_request: - branches: ["**"] + branches: ['**'] jobs: check-pr-title: name: Check PR Title runs-on: ubuntu-22.04 - steps: + steps: - uses: amannn/action-semantic-pull-request@v5 - env: + env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/sanity.yml b/.github/workflows/sanity.yml new file mode 100644 index 00000000..d6311a48 --- /dev/null +++ b/.github/workflows/sanity.yml @@ -0,0 +1,32 @@ +name: Sanity + +on: + push: + branches: + - main + +# TODO(@raducristianpopa): add lint/format checks and tests + +jobs: + build: + name: Build extension + strategy: + fail-fast: false + matrix: + browser: [chrome, firefox, opera, edge] + runs-on: ubuntu-22.04 + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + - name: Setup Node + uses: actions/setup-node@v3 + with: + node-version: 18.x + cache: 'yarn' + + - name: Install dependencies + run: rm -rf node_modules && yarn install --frozen-lockfile + + - name: Build + run: yarn build:${{ matrix.browser }}