mirror of
https://github.com/pnpm/pnpm.git
synced 2026-05-13 02:55:56 -04:00
This is consistent with #9358, but implements support for the GitHub Packages npm registry and, more broadly, for vlt-style https://docs.vlt.sh/cli/registries for any registry. This PR adds a built-in gh: specifier that resolves against the GitHub Packages npm registry, plus a namedRegistries config key so a project can map its own aliases to arbitrary registries. A project can mix public npm packages and private GitHub Packages (or self-hosted) ones without applying a scope-wide registry override to every @scope/* package. - pnpm add gh:@acme/private writes "@acme/private": "gh:^1.0.0" and resolves from https://npm.pkg.github.com/. - pnpm add gh:@acme/private@^1.0.0 (with or without an alias) is also supported. Aliased form writes "my-alias": "gh:@acme/private@^1.0.0". - Auth comes from the existing per-URL .npmrc mechanism, e.g. //npm.pkg.github.com/:_authToken=${GITHUB_TOKEN}. No new auth surface. - @github is intentionally not defaulted to https://npm.pkg.github.com/ - hardcoding that would hijack installs of the public @github/* packages on npmjs.org (e.g. @github/relative-time-element) for users without a scope-wide override. Use gh: to install from GitHub Packages, or configure @github:registry=... yourself if that's really what you want. - Additional named registries (a self-hosted proxy, GitHub Enterprise Server, etc.) can be configured in pnpm-workspace.yaml: ```yml namedRegistries: gh: https://npm.pkg.github.example.com/ # optional: overrides the built-in `gh` alias for GHES work: https://npm.work.example.com/ ``` - Then work:@corp/lib@^2.0.0 resolves against https://npm.work.example.com/, and the built-in gh alias can be redirected to a GHES host. - Env-var substitution (${VAR}) is supported in namedRegistries values, mirroring the .npmrc convention. - Reserved alias names (npm, jsr, github, workspace, catalog, file, git, http, https, link, patch, and related git host shorthands) cannot be redefined as user-named registries - the resolver throws ERR_PNPM_RESERVED_NAMED_REGISTRY_ALIAS at startup rather than silently shadowing another protocol. Malformed URLs throw ERR_PNPM_INVALID_NAMED_REGISTRY_URL at startup too, instead of failing as a confusing 404 during resolution. - On publish, createExportableManifest strips any named-registry prefix (both the built-in gh: and any user-configured alias) so npm and yarn consumers can still resolve the dependency via their own scope-registry configuration - mirroring the user-facing requirement when installing such a dep without the prefix. The prefix is gh: rather than github: because github: is reserved by npm-package-arg / hosted-git-info as a git host shorthand (e.g. github:owner/repo) - reusing it would be a deviation from the specs used by the npm CLI. gh: is shorter, matches vlt's convention, and cannot collide with any existing npm scheme. Unlike jsr:, gh: (and any other named-registry alias) does not rewrite the package name - gh:@acme/foo resolves @acme/foo from the GitHub Packages registry as-is. This also means npm/yarn consumers see the original name after the prefix is stripped on publish. --------- Co-authored-by: Zoltan Kochan <z@kochan.io>
38 lines
2.5 KiB
TypeScript
38 lines
2.5 KiB
TypeScript
import { expect, test } from '@jest/globals'
|
|
|
|
import { replaceVersionInBareSpecifier } from '../lib/replaceVersionInBareSpecifier.js'
|
|
|
|
test('replaceVersionInBareSpecifier()', () => {
|
|
expect(replaceVersionInBareSpecifier('^1.0.0', '1.1.0')).toBe('1.1.0')
|
|
expect(replaceVersionInBareSpecifier('npm:foo@^1.0.0', '1.1.0')).toBe('npm:foo@1.1.0')
|
|
expect(replaceVersionInBareSpecifier('npm:@foo/bar@^1.0.0', '1.1.0')).toBe('npm:@foo/bar@1.1.0')
|
|
expect(replaceVersionInBareSpecifier('npm:foo', '1.1.0')).toBe('npm:foo@1.1.0')
|
|
expect(replaceVersionInBareSpecifier('npm:@foo/bar', '1.1.0')).toBe('npm:@foo/bar@1.1.0')
|
|
})
|
|
|
|
test('replaceVersionInBareSpecifier() applies the fast path to configured named-registry prefixes', () => {
|
|
// The caller (deps-resolver) supplies the merged set of named-registry
|
|
// prefixes — built-in `gh:` and any user-defined aliases — so the locked
|
|
// version can be pasted in without re-fetching metadata.
|
|
const prefixes = ['gh:', 'work:']
|
|
expect(replaceVersionInBareSpecifier('gh:^1.0.0', '1.1.0', prefixes)).toBe('gh:1.1.0')
|
|
expect(replaceVersionInBareSpecifier('gh:@acme/foo@^1.0.0', '1.1.0', prefixes)).toBe('gh:@acme/foo@1.1.0')
|
|
expect(replaceVersionInBareSpecifier('gh:@acme/foo', '1.1.0', prefixes)).toBe('gh:@acme/foo@1.1.0')
|
|
expect(replaceVersionInBareSpecifier('work:@corp/lib@^2.0.0', '2.1.0', prefixes)).toBe('work:@corp/lib@2.1.0')
|
|
})
|
|
|
|
test('replaceVersionInBareSpecifier() leaves unrecognized prefixes untouched', () => {
|
|
// Other resolvers (workspace, file/link, catalog, git, tarball) own these
|
|
// schemes; the npm-style version replacer must not rewrite them. An alias
|
|
// that isn't in the supplied named-registry set also falls through.
|
|
expect(replaceVersionInBareSpecifier('workspace:^1.0.0', '1.1.0')).toBe('workspace:^1.0.0')
|
|
expect(replaceVersionInBareSpecifier('workspace:./pkg', '1.1.0')).toBe('workspace:./pkg')
|
|
expect(replaceVersionInBareSpecifier('file:./pkg', '1.1.0')).toBe('file:./pkg')
|
|
expect(replaceVersionInBareSpecifier('link:../pkg', '1.1.0')).toBe('link:../pkg')
|
|
expect(replaceVersionInBareSpecifier('catalog:', '1.1.0')).toBe('catalog:')
|
|
expect(replaceVersionInBareSpecifier('github:owner/repo', '1.1.0')).toBe('github:owner/repo')
|
|
expect(replaceVersionInBareSpecifier('https://example.com/tarball.tgz', '1.1.0')).toBe('https://example.com/tarball.tgz')
|
|
expect(replaceVersionInBareSpecifier('gh:^1.0.0', '1.1.0', [])).toBe('gh:^1.0.0')
|
|
expect(replaceVersionInBareSpecifier('work:^1.0.0', '1.1.0', ['gh:'])).toBe('work:^1.0.0')
|
|
})
|