From 528a7341873655559c064425a58d8f95a2dcd6b6 Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 6 Feb 2025 15:19:33 -0800 Subject: [PATCH 1/2] Create reusable validateActor action --- .github/actionlint.yaml | 8 ++ .../composite/validateActor/action.yml | 45 ++++++++++ .github/workflows/cherryPick.yml | 18 ---- .github/workflows/createNewVersion.yml | 20 ++--- .github/workflows/deploy.yml | 25 ++---- .github/workflows/testBuild.yml | 84 ++++++++----------- .github/workflows/testBuildHybrid.yml | 77 +++++++---------- 7 files changed, 131 insertions(+), 146 deletions(-) create mode 100644 .github/actions/composite/validateActor/action.yml diff --git a/.github/actionlint.yaml b/.github/actionlint.yaml index e2076c029f70..073c836595ae 100644 --- a/.github/actionlint.yaml +++ b/.github/actionlint.yaml @@ -6,3 +6,11 @@ self-hosted-runner: - macos-15-xlarge - ubuntu-latest-reassure-tests - macos-12 + +paths: + '**/*': + ignore: + # This is meant to be a temporary workaround for a bug in actionslint. Upstream: + # - issue: https://github.com/rhysd/actionlint/issues/511 + # - PR: https://github.com/rhysd/actionlint/pull/513 + - '"env" is not allowed in "runs" section because .* is a Composite action.*' diff --git a/.github/actions/composite/validateActor/action.yml b/.github/actions/composite/validateActor/action.yml new file mode 100644 index 000000000000..5cb2d167f39f --- /dev/null +++ b/.github/actions/composite/validateActor/action.yml @@ -0,0 +1,45 @@ +name: Validate actor +description: Validate the the current actor has the permissions they need. By default, it validates that the user has write permissions. + +inputs: + # If `REQUIRE_APP_DEPLOYER` is true, we check that they're an app deployer. If not, we just check that they have write access. + REQUIRE_APP_DEPLOYER: + description: Should this action require the actor to be an app deployer? + required: false + default: 'true' + OS_BOTIFY_TOKEN: + description: OSBotify token. Needed to access certain API endpoints the regular github.token can't + required: true + +runs: + using: composite + steps: + - name: Get user permissions + if: ${{ !fromJSON(inputs.REQUIRE_APP_DEPLOYER) }} + id: getUserPermissions + shell: bash + run: | + PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission | jq -r '.permission') + if [[ "$PERMISSION" == 'write' || "$PERMISSION" == 'admin' ]]; then + echo "::notice::✅ Actor ${{ github.actor }} has write permission" + else + echo "::error::❌ Actor ${{ github.actor }} does not have write permission" + exit 1 + fi + env: + GITHUB_TOKEN: ${{ inputs.OS_BOTIFY_TOKEN }} + + - name: Check if user is deployer + id: isUserDeployer + if: fromJSON(inputs.REQUIRE_APP_DEPLOYER) + shell: bash + run: | + if [[ "${{ github.actor }}" == "OSBotify" || "${{ github.actor }}" == "os-botify[bot]" ]] || \ + gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then + echo "::notice::✅ Actor ${{ github.actor }} is an app deployer" + else + echo "::error::❌ Actor ${{ github.actor }} is not an app deployer" + exit 1 + fi + env: + GITHUB_TOKEN: ${{ inputs.OS_BOTIFY_TOKEN }} diff --git a/.github/workflows/cherryPick.yml b/.github/workflows/cherryPick.yml index b7dcf95294be..5816de115e86 100644 --- a/.github/workflows/cherryPick.yml +++ b/.github/workflows/cherryPick.yml @@ -8,25 +8,7 @@ on: required: true jobs: - validateActor: - runs-on: ubuntu-latest - outputs: - IS_DEPLOYER: ${{ fromJSON(steps.isDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' || github.actor == 'os-botify[bot]' }} - steps: - - name: Check if user is deployer - id: isDeployer - run: | - if gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then - echo "IS_DEPLOYER=true" >> "$GITHUB_OUTPUT" - else - echo "IS_DEPLOYER=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - createNewVersion: - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} uses: ./.github/workflows/createNewVersion.yml secrets: inherit diff --git a/.github/workflows/createNewVersion.yml b/.github/workflows/createNewVersion.yml index a933c2f1686f..6418c721cd00 100644 --- a/.github/workflows/createNewVersion.yml +++ b/.github/workflows/createNewVersion.yml @@ -34,26 +34,16 @@ on: required: true jobs: - validateActor: - runs-on: ubuntu-latest - outputs: - HAS_WRITE_ACCESS: ${{ contains(fromJSON('["write", "admin"]'), steps.getUserPermissions.outputs.PERMISSION) }} - steps: - - name: Get user permissions - id: getUserPermissions - run: echo "PERMISSION=$(gh api /repos/${{ github.repository }}/collaborators/${{ github.actor }}/permission | jq -r '.permission')" >> "$GITHUB_OUTPUT" - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - createNewVersion: runs-on: macos-latest - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.HAS_WRITE_ACCESS) }} - outputs: NEW_VERSION: ${{ steps.bumpVersion.outputs.NEW_VERSION }} - steps: + - name: Validate actor + uses: ./.github/actions/composite/validateActor + with: + OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} + - name: Run turnstyle uses: softprops/turnstyle@49108bdfa571e62371bd2c3094893c547ab3fc03 with: diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 4b4ea2413ae5..75a1fd773a2d 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -12,26 +12,7 @@ concurrency: cancel-in-progress: true jobs: - validateActor: - runs-on: ubuntu-latest - timeout-minutes: 90 - outputs: - IS_DEPLOYER: ${{ fromJSON(steps.isUserDeployer.outputs.IS_DEPLOYER) || github.actor == 'OSBotify' || github.actor == 'os-botify[bot]' }} - steps: - - name: Check if user is deployer - id: isUserDeployer - run: | - if gh api /orgs/Expensify/teams/mobile-deployers/memberships/${{ github.actor }} --silent; then - echo "IS_DEPLOYER=true" >> "$GITHUB_OUTPUT" - else - echo "IS_DEPLOYER=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} - prep: - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.IS_DEPLOYER) }} runs-on: ubuntu-latest outputs: APP_VERSION: ${{ steps.getAppVersion.outputs.VERSION }} @@ -41,6 +22,12 @@ jobs: with: token: ${{ secrets.OS_BOTIFY_TOKEN }} + - name: Validate actor + id: validateActor + uses: ./.github/actions/composite/validateActor + with: + OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} + - name: Setup git for OSBotify uses: ./.github/actions/composite/setupGitForOSBotifyApp id: setupGitForOSBotify diff --git a/.github/workflows/testBuild.yml b/.github/workflows/testBuild.yml index 80918d65462c..3914f9c29cff 100644 --- a/.github/workflows/testBuild.yml +++ b/.github/workflows/testBuild.yml @@ -14,43 +14,37 @@ env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} jobs: - validateActor: + prep: runs-on: ubuntu-latest outputs: - READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} + REF: ${{ github.event.pull_request.head.sha || steps.getHeadRef.outputs.REF }} + READY_TO_BUILD: ${{ steps.readyToBuild.outputs.READY_TO_BUILD }} steps: - - name: Is Expensify employee - id: isExpensifyEmployee - run: | - if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then - echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" - else - echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + - name: Checkout + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: actions/checkout@v4 + + - name: Validate that user is an Expensify employee + uses: ./.github/actions/composite/validateActor + with: + REQUIRE_APP_DEPLOYER: false + OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - - id: hasReadyToBuildLabel - name: Set HAS_READY_TO_BUILD_LABEL flag + - name: Check if PR has Ready to Build label + id: readyToBuild run: | - echo "HAS_READY_TO_BUILD_LABEL=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name' | grep -q 'Ready To Build' && echo 'true')" >> "$GITHUB_OUTPUT" - if [[ "$HAS_READY_TO_BUILD_LABEL" != 'true' ]]; then - echo "The 'Ready to Build' label is not attached to the PR #${{ env.PULL_REQUEST_NUMBER }}" + LABELS=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name') + if echo "$LABELS" | grep -q 'Ready To Build'; then + echo "::notice::✅ PR ${{ env.PULL_REQUEST_NUMBER }} has 'Ready to Build' label" + echo "READY_TO_BUILD=true" >> "$GITHUB_OUTPUT" + else + echo "::error::❌ PR ${{ env.PULL_REQUEST_NUMBER }} does not have 'Ready to Build' label" + echo "READY_TO_BUILD=false" >> "$GITHUB_OUTPUT" + exit 1 fi env: GITHUB_TOKEN: ${{ github.token }} - getBranchRef: - runs-on: ubuntu-latest - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - outputs: - REF: ${{ steps.getHeadRef.outputs.REF }} - steps: - - name: Checkout - if: ${{ github.event_name == 'workflow_dispatch' }} - uses: actions/checkout@v4 - - name: Check if pull request number is correct if: ${{ github.event_name == 'workflow_dispatch' }} id: getHeadRef @@ -58,12 +52,11 @@ jobs: set -e echo "REF=$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} postGitHubCommentBuildStarted: runs-on: ubuntu-latest - needs: [validateActor, getBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] steps: - name: Add build start comment uses: actions/github-script@v7 @@ -81,12 +74,11 @@ jobs: buildAndroid: name: Build Android app for testing uses: ./.github/workflows/buildAndroid.yml - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - needs: [validateActor, getBranchRef] + needs: [prep] secrets: inherit with: type: adhoc - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} pull_request_number: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} uploadAndroid: @@ -135,8 +127,7 @@ jobs: iOS: name: Build and deploy iOS for testing - needs: [validateActor, getBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] env: DEVELOPER_DIR: /Applications/Xcode_16.2.0.app/Contents/Developer runs-on: macos-15-xlarge @@ -144,7 +135,7 @@ jobs: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} - name: Configure MapBox SDK run: ./scripts/setup-mapbox-sdk.sh ${{ secrets.MAPBOX_SDK_DOWNLOAD_TOKEN }} @@ -224,14 +215,13 @@ jobs: desktop: name: Build and deploy Desktop for testing - needs: [validateActor, getBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] runs-on: macos-14-large steps: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it run: | @@ -273,14 +263,13 @@ jobs: web: name: Build and deploy Web - needs: [validateActor, getBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] runs-on: ubuntu-latest-xl steps: - name: Checkout uses: actions/checkout@v4 with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} - name: Create .env.adhoc file based on staging and add PULL_REQUEST_NUMBER env to it run: | @@ -307,18 +296,16 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, uploadAndroid, iOS, desktop, web] - if: ${{ always() }} + needs: [prep, uploadAndroid, iOS, desktop, web] + if: ${{ always() && needs.prep.outputs.READY_TO_BUILD == 'true' }} steps: - name: Checkout uses: actions/checkout@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} - name: Download Artifact uses: actions/download-artifact@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - name: Read JSONs with iOS paths id: get_ios_path @@ -332,7 +319,6 @@ jobs: echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT" - name: Publish links to apps for download - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} uses: ./.github/actions/javascript/postTestBuildComment with: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} diff --git a/.github/workflows/testBuildHybrid.yml b/.github/workflows/testBuildHybrid.yml index 9bd1b3b0f541..9ef2e593a827 100644 --- a/.github/workflows/testBuildHybrid.yml +++ b/.github/workflows/testBuildHybrid.yml @@ -14,43 +14,37 @@ env: PULL_REQUEST_NUMBER: ${{ github.event.number || github.event.inputs.PULL_REQUEST_NUMBER }} jobs: - validateActor: + prep: runs-on: ubuntu-latest outputs: - READY_TO_BUILD: ${{ fromJSON(steps.isExpensifyEmployee.outputs.IS_EXPENSIFY_EMPLOYEE) && fromJSON(steps.hasReadyToBuildLabel.outputs.HAS_READY_TO_BUILD_LABEL) }} + REF: ${{ github.event.pull_request.head.sha || steps.getHeadRef.outputs.REF }} + READY_TO_BUILD: ${{ steps.readyToBuild.outputs.READY_TO_BUILD }} steps: - - name: Is Expensify employee - id: isExpensifyEmployee - run: | - if gh api /orgs/Expensify/teams/expensify-expensify/memberships/${{ github.actor }} --silent; then - echo "IS_EXPENSIFY_EMPLOYEE=true" >> "$GITHUB_OUTPUT" - else - echo "IS_EXPENSIFY_EMPLOYEE=false" >> "$GITHUB_OUTPUT" - fi - env: - GITHUB_TOKEN: ${{ secrets.OS_BOTIFY_TOKEN }} + - name: Checkout + if: ${{ github.event_name == 'workflow_dispatch' }} + uses: actions/checkout@v4 + + - name: Validate that user is an Expensify employee + uses: ./.github/actions/composite/validateActor + with: + REQUIRE_APP_DEPLOYER: false + OS_BOTIFY_TOKEN: ${{ secrets.OS_BOTIFY_COMMIT_TOKEN }} - - id: hasReadyToBuildLabel - name: Set HAS_READY_TO_BUILD_LABEL flag + - name: Check if PR has Ready to Build label + id: readyToBuild run: | - echo "HAS_READY_TO_BUILD_LABEL=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name' | grep -q 'Ready To Build' && echo 'true')" >> "$GITHUB_OUTPUT" - if [[ "$HAS_READY_TO_BUILD_LABEL" != 'true' ]]; then - echo "The 'Ready to Build' label is not attached to the PR #${{ env.PULL_REQUEST_NUMBER }}" + LABELS=$(gh pr view "${{ env.PULL_REQUEST_NUMBER }}" --repo Expensify/App --json labels --jq '.labels[].name') + if echo "$LABELS" | grep -q 'Ready To Build'; then + echo "::notice::✅ PR ${{ env.PULL_REQUEST_NUMBER }} has 'Ready to Build' label" + echo "READY_TO_BUILD=true" >> "$GITHUB_OUTPUT" + else + echo "::error::❌ PR ${{ env.PULL_REQUEST_NUMBER }} does not have 'Ready to Build' label" + echo "READY_TO_BUILD=false" >> "$GITHUB_OUTPUT" + exit 1 fi env: GITHUB_TOKEN: ${{ github.token }} - getBranchRef: - runs-on: ubuntu-latest - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - outputs: - REF: ${{ steps.getHeadRef.outputs.REF }} - steps: - - name: Checkout - if: ${{ github.event_name == 'workflow_dispatch' }} - uses: actions/checkout@v4 - - name: Check if pull request number is correct if: ${{ github.event_name == 'workflow_dispatch' }} id: getHeadRef @@ -58,12 +52,11 @@ jobs: set -e echo "REF=$(gh pr view ${{ github.event.inputs.PULL_REQUEST_NUMBER }} --json headRefOid --jq '.headRefOid')" >> "$GITHUB_OUTPUT" env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + GITHUB_TOKEN: ${{ github.token }} getOldDotPR: runs-on: ubuntu-latest - needs: validateActor - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] outputs: OLD_DOT_PR: ${{ steps.old-dot-pr.outputs.result }} steps: @@ -110,8 +103,7 @@ jobs: postGitHubCommentBuildStarted: runs-on: ubuntu-latest - needs: [validateActor, getBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep] steps: - name: Add build start comment uses: actions/github-script@v7 @@ -128,8 +120,7 @@ jobs: androidHybrid: name: Build Android HybridApp - needs: [validateActor, getBranchRef, getOldDotBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep, getOldDotBranchRef] runs-on: ubuntu-latest-xl outputs: S3_APK_PATH: ${{ steps.exportAndroidS3Path.outputs.S3_APK_PATH }} @@ -138,7 +129,7 @@ jobs: uses: actions/checkout@v4 with: submodules: true - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} token: ${{ secrets.OS_BOTIFY_TOKEN }} # fetch-depth: 0 is required in order to fetch the correct submodule branch fetch-depth: 0 @@ -240,8 +231,7 @@ jobs: iosHybrid: name: Build and deploy iOS for testing - needs: [validateActor, getBranchRef, getOldDotBranchRef] - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} + needs: [prep, getOldDotBranchRef] env: DEVELOPER_DIR: /Applications/Xcode_16.2.0.app/Contents/Developer runs-on: macos-15-xlarge @@ -250,7 +240,7 @@ jobs: uses: actions/checkout@v4 with: submodules: true - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} token: ${{ secrets.OS_BOTIFY_TOKEN }} # fetch-depth: 0 is required in order to fetch the correct submodule branch fetch-depth: 0 @@ -347,18 +337,16 @@ jobs: postGithubComment: runs-on: ubuntu-latest name: Post a GitHub comment with app download links for testing - needs: [validateActor, getBranchRef, androidHybrid, iosHybrid] - if: ${{ always() }} + needs: [prep, androidHybrid, iosHybrid] + if: ${{ always() && needs.prep.outputs.READY_TO_BUILD == 'true' }} steps: - name: Checkout uses: actions/checkout@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} with: - ref: ${{ github.event.pull_request.head.sha || needs.getBranchRef.outputs.REF }} + ref: ${{ needs.prep.outputs.REF }} - name: Download Artifact uses: actions/download-artifact@v4 - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} - name: Read JSONs with iOS paths id: get_ios_path @@ -372,7 +360,6 @@ jobs: echo "ios_path=$ios_path" >> "$GITHUB_OUTPUT" - name: Publish links to apps for download - if: ${{ fromJSON(needs.validateActor.outputs.READY_TO_BUILD) }} uses: ./.github/actions/javascript/postTestBuildComment with: PR_NUMBER: ${{ env.PULL_REQUEST_NUMBER }} From 7f80b51c49a5f340400845bc5d97137e24c20a3e Mon Sep 17 00:00:00 2001 From: rory Date: Thu, 6 Feb 2025 15:43:07 -0800 Subject: [PATCH 2/2] Use default environment variables for GITHUB_OWNER and APP_REPO --- .github/libs/CONST.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/libs/CONST.ts b/.github/libs/CONST.ts index 0fa32bc31ded..50fa269069c5 100644 --- a/.github/libs/CONST.ts +++ b/.github/libs/CONST.ts @@ -1,8 +1,9 @@ const GITHUB_BASE_URL_REGEX = new RegExp('https?://(?:github\\.com|api\\.github\\.com)'); const GIT_CONST = { - GITHUB_OWNER: 'Expensify', - APP_REPO: 'App', + GITHUB_OWNER: process.env.GITHUB_REPOSITORY_OWNER, + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + APP_REPO: process.env.GITHUB_REPOSITORY_OWNER.split('/').at(1)!, } as const; const CONST = {