├── .gitignore ├── github-readme.webp ├── github-actions-pr-commit.webp ├── github-settings-new-repo-secret.webp ├── github-settings-new-repo-secret-form.webp ├── github-fine-grained-personal-access-token-metadata.webp ├── github-fine-grained-personal-access-token-overview.webp ├── github-fine-grained-personal-access-token-repo-access.webp ├── github-fine-grained-personal-access-token-contents-read-write.webp ├── github-fine-grained-personal-access-token-name-expiration-description.webp └── readme.md /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | -------------------------------------------------------------------------------- /github-readme.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-readme.webp -------------------------------------------------------------------------------- /github-actions-pr-commit.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-actions-pr-commit.webp -------------------------------------------------------------------------------- /github-settings-new-repo-secret.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-settings-new-repo-secret.webp -------------------------------------------------------------------------------- /github-settings-new-repo-secret-form.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-settings-new-repo-secret-form.webp -------------------------------------------------------------------------------- /github-fine-grained-personal-access-token-metadata.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-fine-grained-personal-access-token-metadata.webp -------------------------------------------------------------------------------- /github-fine-grained-personal-access-token-overview.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-fine-grained-personal-access-token-overview.webp -------------------------------------------------------------------------------- /github-fine-grained-personal-access-token-repo-access.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-fine-grained-personal-access-token-repo-access.webp -------------------------------------------------------------------------------- /github-fine-grained-personal-access-token-contents-read-write.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-fine-grained-personal-access-token-contents-read-write.webp -------------------------------------------------------------------------------- /github-fine-grained-personal-access-token-name-expiration-description.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/karlhorky/github-tricks/HEAD/github-fine-grained-personal-access-token-name-expiration-description.webp -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # GitHub Tricks 2 | 3 | A collection of useful GitHub tricks 4 | 5 | ## GitHub Actions: Configure `actions/cache` to Skip Cache Restoration on Changes in Directory 6 | 7 | To configure [`actions/cache`](https://github.com/actions/cache) to skip cache restoration on modification of any files or directories inside a Git-tracked directory, configure [`actions/checkout`](https://github.com/actions/checkout) to fetch all commits in all branches and tags (warning: may be expensive) and use a `key` based on the last Git commit hash which modified anything contained in the directory: 8 | 9 | ```yaml 10 | name: Skip cache restoration on changes in directory 11 | on: [push] 12 | jobs: 13 | build: 14 | runs-on: ubuntu-latest 15 | timeout-minutes: 10 16 | steps: 17 | - name: Checkout repository 18 | uses: actions/checkout@v4 19 | with: 20 | # Fetch all commits in all branches and tags 21 | fetch-depth: 0 22 | 23 | - name: Get last Git commit hash modifying packages/abc 24 | run: | 25 | echo "ABC_HASH=$(git log -1 --pretty=format:%H -- packages/abc)" >> $GITHUB_ENV 26 | 27 | - name: Cache packages/abc 28 | uses: actions/cache@v4 29 | with: 30 | path: packages/abc 31 | key: abc-build-cache-${{ env.ABC_HASH }} 32 | 33 | - name: Build packages/abc 34 | run: | 35 | pnpm --filter=abc build 36 | ``` 37 | 38 | ## GitHub Actions: Configure `actions/cache` to Skip Cache Restoration on Re-runs 39 | 40 | To configure [`actions/cache`](https://github.com/actions/cache) to skip cache restoration on any re-runs of the workflow (to avoid having to manually delete flaky caches), use [an `if` conditional](https://docs.github.com/en/actions/using-jobs/using-conditions-to-control-job-execution) on the workflow step to check that [`github.run_attempt`](https://docs.github.com/en/actions/learn-github-actions/contexts#github-context:~:text=the%20workflow%20run.-,github.run_attempt,-string) is set to `1`, indicating that it is the first attempt to run the workflow: 41 | 42 | ```yaml 43 | name: Skip cache restoration on re-runs 44 | on: [push] 45 | jobs: 46 | build: 47 | runs-on: ubuntu-latest 48 | timeout-minutes: 10 49 | steps: 50 | - name: Checkout repository 51 | uses: actions/checkout@v4 52 | 53 | - name: Cache packages/abc 54 | # Only restore cache on first run attempt 55 | if: ${{ github.run_attempt == 1 }} 56 | uses: actions/cache@v4 57 | with: 58 | path: packages/abc 59 | key: abc-build-cache 60 | 61 | - name: Build packages/abc 62 | run: | 63 | pnpm --filter=abc build 64 | ``` 65 | 66 | ## GitHub Actions: Correct Broken `actions/setup-node` Version Resolution 67 | 68 | [Version resolution of Node.js aliases like `lts/*` in `actions/setup-node` is broken as of Aug 2024](https://github.com/actions/setup-node/issues/940#issuecomment-2029638604) (and will probably continue to be broken). 69 | 70 | To resolve this, switch off `actions/setup-node` and instead use the preinstalled `nvm` to install the correct Node.js version based on the alias: 71 | 72 | ```yaml 73 | # Use nvm because actions/setup-node does not install latest versions 74 | # https://github.com/actions/setup-node/issues/940 75 | - name: Install latest LTS with nvm 76 | run: | 77 | nvm install 'lts/*' 78 | echo "$(dirname $(nvm which node))" >> $GITHUB_PATH 79 | shell: bash -l {0} 80 | ``` 81 | 82 | If you also need caching for pnpm (replacement for the `cache` setting of `actions/setup-node`), follow with this config of [`actions/cache`](https://github.com/actions/cache): 83 | 84 | ```yaml 85 | - name: Get pnpm store directory 86 | shell: bash 87 | run: | 88 | echo "STORE_PATH=$(pnpm store path --silent)" >> $GITHUB_ENV 89 | - uses: actions/cache@v4 90 | name: Setup pnpm cache 91 | with: 92 | path: ${{ env.STORE_PATH }} 93 | key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }} 94 | restore-keys: | 95 | ${{ runner.os }}-pnpm-store- 96 | ``` 97 | 98 | ## GitHub Actions: Create PostgreSQL databases on Windows, macOS and Linux 99 | 100 | PostgreSQL databases can be created and used cross-platform on GitHub Actions, either by using the preinstalled PostgreSQL installation or installing PostgreSQL: 101 | 102 | - [`windows-latest` and `ubuntu-latest` runners](https://github.com/actions/runner-images#available-images) have PostgreSQL preinstalled 103 | - [`macos-latest` runners](https://github.com/actions/runner-images#available-images) [don't have PostgreSQL preinstalled (as of May 2024)](https://github.com/actions/runner-images/issues/9029#issuecomment-1856487621) 104 | 105 | To conditionally install PostgreSQL, initialize a cluster, create a user and database and start PostgreSQL cross-platform, use the following GitHub Actions workflow steps (change `database_name`, `username` and `password` to whatever you want): 106 | 107 | ```yaml 108 | name: CI 109 | on: push 110 | 111 | jobs: 112 | ci: 113 | name: CI 114 | runs-on: ${{ matrix.os }} 115 | strategy: 116 | matrix: 117 | os: [windows-latest, macos-latest, ubuntu-latest] 118 | timeout-minutes: 15 119 | env: 120 | PGHOST: localhost 121 | PGDATABASE: database_name 122 | PGUSERNAME: username 123 | PGPASSWORD: password 124 | steps: 125 | - name: Install PostgreSQL on macOS 126 | if: runner.os == 'macOS' 127 | run: | 128 | brew install postgresql@16 129 | brew link postgresql@16 130 | - name: Add PostgreSQL binaries to PATH 131 | shell: bash 132 | run: | 133 | if [ "$RUNNER_OS" == "Windows" ]; then 134 | echo "$PGBIN" >> $GITHUB_PATH 135 | elif [ "$RUNNER_OS" == "Linux" ]; then 136 | echo "$(pg_config --bindir)" >> $GITHUB_PATH 137 | fi 138 | - name: Start preinstalled PostgreSQL 139 | shell: bash 140 | run: | 141 | echo "Initializing database cluster..." 142 | 143 | # Convert backslashes to forward slashes in RUNNER_TEMP for Windows Git Bash 144 | export PGHOST="${RUNNER_TEMP//\\//}/postgres" 145 | export PGDATA="$PGHOST/pgdata" 146 | mkdir -p "$PGDATA" 147 | 148 | # initdb requires file for password in non-interactive mode 149 | export PWFILE="$RUNNER_TEMP/pwfile" 150 | echo "postgres" > "$PWFILE" 151 | initdb --pgdata="$PGDATA" --username="postgres" --pwfile="$PWFILE" 152 | 153 | echo "Starting PostgreSQL..." 154 | echo "unix_socket_directories = '$PGHOST'" >> "$PGDATA/postgresql.conf" 155 | pg_ctl start 156 | 157 | echo "Creating user..." 158 | psql --host "$PGHOST" --username="postgres" --dbname="postgres" --command="CREATE USER $PGUSERNAME PASSWORD '$PGPASSWORD'" --command="\du" 159 | 160 | echo "Creating database..." 161 | createdb --owner="$PGUSERNAME" --username="postgres" "$PGDATABASE" 162 | ``` 163 | 164 | Example PR: https://github.com/upleveled/preflight-test-project-next-js-passing/pull/152/ 165 | 166 | ## GitHub Actions: Create Release from `CHANGELOG.md` on New Tag 167 | 168 | Create a new GitHub Release with contents from `CHANGELOG.md` every time a new tag is pushed. 169 | 170 | **`.github/workflows/release.yml`** 171 | 172 | ```yml 173 | name: Release 174 | on: 175 | push: 176 | tags: 177 | - '*' 178 | permissions: 179 | contents: write 180 | jobs: 181 | release: 182 | name: Release On Tag 183 | if: startsWith(github.ref, 'refs/tags/') 184 | runs-on: ubuntu-latest 185 | steps: 186 | - name: Checkout the repository 187 | uses: actions/checkout@v4 188 | - name: Extract the changelog 189 | id: changelog 190 | run: | 191 | TAG_NAME=${GITHUB_REF/refs\/tags\//} 192 | READ_SECTION=false 193 | CHANGELOG_CONTENT="" 194 | while IFS= read -r line; do 195 | if [[ "$line" =~ ^#+\ +(.*) ]]; then 196 | if [[ "${BASH_REMATCH[1]}" == "$TAG_NAME" ]]; then 197 | READ_SECTION=true 198 | elif [[ "$READ_SECTION" == true ]]; then 199 | break 200 | fi 201 | elif [[ "$READ_SECTION" == true ]]; then 202 | CHANGELOG_CONTENT+="$line"$'\n' 203 | fi 204 | done < "CHANGELOG.md" 205 | CHANGELOG_CONTENT=$(echo "$CHANGELOG_CONTENT" | awk '/./ {$1=$1;print}') 206 | echo "changelog_content<> $GITHUB_OUTPUT 207 | echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT 208 | echo "EOF" >> $GITHUB_OUTPUT 209 | - name: Create the release 210 | if: steps.changelog.outputs.changelog_content != '' 211 | uses: softprops/action-gh-release@v1 212 | with: 213 | name: ${{ github.ref_name }} 214 | body: '${{ steps.changelog.outputs.changelog_content }}' 215 | draft: false 216 | prerelease: false 217 | ``` 218 | 219 | Credit: @edloidas in https://github.com/nanostores/nanostores/pull/267 220 | 221 | ## GitHub Actions: Edit `.json`, `.yml` and `.csv` Files Without Installing Anything 222 | 223 | `yq` (similar to `jq`) is preinstalled on GitHub Actions runners, which means you can edit a `.json`, `.yml` or `.csv` file very easily without installing any software. 224 | 225 | For example, the following workflow file would use `yq` to copy all `"resolutions"` to `"overrides"` in a `package.json` file (and then commit the result using `stefanzweifel/git-auto-commit-action`. 226 | 227 | **`.github/workflows/copy-resolutions-to-overrides.yml`** 228 | 229 | ```yml 230 | name: Copy Yarn Resolutions to npm Overrides 231 | 232 | on: 233 | push: 234 | branches: 235 | # Match every branch except for main 236 | - '**' 237 | - '!main' 238 | 239 | jobs: 240 | build: 241 | name: Copy Yarn Resolutions to npm Overrides 242 | runs-on: ubuntu-latest 243 | steps: 244 | - uses: actions/checkout@v3 245 | # To trigger further `on: [push]` workflow runs 246 | # Ref: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs 247 | # Ref: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#push-using-ssh-deploy-keys 248 | with: 249 | ssh-key: ${{ secrets.SSH_PRIVATE_KEY }} 250 | 251 | - name: Use Node.js 252 | uses: actions/setup-node@v4 253 | with: 254 | node-version: 'lts/*' 255 | 256 | - name: Copy "resolutions" object to "overrides" in package.json 257 | run: yq --inplace --output-format=json '.overrides = .resolutions' package.json 258 | 259 | - name: Install any updated dependencies 260 | run: npm install 261 | 262 | - uses: stefanzweifel/git-auto-commit-action@v4 263 | with: 264 | commit_message: Update Overrides from Resolutions 265 | ``` 266 | 267 | Or, to copy all `@types/*` and `typescript` packages from `devDependencies` to `dependencies` (eg. for a production build): 268 | 269 | ```bash 270 | yq --inplace --output-format=json '.dependencies = .dependencies * (.devDependencies | to_entries | map(select(.key | test("^(typescript|@types/*)"))) | from_entries)' package.json 271 | ``` 272 | 273 | ## GitHub Actions: Free Disk Space 274 | 275 | On GitHub Actions, [runners are only guaranteed 14GB of storage space (disk space)](https://github.com/actions/runner-images/issues/9344#issuecomment-1942811369) ([docs](https://docs.github.com/en/actions/using-github-hosted-runners/using-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories)), which can lead to the following errors if your workflow uses more than that: 276 | 277 | ``` 278 | System.IO.IOException: No space left on device 279 | ``` 280 | 281 | or 282 | 283 | ``` 284 | You are running out of disk space. The runner will stop working when the machine runs out of disk space. Free space left: 72 MB 285 | ``` 286 | 287 | OR 288 | 289 | ``` 290 | ENOSPC: no space left on device, write 291 | ``` 292 | 293 | To free up disk space for your workflow, use [the Free Disk Space (Ubuntu) action](https://github.com/marketplace/actions/free-disk-space-ubuntu) ([GitHub repo](https://github.com/jlumbroso/free-disk-space)): 294 | 295 | ```yaml 296 | - name: Free Disk Space (Ubuntu) 297 | uses: jlumbroso/free-disk-space@v1.3.1 298 | with: 299 | # Avoid slow clearing of large packages 300 | large-packages: false 301 | ``` 302 | 303 | You may need to disable some of the clearing options, if your workflow relies upon features or programs which are being removed: 304 | 305 | ```yaml 306 | - name: Free Disk Space (Ubuntu) 307 | uses: jlumbroso/free-disk-space@v1.3.1 308 | with: 309 | # Re-enable swap storage for processes which use more memory 310 | # than available and start using swap 311 | swap-storage: false 312 | 313 | # Avoid slow clearing of large packages 314 | large-packages: false 315 | ``` 316 | 317 | ## GitHub Actions: Only Run When Certain Files Changed 318 | 319 | Only run a GitHub Actions workflow when files matching a pattern have been changed, for example on an update to a pull request: 320 | 321 | ```yaml 322 | name: Fix Excalidraw SVG Fonts 323 | on: 324 | pull_request: 325 | paths: 326 | # All .excalidraw.svg files in any folder at any level inside `packages/content-items` 327 | - 'packages/content-items/**/*.excalidraw.svg' 328 | # All .excalidraw.svg files directly inside `packages/database/.readme/` 329 | - 'packages/database/.readme/*.excalidraw.svg' 330 | ``` 331 | 332 | For example, the following workflow uses `sed` to add default fonts to Excalidraw diagrams ([no longer needed](https://github.com/excalidraw/excalidraw/issues/4855#issuecomment-2259189107)): 333 | 334 | ```yaml 335 | # Workaround to fix fonts in Excalidraw SVGs 336 | # https://github.com/excalidraw/excalidraw/issues/4855#issuecomment-1513014554 337 | # 338 | # Temporary workaround until the following PR is merged: 339 | # https://github.com/excalidraw/excalidraw/pull/6479 340 | # 341 | # TODO: If the PR above is merged, this file can be removed 342 | name: Fix Excalidraw SVG Fonts 343 | on: 344 | pull_request: 345 | # Run only when Excalidraw SVG files are added or changed 346 | paths: 347 | - 'packages/content-items/contentItems/documentation/*.excalidraw.svg' 348 | - 'packages/database/.readme/*.excalidraw.svg' 349 | 350 | jobs: 351 | build: 352 | # Only run on Pull Requests within the same repository, and not from forks 353 | if: github.event.pull_request.head.repo.full_name == github.repository 354 | name: Fix Excalidraw SVG Fonts 355 | runs-on: ubuntu-latest 356 | timeout-minutes: 30 357 | steps: 358 | - name: Checkout Repo 359 | uses: actions/checkout@v4 360 | with: 361 | ref: ${{ github.head_ref }} 362 | 363 | - name: Fix fonts in Excalidraw SVGs 364 | run: | 365 | find packages/content-items/contentItems/documentation packages/database/.readme -type f -iname '*.excalidraw.svg' | while read file; do 366 | echo "Fixing fonts in $file" 367 | sed -i.bak 's/Virgil, Segoe UI Emoji/Virgil, '"'"'Comic Sans MS'"'"', '"'"'Segoe Print'"'"', '"'"'Bradley Hand'"'"', '"'"'Lucida Handwriting'"'"', '"'"'Marker Felt'"'"', cursive/g' "$file" 368 | sed -i.bak 's/Helvetica, Segoe UI Emoji/Helvetica, -apple-system,BlinkMacSystemFont, '"'"'Segoe UI'"'"', '"'"'Noto Sans'"'"', Helvetica, Arial, sans-serif, '"'"'Apple Color Emoji'"'"', '"'"'Segoe UI Emoji'"'"'/g' "$file" 369 | sed -i.bak 's/Cascadia, Segoe UI Emoji/Cascadia, ui-monospace, SFMono-Regular, '"'"'SF Mono'"'"', Menlo, Consolas, '"'"'Liberation Mono'"'"', monospace/g' "$file" 370 | rm "${file}.bak" 371 | done 372 | - name: Commit files 373 | run: | 374 | git config user.email github-actions[bot]@users.noreply.github.com 375 | git config user.name github-actions[bot] 376 | git add packages/content-items/contentItems/documentation/*.excalidraw.svg 377 | git add packages/database/.readme/*.excalidraw.svg 378 | if [ -z "$(git status --porcelain)" ]; then 379 | exit 0 380 | fi 381 | git commit -m "Fix fonts in Excalidraw SVGs" 382 | git push origin HEAD:${{ github.head_ref }} 383 | env: 384 | GITHUB_TOKEN: ${{ secrets.EXCALIDRAW_FONT_FIX_GITHUB_TOKEN }} 385 | ``` 386 | 387 | ## GitHub Actions: Push to Pull Request and Re-Run Workflows 388 | 389 | It can be useful to commit and push to a pull request in a GitHub Actions workflow, eg. an automated script that fixes something like [upgrading pnpm patch versions on automatic Renovate dependency upgrades](https://github.com/pnpm/pnpm/issues/5686#issuecomment-1669538653). 390 | 391 | Once your script makes the commit and pushes to the PR, it can also be useful to re-run the workflows on that commit, eg. linting the new commit, so that [GitHub auto-merge](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/incorporating-changes-from-a-pull-request/automatically-merging-a-pull-request) can run also on [protected branches with required status checks](https://docs.github.com/en/repositories/configuring-branches-and-merges-in-your-repository/managing-protected-branches/about-protected-branches#require-status-checks-before-merging). 392 | 393 | ‼️ WARNING: Make sure that you do not create a loop! Once your script creates a commit, the workflow will run your script again on the new commit. On the 2nd run, it **should not** create a new commit, or you will have an endless loop of GitHub Actions workflow runs. 😬 We will revisit this in the script below. 394 | 395 | 1. First, create a GitHub fine-grained personal access token: 396 | 1. Visit https://github.com/settings/personal-access-tokens/new 397 | 2. Fill out **Token name** (repository name + a short reminder), **Expiration** (longest available, 1 year), **Description** (a reminder of the purpose)

398 | Screenshot of GitHub fine-grained personal access token form, with the values Token name: 'archive-webpage-browser-extension push', Expiration: 'Custom - 1 year', Description: 'archive-webpage-browser-extension: Fix `pnpm patch` not upgrading patch versions automatically'

399 | 3. Under **Repository access**, choose **Only select repositories** and then choose the repository where the workflow will run

400 | Screenshot of GitHub fine-grained personal access token Repository Access form, showing the `karlhorky/archive-webpage-browser-extension` selected as the single repository with access

401 | 4. Under **Permissions**, expand **Repository permissions**, locate **Contents** and choose **Access: Read and write**

402 | Screenshot of GitHub fine-grained personal access token form, showing the Contents setting

403 | This will also by default set **Metadata** to **Access: Read-only**

404 | Screenshot of GitHub fine-grained personal access token form, showing the Metadata setting

405 | 5. Review your settings under **Overview** - it should be set to "2 permissions for 1 of your repositories" and "0 Account permissions"

406 | Screenshot of GitHub fine-grained personal access token form, showing the Overview details

407 | 6. If you are satisfied with this, click on **Generate token**. 408 | 7. This will show you the token on your screen only once, so be careful to copy the token. 409 | 2. Add a repository secret 410 | 1. In the repository where the workflow will run, visit **Settings** -> **Secrets and variables** -> **Actions** and click on **New repository secret**

411 | Screenshot of GitHub repository settings page for Actions secrets and variables

412 | 2. For **Name**, enter a `SCREAMING_SNAKE_CASE` name that makes it clear that it's a GitHub token (eg. `PNPM_PATCH_UPDATE_GITHUB_TOKEN`) and for **Secret** paste in the token that you copied at the end of step 1 above. Click **Add secret**.

413 | Screenshot of GitHub repository settings page for Actions secrets and variables

414 | 3. Adjust your workflow for the token 415 | 416 | 1. Under `uses: actions/checkout`, add a `with:` block including `persist-credentials: false` 417 | ```yaml 418 | - uses: actions/checkout@v4 419 | with: 420 | # Disable configuring $GITHUB_TOKEN in local git config 421 | persist-credentials: false 422 | ``` 423 | 2. ‼️ IMPORTANT: As mentioned above, make sure that you don't create a loop! Make sure that your script which alters files includes some kind of way to exit early, for example checking `git status --porcelain` and running `exit 0` to exit the script early without errors or [by skipping steps based on the last commits](https://joht.github.io/johtizen/build/2022/01/20/github-actions-push-into-repository.html#skip-push-on-auto-commit) 424 | 425 | ```yaml 426 | - name: Fix `pnpm patch` not upgrading patch versions automatically 427 | run: | 428 | # 429 | 430 | git add package.json pnpm-lock.yaml patches 431 | if [ -z "$(git status --porcelain)" ]; then 432 | echo "No changes to commit, exiting" 433 | exit 0 434 | fi 435 | 436 | # 437 | # 438 | ``` 439 | 440 | 3. Set your Git `user.email` and `user.name` credentials to the GitHub Actions bot and commit: 441 | 442 | ```yaml 443 | - name: Fix `pnpm patch` not upgrading patch versions automatically 444 | run: | 445 | # 446 | # 447 | 448 | git config user.email github-actions[bot]@users.noreply.github.com 449 | git config user.name github-actions[bot] 450 | 451 | git commit -m "Upgrade versions for \`pnpm patch\`" 452 | 453 | # 454 | ``` 455 | 456 | 4. Use the token via `secrets.` in your Git origin in your workflow (credit: [`ad-m/github-push-action`](https://github.com/ad-m/github-push-action/blob/d91a481090679876dfc4178fef17f286781251df/start.sh#L43-L55)) 457 | 458 | ```yaml 459 | - name: Fix `pnpm patch` not upgrading patch versions automatically 460 | run: | 461 | # 462 | # 463 | # 464 | 465 | # Credit for oauth2 syntax is the ad-m/github-push-action GitHub Action: 466 | # https://github.com/ad-m/github-push-action/blob/d91a481090679876dfc4178fef17f286781251df/start.sh#L43-L5 467 | git push https://oauth2:${{ secrets.PNPM_PATCH_UPDATE_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref }} 468 | ``` 469 | 470 | ### Example 471 | 472 | ```yaml 473 | name: Fix pnpm patches 474 | on: push 475 | 476 | jobs: 477 | fix-pnpm-patches: 478 | name: Fix pnpm patches 479 | runs-on: ubuntu-latest 480 | steps: 481 | - uses: actions/checkout@v4 482 | with: 483 | # Disable configuring $GITHUB_TOKEN in local git config 484 | persist-credentials: false 485 | - uses: pnpm/action-setup@v3 486 | with: 487 | version: 'latest' 488 | - uses: actions/setup-node@v4 489 | with: 490 | node-version: 'lts/*' 491 | cache: 'pnpm' 492 | 493 | # Fix `pnpm patch` not upgrading patch versions automatically 494 | # https://github.com/pnpm/pnpm/issues/5686#issuecomment-1669538653 495 | - name: Fix `pnpm patch` not upgrading patch versions automatically 496 | run: | 497 | # Exit if no patches/ directory in root 498 | if [ ! -d patches ]; then 499 | echo "No patches/ directory found in root" 500 | exit 0 501 | fi 502 | 503 | ./scripts/fix-pnpm-patches.sh 504 | 505 | git add package.json pnpm-lock.yaml patches 506 | if [ -z "$(git status --porcelain)" ]; then 507 | echo "No changes to commit, exiting" 508 | exit 0 509 | fi 510 | 511 | git config user.email github-actions[bot]@users.noreply.github.com 512 | git config user.name github-actions[bot] 513 | 514 | git commit -m "Upgrade versions for \`pnpm patch\`" 515 | 516 | # Credit for oauth2 syntax is the ad-m/github-push-action GitHub Action: 517 | # https://github.com/ad-m/github-push-action/blob/d91a481090679876dfc4178fef17f286781251df/start.sh#L43-L55 518 | git push https://oauth2:${{ secrets.PNPM_PATCH_UPDATE_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref }} 519 | ``` 520 | 521 | Pull request with automatic PR commit including workflow checks: https://github.com/karlhorky/archive-webpage-browser-extension/pull/47 522 | 523 |
524 | 525 |
526 | 527 | Screenshot of a GitHub PR showing two commits, the first by Renovate bot upgrading dependencies and the second by the automated script shown above. Both commits have status icons to the right of them (one red X and one green checkmark), indicating that the workflows have run on both of them. At the bottom, the PR auto-merge added by the Renovate bot is carried out because the last commit has a green checkmark. 528 | 529 |
530 |
531 |
532 |
533 | 534 | ## GitHub Actions: Skip step if PR branch deleted 535 | 536 | If commands in a GitHub Actions step rely on existence of the PR branch and the PR branch has been deleted (for example, automatically after the PR is merged), then workflow runs will fail with `couldn't find remote ref` errors: 537 | 538 | ```bash 539 | git fetch origin "${{ github.ref }}" # Git command in the workflow step relies upon existence of PR branch 540 | fatal: couldn't find remote ref allow-sharp-build-explicitly 541 | Error: Process completed with exit code 128. 542 | ``` 543 | 544 | To avoid these failing workflow runs, skip the rest of the step by exiting early with a `0` exit code if `git ls-remote` for `github.ref` errors: 545 | 546 | ```yaml 547 | name: CI 548 | on: [push] 549 | jobs: 550 | ci: 551 | name: CI 552 | runs-on: ubuntu-latest 553 | timeout-minutes: 30 554 | steps: 555 | - uses: actions/checkout@v5 556 | - run: | 557 | git ls-remote --exit-code origin "${{ github.ref }}" >/dev/null || { echo 'Upstream branch missing on origin, skipping...'; exit 0; } 558 | 559 | # Would fail without the skip command above 560 | git fetch origin "${{ github.ref }}" 561 | ``` 562 | 563 | ## GitHub Flavored Markdown Formatted Table Width 564 | 565 | Use ` ` entities to give a table column a width: 566 | 567 | ```markdown 568 | | property                                                            | description | 569 | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | 570 | | `border-bottom-right-radius` | Defines the shape of the bottom-right | 571 | ``` 572 | 573 | Demo: 574 | 575 | | property                                                            | description | 576 | | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- | 577 | | `border-bottom-right-radius` | Defines the shape of the bottom-right | 578 | 579 | ## GitHub Flavored Markdown Linking to Anchors in Other Markdown Files 580 | 581 | Linking to an anchor in a relative Markdown file path in the same repo (eg. `./windows.md#user-content-xxx`) doesn't currently work on GitHub (Mar 2023). Probably another bug in GitHub's client-side router, maybe fixed sometime. 582 | 583 | A workaround is to link to the full GitHub URL with a `www.` subdomain - this will cause a redirect to the non-`www.` version, and scroll to the anchor: 584 | 585 | ```diff 586 | -[Expo + React Native](./windows.md#user-content-expo-react-native) 587 | +[Expo + React Native](https://www.github.com/upleveled/system-setup/blob/main/windows.md#user-content-expo-react-native) 588 | ``` 589 | 590 | ## README Symlinks 591 | 592 | When in a particular folder (such as the root directory), GitHub displays content from README files underneath the files in that folder: 593 | 594 | Screenshot showing readme content below file list 595 | 596 | However, these README files need to be named `README.md`, `readme.md`, `README.mdx` or `readme.mdx` in order to be recognized. GitHub doesn't display the content of certain common Markdown index filenames such as `index.md` or `index.mdx` ([❓MDX file extension](https://github.com/mdx-js/mdx)) ([as of 18 June 2019](https://twitter.com/karlhorky/status/1140962752858677249)). 597 | 598 | GitHub does however follow symlinks named `README.md`, `readme.md`, `README.mdx` and `readme.mdx`. See example here: [mdx-deck root folder](https://github.com/jxnblk/mdx-deck), [mdx-deck symlink README.md](https://github.com/jxnblk/mdx-deck/blob/master/README.md) 599 | 600 | So if you want to use another file (also in a subdirectory) as the contents displayed within a directory, create a symlink pointing at it: 601 | 602 | ```sh 603 | ln -s index.md README.md 604 | ``` 605 | 606 | ### Shell Script: Create README Symlinks 607 | 608 | If you have many directories with `index.mdx` files that you want to display as the readme content when you enter those directories on the web version of GitHub, you can run this script in the containing directory. 609 | 610 | ```sh 611 | # Create symlinks for all directories within the current directory that 612 | # do not yet have README.mdx files. 613 | find . -mindepth 1 -maxdepth 1 -type d '!' -exec test -e "{}/README.mdx" ';' -print0 | while IFS= read -r -d $'\0' line; do 614 | cd $line && ln -s index.mdx README.mdx && cd .. 615 | done 616 | ``` 617 | 618 | ## Refined GitHub: Sticky Comment Headers 619 | 620 | Keeping the context and actions from the comment header in view can be especially useful for long comments: 621 | 622 | https://github.com/user-attachments/assets/0a38d7e2-d16c-4d52-b250-c51a050af501 623 | 624 | First, install and set up the [Refined GitHub](https://github.com/refined-github/refined-github) browser extension. In the Refined GitHub options under Custom CSS, add the following CSS from [@SunsetTechuila](https://github.com/SunsetTechuila)'s closed PR https://github.com/refined-github/refined-github/pull/8544 625 | 626 | ```css 627 | /* https://github.com/refined-github/refined-github/issues/8463#issuecomment-3094626175 */ 628 | 629 | /* Issue body header */ 630 | [class^="IssueBodyHeader-module__IssueBodyHeaderContainer"], 631 | /* Issue comment header */ 632 | [data-testid='comment-header'], 633 | /* PR body/comment header */ 634 | .timeline-comment-header { 635 | position: sticky; 636 | top: calc(var(--base-sticky-header-height, 0px) + 56px); 637 | z-index: 3; 638 | background: var(--bgColor-muted, var(--color-canvas-subtle)) !important; 639 | } 640 | 641 | [id^="gistcomment"] .timeline-comment-header { 642 | top: 0; 643 | } 644 | 645 | /* Show menus on top of headers */ 646 | .js-comment-container:has(details[open]) .timeline-comment-header { 647 | z-index: 4; 648 | } 649 | 650 | [class^="IssueBodyHeader-module__IssueBodyHeaderContainer"], 651 | [data-testid="comment-header"] { 652 | border-radius: 0; 653 | } 654 | 655 | /* Issue body container */ 656 | [data-testid="issue-body"] [class^="Box"]:has(> #issue-body-viewer), 657 | /* Issue comment container */ 658 | [class^="IssueCommentViewer-module__IssueCommentContent"] { 659 | border-radius: var(--borderRadius-medium); 660 | overflow: clip; 661 | } 662 | 663 | @supports (anchor-name: --test) { 664 | [app-name="issues-react"] { 665 | [class*="ActivityHeader-module__activityHeader"] 666 | button[aria-haspopup="true"][aria-expanded="true"] { 667 | anchor-name: --header; 668 | } 669 | 670 | [class^="prc-Overlay-Overlay"][data-variant="anchored"]:has( 671 | .octicon-quote, 672 | [class*="scrollableEditHistoryList"] 673 | ) { 674 | position: fixed !important; 675 | position-anchor: --header; 676 | top: anchor(bottom) !important; 677 | } 678 | } 679 | } 680 | ``` 681 | --------------------------------------------------------------------------------