@@ -4,6 +4,48 @@ const hostedGitInfo = require('hosted-git-info');
44const { verifyAuth} = require ( './git' ) ;
55const debug = require ( 'debug' ) ( 'semantic-release:get-git-auth-url' ) ;
66
7+ /**
8+ * Machinery to format a repository URL with the given credentials
9+ *
10+ * @param {String } protocol URL protocol (which should not be present in repositoryUrl)
11+ * @param {String } repositoryUrl User-given repository URL
12+ * @param {String } gitCredentials The basic auth part of the URL
13+ *
14+ * @return {String } The formatted Git repository URL.
15+ */
16+ function formatAuthUrl ( protocol , repositoryUrl , gitCredentials ) {
17+ const [ match , auth , host , basePort , path ] =
18+ / ^ (? ! .+ : \/ \/ ) (?: (?< auth > .* ) @ ) ? (?< host > .* ?) : (?< port > \d + ) ? : ? \/ ? (?< path > .* ) $ / . exec ( repositoryUrl ) || [ ] ;
19+ const { port, hostname, ...parsed } = parse (
20+ match ? `ssh://${ auth ? `${ auth } @` : '' } ${ host } ${ basePort ? `:${ basePort } ` : '' } /${ path } ` : repositoryUrl
21+ ) ;
22+
23+ return format ( {
24+ ...parsed ,
25+ auth : gitCredentials ,
26+ host : `${ hostname } ${ protocol === 'ssh:' ? '' : port ? `:${ port } ` : '' } ` ,
27+ protocol : protocol && / h t t p [ ^ s ] / . test ( protocol ) ? 'http' : 'https' ,
28+ } ) ;
29+ }
30+
31+ /**
32+ * Verify authUrl by calling git.verifyAuth, but don't throw on failure
33+ *
34+ * @param {Object } context semantic-release context.
35+ * @param {String } authUrl Repository URL to verify
36+ *
37+ * @return {String } The authUrl as is if the connection was successfull, null otherwise
38+ */
39+ async function ensureValidAuthUrl ( { cwd, env, branch} , authUrl ) {
40+ try {
41+ await verifyAuth ( authUrl , branch . name , { cwd, env} ) ;
42+ return authUrl ;
43+ } catch ( error ) {
44+ debug ( error ) ;
45+ return null ;
46+ }
47+ }
48+
749/**
850 * Determine the the git repository URL to use to push, either:
951 * - The `repositoryUrl` as is if allowed to push
@@ -15,7 +57,8 @@ const debug = require('debug')('semantic-release:get-git-auth-url');
1557 *
1658 * @return {String } The formatted Git repository URL.
1759 */
18- module . exports = async ( { cwd, env, branch, options : { repositoryUrl} } ) => {
60+ module . exports = async ( context ) => {
61+ const { cwd, env, branch} = context ;
1962 const GIT_TOKENS = {
2063 GIT_CREDENTIALS : undefined ,
2164 GH_TOKEN : undefined ,
@@ -30,6 +73,7 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
3073 BITBUCKET_TOKEN_BASIC_AUTH : '' ,
3174 } ;
3275
76+ let { repositoryUrl} = context . options ;
3377 const info = hostedGitInfo . fromUrl ( repositoryUrl , { noGitPlus : true } ) ;
3478 const { protocol, ...parsed } = parse ( repositoryUrl ) ;
3579
@@ -47,24 +91,30 @@ module.exports = async ({cwd, env, branch, options: {repositoryUrl}}) => {
4791 await verifyAuth ( repositoryUrl , branch . name , { cwd, env} ) ;
4892 } catch ( _ ) {
4993 debug ( 'SSH key auth failed, falling back to https.' ) ;
94+ const envVars = Object . keys ( GIT_TOKENS ) . filter ( ( envVar ) => ! isNil ( env [ envVar ] ) ) ;
95+
96+ // Skip verification if there is no ambiguity on which env var to use for authentication
97+ if ( envVars . length === 1 ) {
98+ const gitCredentials = `${ GIT_TOKENS [ envVars [ 0 ] ] || '' } ${ env [ envVars [ 0 ] ] } ` ;
99+ return formatAuthUrl ( protocol , repositoryUrl , gitCredentials ) ;
100+ }
50101
51- const envVar = Object . keys ( GIT_TOKENS ) . find ( ( envVar ) => ! isNil ( env [ envVar ] ) ) ;
52- const gitCredentials = ` ${ GIT_TOKENS [ envVar ] || '' } ${ env [ envVar ] || '' } ` ;
102+ if ( envVars . length > 1 ) {
103+ debug ( `Found ${ envVars . length } credentials in environment, trying all of them` ) ;
53104
54- if ( gitCredentials ) {
55- // If credentials are set via environment variables, convert the URL to http/https and add basic auth, otherwise return `repositoryUrl` as is
56- const [ match , auth , host , basePort , path ] =
57- / ^ (? ! .+ : \/ \/ ) (?: (?< auth > .* ) @ ) ? (?< host > .* ?) : (?< port > \d + ) ? : ? \/ ? (?< path > .* ) $ / . exec ( repositoryUrl ) || [ ] ;
58- const { port, hostname, ...parsed } = parse (
59- match ? `ssh://${ auth ? `${ auth } @` : '' } ${ host } ${ basePort ? `:${ basePort } ` : '' } /${ path } ` : repositoryUrl
60- ) ;
105+ const candidateRepositoryUrls = [ ] ;
106+ for ( const envVar of envVars ) {
107+ const gitCredentials = `${ GIT_TOKENS [ envVar ] || '' } ${ env [ envVar ] } ` ;
108+ const authUrl = formatAuthUrl ( protocol , repositoryUrl , gitCredentials ) ;
109+ candidateRepositoryUrls . push ( ensureValidAuthUrl ( context , authUrl ) ) ;
110+ }
61111
62- return format ( {
63- ... parsed ,
64- auth : gitCredentials ,
65- host : ` ${ hostname } ${ protocol === 'ssh:' ? '' : port ? `: ${ port } ` : '' } ` ,
66- protocol : protocol && / h t t p [ ^ s ] / . test ( protocol ) ? 'http' : 'https' ,
67- } ) ;
112+ const validRepositoryUrls = await Promise . all ( candidateRepositoryUrls ) ;
113+ const chosenAuthUrlIndex = validRepositoryUrls . findIndex ( ( url ) => url !== null ) ;
114+ if ( chosenAuthUrlIndex > - 1 ) {
115+ debug ( `Using " ${ envVars [ chosenAuthUrlIndex ] } " to authenticate` ) ;
116+ return validRepositoryUrls [ chosenAuthUrlIndex ] ;
117+ }
68118 }
69119 }
70120
0 commit comments