Skip to content

Commit e7da621

Browse files
committed
feat: add PR CI and modernize deployment workflows
- add PR CI workflow (lint, stylelint, tests, build) on pull requests - modernize staging auto-PR flow to native gh CLI and include generated list of included PRs - update staging/production deploy workflows to current action versions
1 parent 3d8ac58 commit e7da621

6 files changed

Lines changed: 158 additions & 35 deletions

File tree

.eslintignore

Lines changed: 0 additions & 5 deletions
This file was deleted.

.github/workflows/build-production.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,21 @@ jobs:
2222

2323
steps:
2424
- name: Checkout
25-
uses: actions/checkout@v4
25+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2626

2727
- name: Set up Docker Buildx
28-
uses: docker/setup-buildx-action@v3
28+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
2929

3030
- name: Log in to GHCR
31-
uses: docker/login-action@v3
31+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
3232
with:
3333
registry: ghcr.io
3434
username: ${{ github.actor }}
3535
password: ${{ secrets.GITHUB_TOKEN }}
3636

3737
- name: Build and push by digest
3838
id: build
39-
uses: docker/build-push-action@v6
39+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
4040
with:
4141
context: .
4242
platforms: ${{ matrix.platform }}
@@ -49,7 +49,7 @@ jobs:
4949
touch "/tmp/digests/${digest#sha256:}"
5050
5151
- name: Upload digest
52-
uses: actions/upload-artifact@v4
52+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
5353
with:
5454
name: digest-production-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
5555
path: /tmp/digests/*
@@ -65,17 +65,17 @@ jobs:
6565

6666
steps:
6767
- name: Download digests
68-
uses: actions/download-artifact@v4
68+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
6969
with:
7070
path: /tmp/digests
7171
pattern: digest-production-*
7272
merge-multiple: true
7373

7474
- name: Set up Docker Buildx
75-
uses: docker/setup-buildx-action@v3
75+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
7676

7777
- name: Log in to GHCR
78-
uses: docker/login-action@v3
78+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
7979
with:
8080
registry: ghcr.io
8181
username: ${{ github.actor }}

.github/workflows/build-staging.yaml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -22,21 +22,21 @@ jobs:
2222

2323
steps:
2424
- name: Checkout
25-
uses: actions/checkout@v4
25+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
2626

2727
- name: Set up Docker Buildx
28-
uses: docker/setup-buildx-action@v3
28+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
2929

3030
- name: Log in to GHCR
31-
uses: docker/login-action@v3
31+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
3232
with:
3333
registry: ghcr.io
3434
username: ${{ github.actor }}
3535
password: ${{ secrets.GITHUB_TOKEN }}
3636

3737
- name: Build and push by digest
3838
id: build
39-
uses: docker/build-push-action@v6
39+
uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6
4040
with:
4141
context: .
4242
platforms: ${{ matrix.platform }}
@@ -49,7 +49,7 @@ jobs:
4949
touch "/tmp/digests/${digest#sha256:}"
5050
5151
- name: Upload digest
52-
uses: actions/upload-artifact@v4
52+
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
5353
with:
5454
name: digest-staging-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }}
5555
path: /tmp/digests/*
@@ -65,17 +65,17 @@ jobs:
6565

6666
steps:
6767
- name: Download digests
68-
uses: actions/download-artifact@v4
68+
uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7
6969
with:
7070
path: /tmp/digests
7171
pattern: digest-staging-*
7272
merge-multiple: true
7373

7474
- name: Set up Docker Buildx
75-
uses: docker/setup-buildx-action@v3
75+
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3
7676

7777
- name: Log in to GHCR
78-
uses: docker/login-action@v3
78+
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3
7979
with:
8080
registry: ghcr.io
8181
username: ${{ github.actor }}

.github/workflows/pr-ci.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
name: PR CI
2+
3+
on:
4+
pull_request:
5+
types:
6+
- opened
7+
- synchronize
8+
- reopened
9+
- ready_for_review
10+
11+
permissions:
12+
contents: read
13+
14+
concurrency:
15+
group: pr-ci-${{ github.event.pull_request.number }}
16+
cancel-in-progress: true
17+
18+
jobs:
19+
validate:
20+
if: ${{ !github.event.pull_request.draft }}
21+
runs-on: ubuntu-latest
22+
timeout-minutes: 20
23+
env:
24+
NEXT_TELEMETRY_DISABLED: 1
25+
26+
steps:
27+
- name: Checkout
28+
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
29+
30+
- name: Enable Corepack
31+
run: corepack enable
32+
33+
- name: Set up Node.js
34+
uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6
35+
with:
36+
node-version-file: .nvmrc
37+
cache: yarn
38+
cache-dependency-path: yarn.lock
39+
40+
- name: Install dependencies
41+
run: yarn install --immutable
42+
43+
- name: Lint JS/TS
44+
run: yarn lint
45+
46+
- name: Lint CSS
47+
run: yarn lint:css
48+
49+
- name: Run tests
50+
run: yarn test --ci --watch=false
51+
52+
- name: Build
53+
run: yarn build

.github/workflows/staging-auto-pr.yaml

Lines changed: 86 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,18 +5,91 @@ on:
55

66
jobs:
77
pull-request:
8-
name: Open PR to main
8+
name: Open or Update PR to Production
99
runs-on: ubuntu-latest
10+
permissions:
11+
contents: read
12+
pull-requests: write
13+
concurrency:
14+
group: staging-auto-pr
15+
cancel-in-progress: true
1016
steps:
11-
- uses: actions/checkout@v2
12-
name: checkout
13-
14-
- uses: repo-sync/pull-request@v2
15-
name: pull-request
16-
with:
17-
destination_branch: "production"
18-
pr_title: "Staging to Production"
19-
pr_body: "This PR was auto-generated via a workflow action."
20-
pr_reviewer: ${{ github.actor }}
21-
source_branch: "staging"
22-
github_token: ${{ secrets.GITHUB_TOKEN }}
17+
- name: Create or update staging -> production PR
18+
env:
19+
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
20+
REPO: ${{ github.repository }}
21+
OWNER: ${{ github.repository_owner }}
22+
SOURCE_BRANCH: staging
23+
DESTINATION_BRANCH: production
24+
PR_TITLE: Staging to Production
25+
REVIEWER: ${{ github.actor }}
26+
run: |
27+
set -euo pipefail
28+
29+
compare_json="$(gh api "repos/${REPO}/compare/${DESTINATION_BRANCH}...${SOURCE_BRANCH}")"
30+
compare_url="$(echo "${compare_json}" | jq -r '.html_url')"
31+
ahead_by="$(echo "${compare_json}" | jq -r '.ahead_by')"
32+
33+
prs_tsv="$(mktemp)"
34+
while IFS= read -r sha; do
35+
[ -n "${sha}" ] || continue
36+
gh api "repos/${REPO}/commits/${sha}/pulls" \
37+
--jq '.[] | select(.merged_at != null) | "\(.number)\t\(.title)\t\(.html_url)"' \
38+
>> "${prs_tsv}" || true
39+
done < <(echo "${compare_json}" | jq -r '.commits[].sha')
40+
41+
if [ -s "${prs_tsv}" ]; then
42+
prs_markdown="$(sort -t $'\t' -k1,1n -u "${prs_tsv}" | awk -F '\t' '{printf "- #%s %s (%s)\n", $1, $2, $3}')"
43+
else
44+
prs_markdown="- No linked pull requests found (direct commits or metadata unavailable)."
45+
fi
46+
47+
pr_body_file="$(mktemp)"
48+
{
49+
echo "This PR was auto-generated via a workflow action."
50+
echo
51+
echo "## Included Pull Requests"
52+
echo "${prs_markdown}"
53+
echo
54+
echo "## Diff"
55+
echo "- Source: \`${SOURCE_BRANCH}\`"
56+
echo "- Target: \`${DESTINATION_BRANCH}\`"
57+
echo "- Commits ahead: ${ahead_by}"
58+
echo "- Compare: ${compare_url}"
59+
} > "${pr_body_file}"
60+
61+
pr_number="$(gh api "repos/${REPO}/pulls" \
62+
-f state="open" \
63+
-f base="${DESTINATION_BRANCH}" \
64+
-f head="${OWNER}:${SOURCE_BRANCH}" \
65+
--jq '.[0].number // empty')"
66+
67+
if [ -z "${pr_number}" ]; then
68+
echo "No open PR found for ${SOURCE_BRANCH} -> ${DESTINATION_BRANCH}. Creating one."
69+
create_payload="$(jq -n \
70+
--arg title "${PR_TITLE}" \
71+
--arg head "${SOURCE_BRANCH}" \
72+
--arg base "${DESTINATION_BRANCH}" \
73+
--rawfile body "${pr_body_file}" \
74+
'{title: $title, head: $head, base: $base, body: $body}')"
75+
76+
pr_number="$(gh api \
77+
-X POST "repos/${REPO}/pulls" \
78+
--input - \
79+
--jq '.number' <<<"${create_payload}")"
80+
else
81+
echo "Updating existing PR #${pr_number}."
82+
update_payload="$(jq -n \
83+
--arg title "${PR_TITLE}" \
84+
--rawfile body "${pr_body_file}" \
85+
'{title: $title, body: $body}')"
86+
87+
gh api \
88+
-X PATCH "repos/${REPO}/pulls/${pr_number}" \
89+
--input - <<<"${update_payload}" >/dev/null
90+
fi
91+
92+
gh api \
93+
-X POST "repos/${REPO}/pulls/${pr_number}/requested_reviewers" \
94+
-f reviewers[]="${REVIEWER}" >/dev/null || \
95+
echo "Reviewer assignment skipped for ${REVIEWER}."

eslint.config.js

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,15 @@
11
const { FlatCompat } = require('@eslint/eslintrc');
2+
const js = require('@eslint/js');
23
const babelParser = require('@babel/eslint-parser');
34

45
const compat = new FlatCompat({
56
baseDirectory: __dirname,
7+
recommendedConfig: js.configs.recommended,
68
});
79

810
module.exports = [
911
{
10-
ignores: ['.next/**', 'node_modules/**', 'public/**'],
12+
ignores: ['.next/**', '.storybook/**', 'coverage/**', 'node_modules/**', 'public/**', 'static/**'],
1113
},
1214
...compat.extends(
1315
'eslint:recommended',

0 commit comments

Comments
 (0)