A collection of useful GitHub tricks
Create a new GitHub Release with contents from CHANGELOG.md
every time a new tag is pushed.
.github/workflows/release.yml
name: Release
on:
push:
tags:
- '*'
permissions:
contents: write
jobs:
release:
name: Release On Tag
if: startsWith(github.ref, 'refs/tags/')
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4
- name: Extract the changelog
id: changelog
run: |
TAG_NAME=${GITHUB_REF/refs\/tags\//}
READ_SECTION=false
CHANGELOG_CONTENT=""
while IFS= read -r line; do
if [[ "$line" =~ ^#+\ +(.*) ]]; then
if [[ "${BASH_REMATCH[1]}" == "$TAG_NAME" ]]; then
READ_SECTION=true
elif [[ "$READ_SECTION" == true ]]; then
break
fi
elif [[ "$READ_SECTION" == true ]]; then
CHANGELOG_CONTENT+="$line"$'\n'
fi
done < "CHANGELOG.md"
CHANGELOG_CONTENT=$(echo "$CHANGELOG_CONTENT" | awk '/./ {$1=$1;print}')
echo "changelog_content<<EOF" >> $GITHUB_OUTPUT
echo "$CHANGELOG_CONTENT" >> $GITHUB_OUTPUT
echo "EOF" >> $GITHUB_OUTPUT
- name: Create the release
if: steps.changelog.outputs.changelog_content != ''
uses: softprops/action-gh-release@v1
with:
name: ${{ github.ref_name }}
body: '${{ steps.changelog.outputs.changelog_content }}'
draft: false
prerelease: false
Credit: @edloidas in nanostores/nanostores#267
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.
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
.
.github/workflows/copy-resolutions-to-overrides.yml
name: Copy Yarn Resolutions to npm Overrides
on:
push:
branches:
# Match every branch except for main
- '**'
- '!main'
jobs:
build:
name: Copy Yarn Resolutions to npm Overrides
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
# To trigger further `on: [push]` workflow runs
# Ref: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#triggering-further-workflow-runs
# Ref: https://github.com/peter-evans/create-pull-request/blob/main/docs/concepts-guidelines.md#push-using-ssh-deploy-keys
with:
ssh-key: ${{ secrets.SSH_PRIVATE_KEY }}
- name: Use Node.js
uses: actions/setup-node@v3
with:
node-version: '16'
- name: Copy "resolutions" object to "overrides" in package.json
run: yq --inplace --output-format=json '.overrides = .resolutions' package.json
- name: Install any updated dependencies
run: npm install
- uses: stefanzweifel/git-auto-commit-action@v4
with:
commit_message: Update Overrides from Resolutions
Or, to copy all @types/*
and typescript
packages from devDependencies
to dependencies
(eg. for a production build):
yq --inplace --output-format=json '.dependencies = .dependencies * (.devDependencies | to_entries | map(select(.key | test("^(typescript|@types/*)"))) | from_entries)' package.json
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.
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 can run also on protected branches with required status checks.
-
First, create a GitHub fine-grained personal access token:
- Visit https://github.com/settings/personal-access-tokens/new
- Fill out Token name (repository name + a short reminder), Expiration (longest available, 1 year), Description (a reminder of the purpose)
- Under Repository access, choose Only select repositories and then choose the repository where the workflow will run
- Under Permissions, expand Repository permissions, locate Contents and choose Access: Read and write
This will also by default set Metadata to Access: Read-only
- Review your settings under Overview - it should be set to "2 permissions for 1 of your repositories" and "0 Account permissions"
- If you are satisfied with this, click on Generate token.
- This will show you the token on your screen only once, so be careful to copy the token.
-
Add a repository secret
- In the repository where the workflow will run, visit Settings -> Secrets and variables -> Actions and click on New repository secret
- 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.
- In the repository where the workflow will run, visit Settings -> Secrets and variables -> Actions and click on New repository secret
-
Adjust your workflow for the token
-
Under
uses: actions/checkout
, add awith:
block includingpersist-credentials: false
- uses: actions/checkout@v4 with: # Disable configuring $GITHUB_TOKEN in local git config persist-credentials: false
-
‼️ 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 checkinggit status --porcelain
and runningexit 0
to exit the script early without errors or by skipping steps based on the last commits- name: Fix `pnpm patch` not upgrading patch versions automatically run: | # <your script makes changes here> git add package.json pnpm-lock.yaml patches if [ -z "$(git status --porcelain)" ]; then echo "No changes to commit, exiting" exit 0 fi # <your script sets Git credentials and commits here> # <your script pushes here>
-
Set your Git
user.email
anduser.name
credentials to the GitHub Actions bot and commit:- name: Fix `pnpm patch` not upgrading patch versions automatically run: | # <your script makes changes here> # <your script exits early here> git config user.email github-actions[bot]@users.noreply.github.com git config user.name github-actions[bot] git commit -m "Upgrade versions for \`pnpm patch\`" # <your script pushes here>
-
Use the token via
secrets.<name>
in your Git origin in your workflow (credit:ad-m/github-push-action
)- name: Fix `pnpm patch` not upgrading patch versions automatically run: | # <your script makes changes here> # <your script exits early here> # <your script sets Git credentials and commits here> # Credit for oauth2 syntax is the ad-m/github-push-action GitHub Action: # https://github.com/ad-m/github-push-action/blob/d91a481090679876dfc4178fef17f286781251df/start.sh#L43-L5 git push https://oauth2:${{ secrets.PNPM_PATCH_UPDATE_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref }}
-
name: Fix pnpm patches
on: push
jobs:
fix-pnpm-patches:
name: Fix pnpm patches
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
# Disable configuring $GITHUB_TOKEN in local git config
persist-credentials: false
- uses: pnpm/action-setup@v3
with:
version: 'latest'
- uses: actions/setup-node@v4
with:
node-version: 'lts/*'
cache: 'pnpm'
# Fix `pnpm patch` not upgrading patch versions automatically
# https://github.com/pnpm/pnpm/issues/5686#issuecomment-1669538653
- name: Fix `pnpm patch` not upgrading patch versions automatically
run: |
# Exit if no patches/ directory in root
if [ ! -d patches ]; then
echo "No patches/ directory found in root"
exit 0
fi
./scripts/fix-pnpm-patches.sh
git add package.json pnpm-lock.yaml patches
if [ -z "$(git status --porcelain)" ]; then
echo "No changes to commit, exiting"
exit 0
fi
git config user.email github-actions[bot]@users.noreply.github.com
git config user.name github-actions[bot]
git commit -m "Upgrade versions for \`pnpm patch\`"
# Credit for oauth2 syntax is the ad-m/github-push-action GitHub Action:
# https://github.com/ad-m/github-push-action/blob/d91a481090679876dfc4178fef17f286781251df/start.sh#L43-L55
git push https://oauth2:${{ secrets.PNPM_PATCH_UPDATE_GITHUB_TOKEN }}@github.com/${{ github.repository }}.git HEAD:${{ github.ref }}
Pull request with automatic PR commit including workflow checks: karlhorky/archive-webpage-browser-extension#47
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.The windows-latest
, macos-latest
and ubuntu-latest
runners have PostgreSQL preinstalled.
To 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):
name: CI
on: push
jobs:
ci:
name: CI
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [windows-latest, macos-latest, ubuntu-latest]
timeout-minutes: 15
env:
PGHOST: localhost
PGDATABASE: database_name
PGUSERNAME: username
PGPASSWORD: password
steps:
- name: Add PostgreSQL binaries to PATH
shell: bash
run: |
if [ "$RUNNER_OS" == "Windows" ]; then
echo "$PGBIN" >> $GITHUB_PATH
elif [ "$RUNNER_OS" == "Linux" ]; then
echo "$(pg_config --bindir)" >> $GITHUB_PATH
fi
- name: Start preinstalled PostgreSQL
shell: bash
run: |
echo "Initializing database cluster..."
# Convert backslashes to forward slashes in RUNNER_TEMP for Windows Git Bash
export PGHOST="${RUNNER_TEMP//\\//}/postgres"
export PGDATA="$PGHOST/pgdata"
mkdir -p "$PGDATA"
# initdb requires file for password in non-interactive mode
export PWFILE="$RUNNER_TEMP/pwfile"
echo "postgres" > "$PWFILE"
initdb --pgdata="$PGDATA" --username="postgres" --pwfile="$PWFILE"
echo "Starting PostgreSQL..."
echo "unix_socket_directories = '$PGHOST'" >> "$PGDATA/postgresql.conf"
pg_ctl start
echo "Creating user..."
psql --host "$PGHOST" --username="postgres" --dbname="postgres" --command="CREATE USER $PGUSERNAME PASSWORD '$PGPASSWORD'" --command="\du"
echo "Creating database..."
createdb --owner="$PGUSERNAME" --username="postgres" "$PGDATABASE"
Example PR: upleveled/preflight-test-project-next-js-passing#152
Use
entities to give a table column a width:
| property | description |
| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------- |
| `border-bottom-right-radius` | Defines the shape of the bottom-right |
Demo:
property | description |
---|---|
border-bottom-right-radius |
Defines the shape of the bottom-right |
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.
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:
-[Expo + React Native](./windows.md#user-content-expo-react-native)
+[Expo + React Native](https://www.github.com/upleveled/system-setup/blob/main/windows.md#user-content-expo-react-native)
When in a particular folder (such as the root directory), GitHub displays content from README files underneath the files in that folder:
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) (as of 18 June 2019).
GitHub does however follow symlinks named README.md
, readme.md
, README.mdx
and readme.mdx
. See example here: mdx-deck root folder, mdx-deck symlink README.md
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:
ln -s index.md README.md
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.
# Create symlinks for all directories within the current directory that
# do not yet have README.mdx files.
find . -mindepth 1 -maxdepth 1 -type d '!' -exec test -e "{}/README.mdx" ';' -print0 | while IFS= read -r -d $'\0' line; do
cd $line && ln -s index.mdx README.mdx && cd ..
done