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 =
+ ''
+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
+
+
+
+
+ Name |
+ Link |
+
+
+
+
+ Latest commit |
+ {{ SHA }} |
+
+
+ Latest job logs |
+ {{ JOB_LOGS }} |
+
+ {{ TABLE_BODY }}
+
+
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 }}