Publishing TypeScript Packages to an Organization's Github Package Registry

I published my first TypeScript Github Package. Here's my experience.
January 8th, 2020  by Blaine Garrett

It seems that every few years there is a new coalescing of disparate technologies that create a learning cliff slowing down progress. At the moment, for me personally, it is creating reusable typescript packages. I'm new to typescript and also I wanted to give the newly launched github packages a whirl. Here is my experience and some pitfalls I ran into. This was made all the more confusing by working across Github Organizations. 

Note: If you are just looking to figure out how to publish Github packages, read this great post by Dale Nguyen on the topic. 

Update: I started a template repository. You can use it as a base for your package to follow along.

Goals:

  • Create a public TypeScript project in Organization A
  • Publish it to Github Package Registry also within Organization A
  • Consume the package within Organization B
  • Continue on with my life... 

 

Before We Begin

  • I will be publishing a package for the scrapertools project I made within the Github Organization digibodies digibodies. You can use your own TypeScript package if you like or use use my template project.
  • I will be consuming the scrapertools package within a nestjs node application within the Github Organization mplsart
  • Neither of the above projects exist within my personal Github user blainegarrett. This will become important later. 

 

Step 1: Authenticate With Github Using a Personal Access Token 

This step is required to publish your package. Log in to Github and navigate to your Personal Access Tokens (Settings > Developer Settings > Personal Access Tokens)

Create a New Personal Access Token. I tend to name my tokens things like "Package Tests 2020" so I know what to revoke/rotate later. You will need the following scopes:

  • repo (and all scopes below it)
  • write:packages
  • read:packages 

Once you are presented with your key, put it somewhere. You'll need it in a second and it cannot be retrieved again.

In your terminal run:

npm login --registry=https://npm.pkg.github.com

You will then be presented with prompts to enter:

  • Username - use the Github username you created the personal access token for above (not organization, etc). 
  • Password: Use the Personal Access Token generated above
  • Email: Enter your public email address from your Public Profile Settings. If you do not have a public email set up, you will get a permission denied error even if the username and password are correct.

If you authenticate correctly, you will see:

Logged in as <username> on https://npm.pkg.github.com/.

 

Step 2: Publish Your Package

In the package.json file for your project you want to publish, ensure you have the following top level field (sibling to name, scripts, dependencies, etc).

"publishConfig": {
    "registry": "https://npm.pkg.github.com/@github-username-or-organization"
}

Note: It is important here to set @github-username-or-organization to the destination the package will be published to. This should surely be the account under which your project lives. If for example, you project lives under the digibodies Organization at https://github.com/digibodies/scrapertools, then your config should be:

"publishConfig": {
    "registry": "https://npm.pkg.github.com/@digibodies"
}

 

Next lets run the TypeScript compiler  to populate the dist folder with our non-TypeScript transpiled code.

npm run build 

or if you do not have a build directive setup

tcs

Check the contents of the dist folder and make sure they look generally alright. This is what will get published. 


Finally, let's publish:

npm publish

If all goes well, you should be able to navigate to your project in github and see your new package under the packages tab. eg. https://github.com/digibodies/scrapertools/packages/

Pitfall: As you are experimenting and doing subsequent publishes, be sure to bump the version # in your package.json. Otherwise, you'll get a EPUBLISHCONFLICT error Cannot publish over existing version.

 

Step 3: Install Your Package In Another TypeScript

Before we get too excited, we will want to ensure that we can consume our package. For me, this was a project within a different organization, which caused some confusion. Either way, you will need to put an .npmrc file within the root of your consuming project. This informs node to also look at github packages when resolving.

This file should have one line (for now):

registry=https://npm.pkg.github.com/@github-username-or-organization

Replace @github-username-or-organization with the what you used in step 2 above. In my case, this was @digibodies

Finally, install the package into your project:

npm install --save @github-username-or-organization/package-name

If all goes well, your package should be installed! Celebrate momentarily.

Note: If you receive the error: 401 Unauthorized:@github-username-or-organization/packagename you likely need to login with your Personal Access Token again from step 1. I ran into this when working across multiple machines. you need to login on each.
Note: If you receive the error: 404 Not Found:@github-username-or-organization/packagename ensure you spelled the package name correctly and that it is indeed published.

 

Step 4: Ensure your Package works 

In your consuming project, import your package:

const packagename = require('@github-username-or-organization/package-name'); 

or using import syntax

import packagename from '@github-username-or-organization/package-name'

In my case:

import scrapertools from '@digibodies/scrapertools';

This is one of the steps that bit me. Originally, my project contained a helper func that was exported only for unit testing purposes. The function returned a type that was part of a 3rd party package that I didn't want to have to expose as a peer dependency. However, when I went to consume my package, the TypeScript compiler blew up on the type being undefined in my index.d.ts since I didn't install the 3rd party package as part of the consuming repo. As I learn more about TypeScript nuance, I hope to understand this issue better. Either/or, ensure your stuff works before calling it a day.

 

Interesting Gotchas

  • You cannot delete a package nor a specific version from the Github UI. Read this for the reason why.
  • If you use source maps, your dist package contents will include the src directory. I couldn't figure out a way around this.
  • Your package version will be whatever is set in the package.json. This is independent of:
    • The release versions in github
    • The branch you are on (somewhat similar to release versions however).
    • If you even have your code committed.

 

Things To Figure Out

I have some lingering questions I need to resolve in this post or in a followup post:

  • How do I deal with exporting 3rd party types?
  • How to consume packages from multiple Github users and Organizations? 
  • How to locally link w/o the need for monorepos?
  • Does this work with SSH or do I have to use Personal Access Tokens?

 

References: