— TypeScript, GitHub Packages, GitHub Actions — 3 min read
Packages are a great way to share common code between repositories, and GitHub packages is a convenient way to do this. There's unlimited usage for public repos and generous allowances for private repos, even with a free account. Better yet, you get the benefit of keeping all your code in one place.
In this tutorial, we'll go over how to:
You can preview the completed deployed package here.
First, we'll need something to deploy. For that we're going to create our very own nodash package: lodash's less useful younger brother.
The library will be simple: a single file, index.ts
, exporting a single function, mean
.
1/**2 * Rewrite of lodash's `mean` function with correct output3 */4export const mean = (): string => "I don't like your face";
We'll build our package using tsc
and a minimal tsconfig. Our code will be built into the ./dist
folder and will ship with type declarations for anyone using our module in a TypeScript app.
1{2 "compilerOptions": {3 "target": "es2015",4 "module": "commonjs",5 "strict": true,6 "outDir": "./dist", // build into ./dist7 "declaration": true // create type declarations8 },9 "include": [10 "src/**/*"11 ]12}
Before our package is publishable we'll need to make a few important changes to our package.json.
1{2 "name": "@jonathan-wilkinson/nodash",3 "version": "1.0.0",4 "main": "./dist/index.js",5 "scripts": {6 "build": "tsc"7 },8 "dependencies": {9 "typescript": "^3.9.6"10 },11 "repository": {12 "type": "git",13 "url": "git+https://github.com/jonathan-wilkinson/nodash.git"14 },15 "publishConfig": {16 "registry":"https://npm.pkg.github.com/",17 "access": "public" // Makes the package public!!!18 }19}
GitHub only allows scoped packages. Scopes are a way to group packages by indvidual or organisation and are named @<scope>/<package>
. When using GitHub Packages your scope will be the name of the GitHub account that owns the repository you're publishing to.
My account is jonathan-wilkinson
and I'm publishing my nodash
package, so my package name will be @jonathan-wilkinson/nodash
.
The main
property tells NPM which file is the entrypoint to our package. This is the file that will be loaded when someone imports/requires our module.
We'll be building our code (index.ts
) into the dist
, so our main
will be dist/index.js
.
GitHub needs the repository
field to know which repo to publish a package to. Depending on how you first set up your package, this is probably filled in. If it's not, you'll have to add this information before you'll be able to publish.
This field tells NPM which registry to publish our package to. As we're using GitHub's registry, this will be set to "https://npm.pkg.github.com/"
Scoped packages are private by default. If we want to make our package public, we need to set publishConfig.access
to "public"
. To keep your package private, you can either remove this or set it to "restricted"
.
Now we've got our package ready, we'll go over how to publish it manually or with GitHub Actions.
Before we can publish we need to set-up NPM to authenticate with GitHub. To do this we need to generate a Personal Access Token in GitHub with permissions:
repo
write:packages
read:packages
We can then run npm config set //npm.pkg.github.com/:_authToken <token>
to register this token for use with the GitHub package registry.
Finally, we need to build our package (npm run build
) and then we're ready to publish using npm publish
.
Publishing in GitHub Actions is similar to publishing manually. Before we publish, we need to authenticate with GitHub. However, in GitHub Actions we can use the built-in GITHUB_TOKEN
.
Our action will run on every push to master. It will:
You'll notice that we aren't running unit tests, cacheing our resources or handling package versioning here. This is to keep the focus on publishing the package. You'll need to consider all of these when implementing this in a real project.
1name: Publish23on:4 push:5 branches:6 - master78jobs:9 build:10 runs-on: ubuntu-latest11 steps:12 - uses: actions/checkout@v21314 - run: |15 npm config set //npm.pkg.github.com/:_authToken $TOKEN16 npm install17 npm run build18 npm publish19 env:20 TOKEN: ${{secrets.GITHUB_TOKEN}}
Installing a package from GitHub Packges is relatively simple. We'll use the same npm install
command as always. But there are a few additional steps we'll need to take beforehand.
To help NPM find our package, we're going to associate our package scope with the GitHub Package registry. To do this we need to add the following line to a .npmrc
file in the root of our repo: @<scope>:registry=https://npm.pkg.github.com/
.
In my case, my package scope (the GitHub user associated with my package) is jonathan-wilkinson
, so I'd add the following to my .npmrc
file.
1@jonathan-wilkinson:registry=https://npm.pkg.github.com/
If we're trying to install a public package, we're ready to npm install
, both locally and in GitHub Actions. But if we're going to be installing a private package we'll need to set up authentication with GitHub. The steps will be different for installing the package locally and in GitHub Actions.
If you've been following this tutorial step-by-step you'll have already set up local authentication with GitHub Packages in the publishing step above. If so, you should now be able to npm install
the package.
Otherwise, you should follow the steps above to set-up authentication.
To install a private package inside a GitHub Action we need to set-up authentication with GitHub before we attempt to npm install
our package.
To do this we need to create a new GitHub Personal Access Token with the following permissions:
read:packages
Once we've got this token we're going to store it as a "secret" in our repo called READ_PACKAGES
. Our secret will be available to use in a GitHub action via the secrets
object.
Inside our action, we can run the following command to set up authentication: npm config set //npm.pkg.github.com/:_authToken $TOKEN
, where $TOKEN
is an environment variable set to the value of secrets.READ_PACKAGES
.
1name: Test-Install-GH-Package23on:4 push56jobs:7 build:8 runs-on: ubuntu-latest9 steps:10 - uses: actions/checkout@v21112 - run: |13 npm config set //npm.pkg.github.com/:_authToken $TOKEN14 npm install15 npm run speak # Runs a script that calls our "mean" fn16 env:17 TOKEN: ${{secrets.READ_PACKAGES}}
If you've got to this point, you should be equipped with everything you need to know to go out into the world and make your own nodash
library, or maybe even something useful.