diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index 0d892b6..0000000 --- a/.eslintignore +++ /dev/null @@ -1,5 +0,0 @@ -node_modules -.next -.storybook -coverage -static diff --git a/.github/workflows/build-production.yaml b/.github/workflows/build-production.yaml index 5b84004..2260b39 100644 --- a/.github/workflows/build-production.yaml +++ b/.github/workflows/build-production.yaml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -36,7 +36,7 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . platforms: ${{ matrix.platform }} @@ -49,7 +49,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: digest-production-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} path: /tmp/digests/* @@ -65,17 +65,17 @@ jobs: steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: path: /tmp/digests pattern: digest-production-* merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/build-staging.yaml b/.github/workflows/build-staging.yaml index b871cb6..283174a 100644 --- a/.github/workflows/build-staging.yaml +++ b/.github/workflows/build-staging.yaml @@ -22,13 +22,13 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v4 + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} @@ -36,7 +36,7 @@ jobs: - name: Build and push by digest id: build - uses: docker/build-push-action@v6 + uses: docker/build-push-action@10e90e3645eae34f1e60eeb005ba3a3d33f178e8 # v6 with: context: . platforms: ${{ matrix.platform }} @@ -49,7 +49,7 @@ jobs: touch "/tmp/digests/${digest#sha256:}" - name: Upload digest - uses: actions/upload-artifact@v4 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6 with: name: digest-staging-${{ matrix.platform == 'linux/amd64' && 'amd64' || 'arm64' }} path: /tmp/digests/* @@ -65,17 +65,17 @@ jobs: steps: - name: Download digests - uses: actions/download-artifact@v4 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 # v7 with: path: /tmp/digests pattern: digest-staging-* merge-multiple: true - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 + uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3 - name: Log in to GHCR - uses: docker/login-action@v3 + uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3 with: registry: ghcr.io username: ${{ github.actor }} diff --git a/.github/workflows/pr-ci.yml b/.github/workflows/pr-ci.yml new file mode 100644 index 0000000..2f0f89e --- /dev/null +++ b/.github/workflows/pr-ci.yml @@ -0,0 +1,53 @@ +name: PR CI + +on: + pull_request: + types: + - opened + - synchronize + - reopened + - ready_for_review + +permissions: + contents: read + +concurrency: + group: pr-ci-${{ github.event.pull_request.number }} + cancel-in-progress: true + +jobs: + validate: + if: ${{ !github.event.pull_request.draft }} + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + NEXT_TELEMETRY_DISABLED: 1 + + steps: + - name: Checkout + uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + + - name: Enable Corepack + run: corepack enable + + - name: Set up Node.js + uses: actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238 # v6 + with: + node-version-file: .nvmrc + cache: yarn + cache-dependency-path: yarn.lock + + - name: Install dependencies + run: yarn install --immutable + + - name: Lint JS/TS + run: yarn lint + + - name: Lint CSS + run: yarn lint:css + + - name: Run tests + run: yarn test --ci --watch=false + + - name: Build + run: yarn build diff --git a/.github/workflows/staging-auto-pr.yaml b/.github/workflows/staging-auto-pr.yaml index c3d9f56..012d747 100644 --- a/.github/workflows/staging-auto-pr.yaml +++ b/.github/workflows/staging-auto-pr.yaml @@ -5,18 +5,91 @@ on: jobs: pull-request: - name: Open PR to main + name: Open or Update PR to Production runs-on: ubuntu-latest + permissions: + contents: read + pull-requests: write + concurrency: + group: staging-auto-pr + cancel-in-progress: true steps: - - uses: actions/checkout@v2 - name: checkout - - - uses: repo-sync/pull-request@v2 - name: pull-request - with: - destination_branch: "production" - pr_title: "Staging to Production" - pr_body: "This PR was auto-generated via a workflow action." - pr_reviewer: ${{ github.actor }} - source_branch: "staging" - github_token: ${{ secrets.GITHUB_TOKEN }} + - name: Create or update staging -> production PR + env: + GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} + REPO: ${{ github.repository }} + OWNER: ${{ github.repository_owner }} + SOURCE_BRANCH: staging + DESTINATION_BRANCH: production + PR_TITLE: Staging to Production + REVIEWER: ${{ github.actor }} + run: | + set -euo pipefail + + compare_json="$(gh api "repos/${REPO}/compare/${DESTINATION_BRANCH}...${SOURCE_BRANCH}")" + compare_url="$(echo "${compare_json}" | jq -r '.html_url')" + ahead_by="$(echo "${compare_json}" | jq -r '.ahead_by')" + + prs_tsv="$(mktemp)" + while IFS= read -r sha; do + [ -n "${sha}" ] || continue + gh api "repos/${REPO}/commits/${sha}/pulls" \ + --jq '.[] | select(.merged_at != null) | "\(.number)\t\(.title)\t\(.html_url)"' \ + >> "${prs_tsv}" || true + done < <(echo "${compare_json}" | jq -r '.commits[].sha') + + if [ -s "${prs_tsv}" ]; then + prs_markdown="$(sort -t $'\t' -k1,1n -u "${prs_tsv}" | awk -F '\t' '{printf "- #%s %s (%s)\n", $1, $2, $3}')" + else + prs_markdown="- No linked pull requests found (direct commits or metadata unavailable)." + fi + + pr_body_file="$(mktemp)" + { + echo "This PR was auto-generated via a workflow action." + echo + echo "## Included Pull Requests" + echo "${prs_markdown}" + echo + echo "## Diff" + echo "- Source: \`${SOURCE_BRANCH}\`" + echo "- Target: \`${DESTINATION_BRANCH}\`" + echo "- Commits ahead: ${ahead_by}" + echo "- Compare: ${compare_url}" + } > "${pr_body_file}" + + pr_number="$(gh api "repos/${REPO}/pulls" \ + -f state="open" \ + -f base="${DESTINATION_BRANCH}" \ + -f head="${OWNER}:${SOURCE_BRANCH}" \ + --jq '.[0].number // empty')" + + if [ -z "${pr_number}" ]; then + echo "No open PR found for ${SOURCE_BRANCH} -> ${DESTINATION_BRANCH}. Creating one." + create_payload="$(jq -n \ + --arg title "${PR_TITLE}" \ + --arg head "${SOURCE_BRANCH}" \ + --arg base "${DESTINATION_BRANCH}" \ + --rawfile body "${pr_body_file}" \ + '{title: $title, head: $head, base: $base, body: $body}')" + + pr_number="$(gh api \ + -X POST "repos/${REPO}/pulls" \ + --input - \ + --jq '.number' <<<"${create_payload}")" + else + echo "Updating existing PR #${pr_number}." + update_payload="$(jq -n \ + --arg title "${PR_TITLE}" \ + --rawfile body "${pr_body_file}" \ + '{title: $title, body: $body}')" + + gh api \ + -X PATCH "repos/${REPO}/pulls/${pr_number}" \ + --input - <<<"${update_payload}" >/dev/null + fi + + gh api \ + -X POST "repos/${REPO}/pulls/${pr_number}/requested_reviewers" \ + -f reviewers[]="${REVIEWER}" >/dev/null || \ + echo "Reviewer assignment skipped for ${REVIEWER}." diff --git a/eslint.config.js b/eslint.config.js index a5a78c9..be003b7 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -4,6 +4,7 @@ const prettierConfig = require("eslint-config-prettier/flat"); const compat = new FlatCompat({ baseDirectory: __dirname, + recommendedConfig: js.configs.recommended, }); module.exports = [ diff --git a/package.json b/package.json index daeb04b..490dc2b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "test:coverage": "yarn test --coverage", "lint": "eslint .", "lint:fix": "eslint . --fix", - "lint:css": "stylelint './**/*.{js,jsx,ts,tsx}'", + "lint:css": "stylelint './**/*.styles.ts'", "format": "prettier . --write", "format:check": "prettier . --check", "prepare": "husky", @@ -76,6 +76,7 @@ "jest-environment-jsdom": "^29.7.0", "jest-fetch-mock": "^3.0.1", "lint-staged": "^16.2.4", + "postcss-styled-syntax": "^0.7.1", "prettier": "^3.8.1", "react-test-renderer": "^18.0.0", "redis-mock": "^0.47.0", diff --git a/stylelint.config.js b/stylelint.config.js index 0c74b0c..5238386 100644 --- a/stylelint.config.js +++ b/stylelint.config.js @@ -1,3 +1,4 @@ module.exports = { extends: ["stylelint-config-recommended"], + customSyntax: "postcss-styled-syntax", }; diff --git a/yarn.lock b/yarn.lock index 644eab3..206c194 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3431,6 +3431,7 @@ __metadata: next: "npm:^14.2.0" next-redux-wrapper: "npm:^8.1.0" nprogress: "npm:^0.2.0" + postcss-styled-syntax: "npm:^0.7.1" prettier: "npm:^3.8.1" quagga: "npm:^0.12.1" react: "npm:^18.0.0" @@ -8048,6 +8049,17 @@ __metadata: languageName: node linkType: hard +"postcss-styled-syntax@npm:^0.7.1": + version: 0.7.1 + resolution: "postcss-styled-syntax@npm:0.7.1" + dependencies: + typescript: "npm:^5.7.3" + peerDependencies: + postcss: ^8.5.1 + checksum: 10c0/85339ca0c511c88cb46bd3e2cdc1bc7ff695960946b653f411939f06cb47bd3e4f2d47e580cf949e56978153c49b462702c9e71d598f1e38619569783e431073 + languageName: node + linkType: hard + "postcss-value-parser@npm:^4.0.2": version: 4.0.2 resolution: "postcss-value-parser@npm:4.0.2"