March 30, 2020
8 minutes
Publish Your Flutter/Dart Package using GitHub Actions
Howdy people! Recently I was exploring the internet to get some guidance on publishing Flutter or Dart packages to pub.dev via CI-CD servers. My primary goal was to use GitHub Actions to do so. Unfortunately π, I failed to get proper information on how can I achieve that. Then I decided to go by myself and I struggled a lot to get it working properly. So, I decided to write this easy and step-by-step guide to help others like me out there!
Here’s what we’re trying to achieve: We have a Flutter/Dart package that we want to publish on pub.dev whenever we create a new release on GitHub or push code to the master branch. Here’s what we’re going to need.
Requirements
- A Flutter package ready to be published.
- An active account on pub.dev
- A repository on GitHub with admin access.
- A working laptop, internet connection, and some patience!
Here’s what we are going to do in order to achieve what we want.
Steps
- Retrieve Login Credentials for pub.dev
- Create Secrets on GitHub
- Write a shell script to set credentials
- Create a workflow for GitHub Action to publish a package.
- Triggering Builds.
I did some research on how pub publish
command works. When we run this command in our terminal for the first time, pub
would ask us to log in to our pub.dev account by opening the URL printed on the command line. Once we log in using that URL, pub
store some login credentials (which happens to be some tokens) into a file called credentials.json
.
Next time when you run pub publish
command, it checks for this file and proceeds further without asking for login again. This gave me a hint that to make it work on CI-CD servers, we need to create this file somehow on the build machine.
Follow this easy guide to setup your GitHub actions to publish your packages.
1. Retrieve Login Credentials for pub.dev
Alright, we need some credentials to log in to pub.dev via the command line. Here’s what it looks like:
{
"accessToken": "<YOUR_ACCESS_TOKEN>",
"refreshToken": "<YOUR_REFRESH_TOKEN>",
"tokenEndpoint": "https://accounts.google.com/o/oauth2/token",
"scopes": [
"openid",
"https://www.googleapis.com/auth/userinfo.email"
],
"expiration": 1583046238465
}
If you have already published your package before, you can easily find the required credentials for pub.dev as you already have logged into your account. You can locate the file at the following path:
Linux/Mac-OS
~/.pub-cache/credentials.json
Windows
C:\Users\<USER>\AppData\Roaming\Pub\Cache\credentials.json
But if you’re doing this for the first time and you haven’t made any releases of your package on pub.dev, these credentials won’t be there!
Here’s a workaround to get these credentials.
Run the following command:
Note: Run this command only if this is your first release. This is just a trick to get what we need. This command will fail eventually after you login successfully but before that, it will create the credentials file.
flutter pub uploader add <YOUR_EMAIL_HERE>
This will ask you to log in to your pub.dev account. Do so by opening the link provided in the terminal. Once you log in successfully, you will be able to locate the credentials.json
file. Keep this file handy. We’ll need it in the next step.
2. Create Secrets on GitHub
Now, that we have login credentials, we’ll need it in our GitHub Action. We could use these tokens directly into our workflow file but it’s a Bad Idea to expose sensitive information like this in your repository. So, we’re going to use GitHub Secrets to store this information on a repository level. That way, it is only accessible by the admin of the repository and we can access those secrets in our workflow file.
Setting secrets for your repository is quite easy. Head over to GitHub and open your repository settings (assuming you already have your repository set up). There, you’ll find the secrets section.
Click on Add a new secret
button to add a secret. Here’s all the secrets that we’re going to need.
Secrets
- PUB_DEV_PUBLISH_ACCESS_TOKEN
- PUB_DEV_PUBLISH_REFRESH_TOKEN
- PUB_DEV_PUBLISH_TOKEN_ENDPOINT
- PUB_DEV_PUBLISH_EXPIRATION
Use these exact same names to create your secrets because these names will be used in our workflow file as well. If you wish to change the names, then make sure you use the same names everywhere.
The names self-explanatory to indicate which information from the credentials.json
file to set for each secret.
3. Write a shell script to set credentials
Alright! Now that we have set up secrets on GitHub, we can use them in our workflow file. But before that, we need to create credentials.json
file on the CI server in order for pub to log in. We’re going to do this using a shell script.
Now, we could do this by writing the whole script in our workflow file itself but that is kind of messy. So, we’re going to keep that shell script in a file in our repository and we’ll invoke it from our workflow file. This way you can reuse the script for other projects and it will keep your workflow file clean.
Create a file in the root of your repository with the name pub_login.sh
. Here’s the script that we’re going to put in that file.
# This script creates/updates credentials.json file which is used
# to authorize publisher when publishing packages to pub.dev
# Checking whether the secrets are available as environment
# variables or not.
if [ -z "${PUB_DEV_PUBLISH_ACCESS_TOKEN}" ]; then
echo "Missing PUB_DEV_PUBLISH_ACCESS_TOKEN environment variable"
exit 1
fi
if [ -z "${PUB_DEV_PUBLISH_REFRESH_TOKEN}" ]; then
echo "Missing PUB_DEV_PUBLISH_REFRESH_TOKEN environment variable"
exit 1
fi
if [ -z "${PUB_DEV_PUBLISH_TOKEN_ENDPOINT}" ]; then
echo "Missing PUB_DEV_PUBLISH_TOKEN_ENDPOINT environment variable"
exit 1
fi
if [ -z "${PUB_DEV_PUBLISH_EXPIRATION}" ]; then
echo "Missing PUB_DEV_PUBLISH_EXPIRATION environment variable"
exit 1
fi
# Create credentials.json file.
cat <<EOF > ~/.pub-cache/credentials.json
{
"accessToken":"${PUB_DEV_PUBLISH_ACCESS_TOKEN}",
"refreshToken":"${PUB_DEV_PUBLISH_REFRESH_TOKEN}",
"tokenEndpoint":"${PUB_DEV_PUBLISH_TOKEN_ENDPOINT}",
"scopes":["https://www.googleapis.com/auth/userinfo.email","openid"],
"expiration":${PUB_DEV_PUBLISH_EXPIRATION}
}
EOF
The if
conditions in the script check whether the secrets are available as environment variables or not and raises an error if not. The cat
command finally creates and writes credentials.json
at ~/.pub-cache/credentials.json
. We are going to use the ubuntu image for our workflow so that explains the path for credentials file.
Ideally, you don’t need to make any changes to this script and you can reuse it directly for any other Flutter/Dart projects. But If you chose to use different names for secrets earlier then, you’ll have to use those same names here.
Commit this file to your repository and push it to the master branch. We’ll need this file when we run our workflow for GitHub actions.
4. Create a workflow for GitHub Action to publish a package
Finally, we now need to create a workflow for our publishing process. Open your repository and create this file .github\workflows\publish.yml
.
Here’s the workflow for your dart package:
Dart workflow
name: Publish Package
on:
release: [published]
jobs:
build:
runs-on: ubuntu-latest
container:
image: google/dart:latest
steps:
- uses: actions/checkout@v2
- name: Install dependencies
run: pub get
- name: Run tests
run: pub run test
- name: Setup Pub Credentials
shell: bash
env:
PUB_DEV_PUBLISH_ACCESS_TOKEN: ${{ secrets.PUB_DEV_PUBLISH_ACCESS_TOKEN }}
PUB_DEV_PUBLISH_REFRESH_TOKEN: ${{ secrets.PUB_DEV_PUBLISH_REFRESH_TOKEN }}
PUB_DEV_PUBLISH_TOKEN_ENDPOINT: ${{ secrets.PUB_DEV_PUBLISH_TOKEN_ENDPOINT }}
PUB_DEV_PUBLISH_EXPIRATION: ${{ secrets.PUB_DEV_PUBLISH_EXPIRATION }}
run: |
sh ./pub_login.sh
- name: Check Publish Warnings
run: pub publish --dry-run
- name: Publish Package
run: pub publish -f
Use this workflow only if your package is a Dart package, not a Flutter package. For Flutter packages, use the following workflow:
Flutter workflow
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v1
- name: Install Flutter
uses: subosito/flutter-action@v1
with:
flutter-version: '1.9.1+hotfix.6'
- name: Install dependencies
run: flutter pub get
- name: Analyze
run: flutter analyze
- name: Run tests
run: flutter test
- name: Setup Pub Credentials
shell: bash
env:
PUB_DEV_PUBLISH_ACCESS_TOKEN: ${{ secrets.PUB_DEV_PUBLISH_ACCESS_TOKEN }}
PUB_DEV_PUBLISH_REFRESH_TOKEN: ${{ secrets.PUB_DEV_PUBLISH_REFRESH_TOKEN }}
PUB_DEV_PUBLISH_TOKEN_ENDPOINT: ${{ secrets.PUB_DEV_PUBLISH_TOKEN_ENDPOINT }}
PUB_DEV_PUBLISH_EXPIRATION: ${{ secrets.PUB_DEV_PUBLISH_EXPIRATION }}
run: |
sh ./pub_login.sh
- name: Check Publish Warnings
run: pub publish --dry-run
- name: Publish Package
run: pub publish -f
Let’s talk about the common part first.
on:
defines when to run the workflow. I want my workflow to run when I create a new release on GitHub. You can set it according to your requirements. Here’s an example:
on:
pull_request:
branches:
- master
push:
branches:
- master
- We’re using
ubuntu-latest
as OS for our CI server. It makes it easy to run shell scripts. - Notice the
Setup Pub Credentials
step which sets environment variables for our little shell script and then runs our script stored inpub_login.sh
file. Check Publish Warnings
step ensures that there’s no error publishing your package by runningpub publish --dry-run
command.Publish Package
step will publish your package to pub.dev
Dart Workflow:
google/dart:latest
container image will provide us with the lastest dart installation.- For a dart package, we use
pub
commands to install dependencies and run tests before proceeding for the publishing process.
Flutter Workflow:
- Step
Install Flutter
will install Flutter SDK with givenflutter-version
. Feel free to use the latest version or the one that suits you. - For a Flutter package, we’ll use
flutter
commands instead ofpub
commands to install dependencies, run tests and, analyze the source code.
Save your workflow file and push it to the master branch on your GitHub repository.
5. Triggering Builds
That’s it! All the setup is done. Now all you need to do is trigger a build. Triggering a build depends on how did you set up the workflow like on pull_request
, on push
or on release
. Once that event happens, it will trigger a build and if everything goes rightπ€, your package will be published to pub.dev.π
Thanks for reading! If you liked what you read or learned, don’t forget to share it! If you have any questions, feel free to drop a message. Happy coding folks!π