Skip to content

Commit 3382071

Browse files
lumaxisbestander
authored andcommitted
[Replace #2507] Support VSTS and other custom registries with unusual feeds (#3231)
* Fix issue 2505 * Fix flow check * Fix test errors * Load custom package host suffix from .yarnrc * Update tests with new customHostPrefix parameter * Use customHostSuffix parameter to determine if request is to registry
1 parent cc79cff commit 3382071

File tree

9 files changed

+129
-79
lines changed

9 files changed

+129
-79
lines changed
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
//registry.yarnpkg.com/:_authToken=abc123
2+
//registry.yarnpkg.com/:always-auth=true
23
@types:registry=https://registry.yarnpkg.com
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
//registry.yarnpkg.com/:_authToken=abc123
2+
//registry.yarnpkg.com/:always-auth=true
23
@types:registry=https://registry.yarnpkg.com/

__tests__/registries/is-request-to-registry.js

Lines changed: 66 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -3,49 +3,70 @@
33

44
import isRequestToRegistry from '../../src/registries/is-request-to-registry.js';
55

6-
test('isRequestToRegistry functional test', () => {
7-
expect(isRequestToRegistry(
8-
'http://foo.bar:80/foo/bar/baz',
9-
'http://foo.bar/foo/',
10-
)).toBe(true);
11-
12-
expect(isRequestToRegistry(
13-
'http://foo.bar/foo/bar/baz',
14-
'http://foo.bar/foo/',
15-
)).toBe(true);
16-
17-
expect(isRequestToRegistry(
18-
'https://foo.bar:443/foo/bar/baz',
19-
'https://foo.bar/foo/',
20-
)).toBe(true);
21-
22-
expect(isRequestToRegistry(
23-
'https://foo.bar/foo/bar/baz',
24-
'https://foo.bar:443/foo/',
25-
)).toBe(true);
26-
27-
expect(isRequestToRegistry(
28-
'http://foo.bar:80/foo/bar/baz',
29-
'https://foo.bar/foo/',
30-
)).toBe(true);
31-
32-
expect(isRequestToRegistry(
33-
'http://foo.bar/blah/whatever/something',
34-
'http://foo.bar/foo/',
35-
)).toBe(false);
36-
37-
expect(isRequestToRegistry(
38-
'https://wrong.thing/foo/bar/baz',
39-
'https://foo.bar/foo/',
40-
)).toBe(false);
41-
42-
expect(isRequestToRegistry(
43-
'https://foo.bar:1337/foo/bar/baz',
44-
'https://foo.bar/foo/',
45-
)).toBe(false);
46-
47-
expect(isRequestToRegistry(
48-
'http://foo.bar/foo/bar/baz',
49-
'https://foo.bar/foo/bar/baz',
50-
)).toBe(true);
6+
describe('isRequestToRegistry functional test', () => {
7+
test('request to registry url matching', () => {
8+
expect(isRequestToRegistry(
9+
'http://foo.bar:80/foo/bar/baz',
10+
'http://foo.bar/foo/',
11+
)).toBe(true);
12+
13+
expect(isRequestToRegistry(
14+
'http://foo.bar/foo/bar/baz',
15+
'http://foo.bar/foo/',
16+
)).toBe(true);
17+
18+
expect(isRequestToRegistry(
19+
'http://foo.bar/foo/00000000-1111-4444-8888-000000000000/baz',
20+
'http://foo.bar/foo/',
21+
)).toBe(true);
22+
23+
expect(isRequestToRegistry(
24+
'https://foo.bar:443/foo/bar/baz',
25+
'https://foo.bar/foo/',
26+
)).toBe(true);
27+
28+
expect(isRequestToRegistry(
29+
'https://foo.bar/foo/bar/baz',
30+
'https://foo.bar:443/foo/',
31+
)).toBe(true);
32+
33+
expect(isRequestToRegistry(
34+
'https://foo.bar/foo/bar/baz',
35+
'https://foo.bar:443/foo/',
36+
)).toBe(true);
37+
38+
expect(isRequestToRegistry(
39+
'http://foo.bar:80/foo/bar/baz',
40+
'https://foo.bar/foo/',
41+
)).toBe(true);
42+
43+
expect(isRequestToRegistry(
44+
'https://wrong.thing/foo/bar/baz',
45+
'https://foo.bar/foo/',
46+
)).toBe(false);
47+
48+
expect(isRequestToRegistry(
49+
'https://foo.bar:1337/foo/bar/baz',
50+
'https://foo.bar/foo/',
51+
)).toBe(false);
52+
});
53+
54+
test('isRequestToRegistry with custom host prefix', () => {
55+
expect(isRequestToRegistry(
56+
'http://pkgs.host.com:80/foo/bar/baz',
57+
'http://pkgs.host.com/bar/baz',
58+
'some.host.org',
59+
)).toBe(false);
60+
61+
expect(isRequestToRegistry(
62+
'http://foo.bar/foo/bar/baz',
63+
'https://foo.bar/foo/bar/baz',
64+
)).toBe(true);
65+
66+
expect(isRequestToRegistry(
67+
'http://pkgs.host.com:80/foo/bar/baz',
68+
'http://pkgs.host.com/bar/baz',
69+
'pkgs.host.com',
70+
)).toBe(true);
71+
});
5172
});

src/fetchers/base-fetcher.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ const path = require('path');
1313
export default class BaseFetcher {
1414
constructor(dest: string, remote: PackageRemote, config: Config) {
1515
this.reporter = config.reporter;
16+
this.packageName = remote.packageName;
1617
this.reference = remote.reference;
1718
this.registry = remote.registry;
1819
this.hash = remote.hash;
@@ -24,6 +25,7 @@ export default class BaseFetcher {
2425
reporter: Reporter;
2526
remote: PackageRemote;
2627
registry: RegistryNames;
28+
packageName: ?string;
2729
reference: string;
2830
config: Config;
2931
hash: ?string;

src/fetchers/tarball-fetcher.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -178,7 +178,7 @@ export default class TarballFetcher extends BaseFetcher {
178178
.pipe(extractorStream)
179179
.on('error', reject);
180180
},
181-
});
181+
}, this.packageName);
182182
}
183183

184184
async _fetch(): Promise<FetchedOverride> {

src/registries/is-request-to-registry.js

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,17 +2,22 @@
22

33
import url from 'url';
44

5-
export default function isRequestToRegistry(requestUrl: string, registry: string): boolean {
5+
export default function isRequestToRegistry(requestUrl: string, registry: string, customHostSuffix: ? any): boolean {
66
const requestParsed = url.parse(requestUrl);
77
const registryParsed = url.parse(registry);
8+
const requestHost = requestParsed.hostname || '';
9+
const registryHost = registryParsed.hostname || '';
810
const requestPort = getPortOrDefaultPort(requestParsed.port, requestParsed.protocol);
911
const registryPort = getPortOrDefaultPort(registryParsed.port, registryParsed.protocol);
1012
const requestPath = requestParsed.path || '';
1113
const registryPath = registryParsed.path || '';
1214

13-
return (requestParsed.hostname === registryParsed.hostname) &&
14-
(requestPort === registryPort) &&
15-
requestPath.startsWith(registryPath);
15+
return (requestHost === registryHost) &&
16+
(requestPort === registryPort) &&
17+
(requestPath.startsWith(registryPath) ||
18+
// For some registries, the package path does not prefix with the registry path
19+
(!!customHostSuffix && customHostSuffix.length > 0 && requestHost.endsWith(customHostSuffix))
20+
);
1621
}
1722

1823
function getPortOrDefaultPort(port: ?string, protocol: ?string): ?string {

src/registries/npm-registry.js

Lines changed: 47 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as fs from '../util/fs.js';
99
import NpmResolver from '../resolvers/registries/npm-resolver.js';
1010
import envReplace from '../util/env-replace.js';
1111
import Registry from './base-registry.js';
12-
import {addSuffix, removePrefix} from '../util/misc';
12+
import {addSuffix} from '../util/misc';
1313
import isRequestToRegistry from './is-request-to-registry.js';
1414

1515
const userHome = require('../util/user-home-dir').default;
@@ -18,6 +18,8 @@ const url = require('url');
1818
const ini = require('ini');
1919

2020
const DEFAULT_REGISTRY = 'https://registry.npmjs.org/';
21+
const REGEX_REGISTRY_PREFIX = /^https?:/;
22+
const REGEX_REGISTRY_SUFFIX = /registry\/?$/;
2123

2224
function getGlobalPrefix(): string {
2325
if (process.env.PREFIX) {
@@ -51,18 +53,17 @@ export default class NpmRegistry extends Registry {
5153
return name.replace('/', '%2f');
5254
}
5355

54-
request(pathname: string, opts?: RegistryRequestOptions = {}): Promise<*> {
55-
const registry = addSuffix(this.getRegistry(pathname), '/');
56+
request(pathname: string, opts?: RegistryRequestOptions = {}, packageName: ?string): Promise<*> {
57+
const registry = this.getRegistry(packageName || pathname);
5658
const requestUrl = url.resolve(registry, pathname);
57-
const alwaysAuth = this.getScopedOption(registry.replace(/^https?:/, ''), 'always-auth')
58-
|| this.getOption('always-auth')
59-
|| removePrefix(requestUrl, registry)[0] === '@';
59+
const alwaysAuth = this.getRegistryOrGlobalOption(registry, 'always-auth');
60+
const customHostSuffix = this.getRegistryOrGlobalOption(registry, 'custom-host-suffix');
6061

6162
const headers = Object.assign({
6263
'Accept': 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*',
6364
}, opts.headers);
64-
if (this.token || (alwaysAuth && isRequestToRegistry(requestUrl, registry))) {
65-
const authorization = this.getAuth(pathname);
65+
if (this.token || (alwaysAuth && isRequestToRegistry(requestUrl, registry, customHostSuffix))) {
66+
const authorization = this.getAuth(packageName || pathname);
6667
if (authorization) {
6768
headers.authorization = authorization;
6869
}
@@ -160,15 +161,15 @@ export default class NpmRegistry extends Registry {
160161
const availableRegistries = this.getAvailableRegistries();
161162
const registry = availableRegistries.find((registry) => packageName.startsWith(registry));
162163
if (registry) {
163-
return registry;
164+
return addSuffix(registry, '/');
164165
}
165166
}
166167

167168
for (const scope of [this.getScope(packageName), '']) {
168169
const registry = this.getScopedOption(scope, 'registry')
169170
|| this.registries.yarn.getScopedOption(scope, 'registry');
170171
if (registry) {
171-
return String(registry);
172+
return addSuffix(String(registry), '/');
172173
}
173174
}
174175

@@ -180,28 +181,26 @@ export default class NpmRegistry extends Registry {
180181
return this.token;
181182
}
182183

183-
for (let registry of [this.getRegistry(packageName), '', DEFAULT_REGISTRY]) {
184-
registry = registry.replace(/^https?:/, '');
184+
const registry = this.getRegistry(packageName);
185185

186-
// Check for bearer token.
187-
let auth = this.getScopedOption(registry.replace(/\/?$/, '/'), '_authToken');
188-
if (auth) {
189-
return `Bearer ${String(auth)}`;
190-
}
186+
// Check for bearer token.
187+
const authToken = this.getRegistryOrGlobalOption(registry, '_authToken');
188+
if (authToken) {
189+
return `Bearer ${String(authToken)}`;
190+
}
191191
192-
// Check for basic auth token.
193-
auth = this.getScopedOption(registry, '_auth');
194-
if (auth) {
195-
return `Basic ${String(auth)}`;
196-
}
192+
// Check for basic auth token.
193+
const auth = this.getRegistryOrGlobalOption(registry, '_auth');
194+
if (auth) {
195+
return `Basic ${String(auth)}`;
196+
}
197197

198-
// Check for basic username/password auth.
199-
const username = this.getScopedOption(registry, 'username');
200-
const password = this.getScopedOption(registry, '_password');
201-
if (username && password) {
202-
const pw = new Buffer(String(password), 'base64').toString();
203-
return 'Basic ' + new Buffer(String(username) + ':' + pw).toString('base64');
204-
}
198+
// Check for basic username/password auth.
199+
const username = this.getRegistryOrGlobalOption(registry, 'username');
200+
const password = this.getRegistryOrGlobalOption(registry, '_password');
201+
if (username && password) {
202+
const pw = new Buffer(String(password), 'base64').toString();
203+
return 'Basic ' + new Buffer(String(username) + ':' + pw).toString('base64');
205204
}
206205

207206
return '';
@@ -210,4 +209,23 @@ export default class NpmRegistry extends Registry {
210209
getScopedOption(scope: string, option: string): mixed {
211210
return this.getOption(scope + (scope ? ':' : '') + option);
212211
}
212+
213+
getRegistryOption(registry: string, option: string): mixed {
214+
const pre = REGEX_REGISTRY_PREFIX;
215+
const suf = REGEX_REGISTRY_SUFFIX;
216+
217+
// When registry is used config scope, the trailing '/' is required
218+
const reg = addSuffix(registry, '/');
219+
220+
// 1st attempt, try to get option for the given registry URL
221+
// 2nd attempt, remove the 'https?:' prefix of the registry URL
222+
// 3nd attempt, remove the 'registry/?' suffix of the registry URL
223+
return this.getScopedOption(reg, option)
224+
|| reg.match(pre) && this.getRegistryOption(reg.replace(pre, ''), option)
225+
|| reg.match(suf) && this.getRegistryOption(reg.replace(suf, ''), option);
226+
}
227+
228+
getRegistryOrGlobalOption(registry: string, option: string): mixed {
229+
return this.getRegistryOption(registry, option) || this.getOption(option);
230+
}
213231
}

src/resolvers/registries/npm-resolver.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,7 @@ export default class NpmResolver extends RegistryResolver {
191191
reference: this.cleanRegistry(dist.tarball),
192192
hash: dist.shasum,
193193
registry: 'npm',
194+
packageName: info.name,
194195
};
195196
}
196197

src/types.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ export type PackageRemote = {
4242
reference: string,
4343
resolved?: ?string,
4444
hash: ?string,
45+
packageName?: string,
4546
};
4647

4748
// `dependencies` field in package info

0 commit comments

Comments
 (0)