Skip to content

Instantly share code, notes, and snippets.

@xeger
Last active May 7, 2024 18:37
Show Gist options
  • Save xeger/eb89630db88cce8c6ca76d653d9f9ad0 to your computer and use it in GitHub Desktop.
Save xeger/eb89630db88cce8c6ca76d653d9f9ad0 to your computer and use it in GitHub Desktop.
NPM Install Packages Hosted in a Private GitHub Repository

Problem Statement

You want to distribute a proprietary NPM package as a GitHub repository "in situ," without publishing it to npmjs.org or even to GitHub Packages. (For example, the package may be subject to constant change, which is not a good fit for the NPM distribution model.)

NPM allows URLs, or even GitHub URLs, as dependencies. When one of these is specified, NPM will perform a shallow clone to install the dependency, and will record the commit ref in your package-lock.json.

However: when the Git repository in question is not public, there is an incompatibility between the way developers, GitHub Actions, and NPM like to access private repositories.

Solution Summary

To successfully handle a private-repo URL in CI and on developer laptops, you need to ensure several things:

  1. The dependency is specified as a git+ssh:// URL so that developers (who typically use SSH to clone) can install the dependnecy on their own machines.
  2. You have a Personal Access Token with repo scope on the dependency's repository, and this is available to your GitHub actions (i.e. as a secret).
  3. GitHub actions configure the Git client to perform a rewrite operation to transform the SSH URL into an authenticated HTTPS URL.
  4. The actions/checkout action does not persist its own credentials, which will prevent the rewrite operation from succeeding!

Note that neither OpenID Connect tokens (as obtained by GitHub job runners) nor Fine-Grained Tokens are sufficient, because neither of them can grant access to clone another repository. As of May 2024, only a Classic PAT can be granted repo scope.

Outcome

  1. Your developers npm install using SSH transport.
  2. GitHub Actions npm install using HTTPS transport plus basic authentication.

Alternate Solutions

  1. Generate a private SSH key and make it available to your GitHub actions in lieu of a PAT.
    • This lets you avoid the git-config rewrite step, but the key is very unwieldy and can't be narrowly scoped to just repo scope.
  2. Ask your developers to configure rewrite rules on their local machines so that everyone uses PAT authentication.
    • This likewise lets you avoid git-config rewrite, but at the cost of complexity for your developers.
{
"dependencies": {
"@some-org/some-internal-package": "git+ssh://git@github.com:some-org/some-internal-package.git"
}
}
# This is what $HOME/.gitconfig looks like in CI after your `git config` command runs
[url "https://some-github-user:ghp_someValidPersonalAccessToken@github.com"]
insteadOf = ssh://git@github.com
steps:
- uses: actions/checkout@v4
with:
# Specify this in order to keep the checkout action from creating a local `.gitconfig` file!
# This will let your global gitconfig "win" in future steps
persist-credentials: false
- uses: actions/setup-node@v4
- name: Configure Git for PAT Authentication
env:
PERSONAL_ACCESS_TOKEN: ${{ secrets.PERSONAL_ACCESS_TOKEN }}
run: |
git config --global url."https://some-github-user:$PERSONAL_ACCESS_TOKEN@github.com".insteadOf "ssh://git@github.com"
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment