diff --git a/workspaces/arborist/lib/override-resolves.js b/workspaces/arborist/lib/override-resolves.js index 794b2c335dc62..f991e6d962389 100644 --- a/workspaces/arborist/lib/override-resolves.js +++ b/workspaces/arborist/lib/override-resolves.js @@ -1,10 +1,51 @@ -function overrideResolves (resolved, opts = {}) { - const { omitLockfileRegistryResolved = false } = opts +const npa = require('npm-package-arg') - if (omitLockfileRegistryResolved) { +function overrideResolves (node, resolved, opts = {}) { + const { + omitLockfileRegistryResolved = false, + recordDefaultRegistry = false, + } = opts + + const isRegistryDependency = node.isRegistryDependency + + // omit the resolved url of registry dependencies. this makes installs slower + // because npm must resolve the url for each package version but it allows + // users to use a lockfile across registries that host tarballs at different + // paths. + if (isRegistryDependency && omitLockfileRegistryResolved) { return undefined } + // replace the configured registry with the default registry. the default + // registry is a magic value meaning the current registry, so recording + // resolved with the default registry allows users to switch to a + // different registry without removing their lockfile. The path portion + // of the resolved url is preserved so this trick only works when the + // different registries host tarballs at the same relative paths. + if (isRegistryDependency && recordDefaultRegistry) { + const scope = npa(node.packageName).scope + const registry = scope && opts[`${scope}:registry`] + ? opts[`${scope}:registry`] + : opts.registry + + // normalize registry url - strip trailing slash. + // TODO improve normalization for both the configured registry and resolved + // url. consider port, protocol, more path normalization. + const normalized = registry.endsWith('/') + ? registry.slice(0, -1) + : registry + + // only replace the host if the resolved url is for the configured + // registry. registries may host tarballs on another server. return + // undefined so npm will re-resolve the url from the current registry when + // it reads the lockfile. + if (resolved.startsWith(normalized)) { + return 'https://registry.npmjs.org' + resolved.slice(normalized.length) + } else { + return undefined + } + } + return resolved } diff --git a/workspaces/arborist/lib/shrinkwrap.js b/workspaces/arborist/lib/shrinkwrap.js index b1ea2cacb360e..f8dd056d62bee 100644 --- a/workspaces/arborist/lib/shrinkwrap.js +++ b/workspaces/arborist/lib/shrinkwrap.js @@ -300,13 +300,8 @@ class Shrinkwrap { }) const resolved = consistentResolve(node.resolved, node.path, path, true) - // hide resolved from registry dependencies. - if (!resolved) { - // no-op - } else if (node.isRegistryDependency) { - meta.resolved = overrideResolves(resolved, options) - } else { - meta.resolved = resolved + if (resolved) { + meta.resolved = overrideResolves(node, resolved, options) } if (node.extraneous) { @@ -1027,7 +1022,7 @@ class Shrinkwrap { spec.type !== 'git' && spec.type !== 'file' && spec.type !== 'remote') { - lock.resolved = overrideResolves(node.resolved, this.resolveOptions) + lock.resolved = overrideResolves(node, node.resolved, this.resolveOptions) } if (node.integrity) { diff --git a/workspaces/arborist/test/shrinkwrap.js b/workspaces/arborist/test/shrinkwrap.js index 5d23904fcaee2..daa661ee38e08 100644 --- a/workspaces/arborist/test/shrinkwrap.js +++ b/workspaces/arborist/test/shrinkwrap.js @@ -247,7 +247,7 @@ t.only('resolveOptions', async t => { dependencies: { registry: '^1.0.0', 'some-other-registry': '^1.0.0', - '@scoped/some-other-registry': '^1.0.0', + '@scope/some-package': '^1.0.0', tar: url, }, }, @@ -271,7 +271,7 @@ t.only('resolveOptions', async t => { }) const scopedOtherRegistry = new Node({ - pkg: { name: '@scope/some-other-registry', version: '1.2.3' }, + pkg: { name: '@scope/some-package', version: '1.2.3' }, resolved: someOtherRegistry, integrity: 'sha512-registry', parent: root, @@ -312,6 +312,33 @@ t.only('resolveOptions', async t => { t.strictSame(data.dependencies.tar.resolved, undefined) }) + await t.test('recordDefaultRegistry: true', async t => { + const { data } = await getData({ + recordDefaultRegistry: true, + registry: 'https://private.registry.org/deadbeef', + '@scope:registry': 'https://someother.registry.org', + }) + + // unscoped packages that resolve to their configured registry should be + // record the default registry + t.strictSame(data.packages['node_modules/registry'].resolved, + 'https://registry.npmjs.org/registry/-/registry-1.2.3.tgz') + t.strictSame(data.dependencies.registry.resolved, + 'https://registry.npmjs.org/registry/-/registry-1.2.3.tgz') + + // scoped packages that resolve to their configured registry should be + // record the default registry + t.strictSame(data.packages['node_modules/@scope/some-package'].resolved, + 'https://registry.npmjs.org/registry/-/registry-1.2.3.tgz') + t.strictSame(data.dependencies['@scope/some-package'].resolved, + 'https://registry.npmjs.org/registry/-/registry-1.2.3.tgz') + + // packages with resolved urls that don't match the configured registry + // should record undefined so npm resolves their url again. + t.strictSame(data.packages['node_modules/some-other-registry'].resolved, undefined) + t.strictSame(data.dependencies['some-other-registry'].resolved, undefined) + }) + t.test('metaFromNode default', async t => { // test to cover options default. const { registry } = await getData(undefined)