How to Release Utilities Package to GitHub Packages

Releasing a closed-source, reusable JavaScript/TypeScript package for internal use across frontend and backend is a common challenge, especially when you want to automate it, keep things modular, and avoid unnecessary leakage of code. Here’s how I do it, step by step, using only what’s needed for a stable, repeatable workflow. Why GitHub Packages (and Not npmjs.org)? Most teams reach for npmjs.org by default, but if your utilities are strictly internal - or have some private contract processing logic you’re not ready to open-source - GitHub’s own registry is more than enough: Integrated with your repository: No extra accounts or keys to manage. Scoped access: Control exactly who gets your code. Familiar workflows: Your team’s already on GitHub; why hop away? I've used this for smart contract SDKs referenced both by frontend app and NestJS API. Directory Structure I keep only my distributable code in /package, separate from internal scripts/docs, to avoid accidentally leaking dev files |-- .github/ |-- src/ |-- package/ #

May 7, 2025 - 16:18
 0
How to Release Utilities Package to GitHub Packages

Releasing a closed-source, reusable JavaScript/TypeScript package for internal use across frontend and backend is a common challenge, especially when you want to automate it, keep things modular, and avoid unnecessary leakage of code. Here’s how I do it, step by step, using only what’s needed for a stable, repeatable workflow.

Why GitHub Packages (and Not npmjs.org)?

Most teams reach for npmjs.org by default, but if your utilities are strictly internal - or have some private contract processing logic you’re not ready to open-source - GitHub’s own registry is more than enough:

  • Integrated with your repository: No extra accounts or keys to manage.
  • Scoped access: Control exactly who gets your code.
  • Familiar workflows: Your team’s already on GitHub; why hop away?

I've used this for smart contract SDKs referenced both by frontend app and NestJS API.

Directory Structure

I keep only my distributable code in /package, separate from internal scripts/docs, to avoid accidentally leaking dev files

|-- .github/
|-- src/
|-- package/           # <--- Only your published files live here
    |-- package.json
    |-- dist/
    |-- index.js
|-- ...

Pro tip: npm publish runs only in /package, not at the repo root.

Manual Releases Triggered From GitHub Releases

Every package update is explicitly tagged as a release in GitHub's UI, which helps prevent accidental releases of incomplete work.

github release package page

Action Workflow File

Below is the full workflow that gets the job done.


name: Publish package on GitHub Packages

on:
  release:
    types: [created]

jobs:
  publish:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write

    steps:
      - uses: actions/checkout@v3

      - uses: actions/setup-node@v3
        with:
          node-version: 18
          registry-url: "https://npm.pkg.github.com"
          scope: "@your-user-name"
          always-auth: true

      - name: Install dependencies
        run: npm ci

      - name: Build package
        run: npm run package

      - name: Install package dependencies
        working-directory: ./package
        run: npm ci

      - name: Publish package
        working-directory: ./package
        run: npm publish
        env:
          NODE_AUTH_TOKEN: ${{ secrets.GITHUB_TOKEN }}


Place it in github/workflows/publish.yml

Key Parts

  • Scopes, Not Monorepos: No workspaces, no publishing the entire repo.
  • No Source Leakage: Only files in /package are seen by consumers—no accidental pushes of TS, docs, or git history.
  • Manual Trigger: The process kicks off only when you create a GitHub Release, not on every push or PR.

Real-World Example

Let’s say you update a Smart Contracts ABI in /src, then run your internal build (maybe via a simple "package" script) to output to /package/dist.

Only that transpiled, dependency-free version ships.

Your API team can safely pull it via:

npm install @user/package-name --registry=https://npm.pkg.github.com

From both backend and frontend, with no npmjs exposure.