diff --git a/cli.js b/cli.js index 9040236228..19c21842a0 100755 --- a/cli.js +++ b/cli.js @@ -34,6 +34,7 @@ Usage: .option('success', {...stringList, group: 'Plugins'}) .option('fail', {...stringList, group: 'Plugins'}) .option('debug', {describe: 'Output debugging information', type: 'boolean', group: 'Options'}) + .option('skip-remote-check', {describe: 'TODO', type: 'boolean', group: 'Options'}) .option('d', {alias: 'dry-run', describe: 'Skip publishing', type: 'boolean', group: 'Options'}) .option('h', {alias: 'help', group: 'Options'}) .option('v', {alias: 'version', group: 'Options'}) diff --git a/index.js b/index.js index 8456ad55d2..3774151686 100644 --- a/index.js +++ b/index.js @@ -58,6 +58,11 @@ async function run(context, plugins) { return false; } + if (options.skipRemoteCheck && !options.dryRun) { + logger.error('--skip-remote-check can only be specified in dry run mode.'); + return false; + } + // Verify config await verify(context); @@ -80,25 +85,29 @@ async function run(context, plugins) { }` ); - try { + if (options.skipRemoteCheck) { + logger.warn('Skipping up-to-date check for branch %s because skip-remote-check is set', context.branch.name); + } else { try { - await verifyAuth(options.repositoryUrl, context.branch.name, {cwd, env}); - } catch (error) { - if (!(await isBranchUpToDate(options.repositoryUrl, context.branch.name, {cwd, env}))) { - logger.log( - `The local branch ${context.branch.name} is behind the remote one, therefore a new version won't be published.` - ); - return false; + try { + await verifyAuth(options.repositoryUrl, context.branch.name, {cwd, env}); + } catch (error) { + if (!(await isBranchUpToDate(options.repositoryUrl, context.branch.name, {cwd, env}))) { + logger.log( + `The local branch ${context.branch.name} is behind the remote one, therefore a new version won't be published.` + ); + return false; + } + + throw error; } - - throw error; + } catch (error) { + logger.error(`The command "${error.command}" failed with the error message ${error.stderr}.`); + throw getError('EGITNOPERMISSION', context); } - } catch (error) { - logger.error(`The command "${error.command}" failed with the error message ${error.stderr}.`); - throw getError('EGITNOPERMISSION', context); - } - logger.success(`Allowed to push to the Git repository`); + logger.success(`Allowed to push to the Git repository`); + } await plugins.verifyConditions(context); diff --git a/lib/get-git-auth-url.js b/lib/get-git-auth-url.js index 56b5fc6932..1c158d125f 100644 --- a/lib/get-git-auth-url.js +++ b/lib/get-git-auth-url.js @@ -73,7 +73,7 @@ module.exports = async (context) => { BITBUCKET_TOKEN_BASIC_AUTH: '', }; - let {repositoryUrl} = context.options; + let {repositoryUrl, skipRemoteCheck} = context.options; const info = hostedGitInfo.fromUrl(repositoryUrl, {noGitPlus: true}); const {protocol, ...parsed} = parse(repositoryUrl); @@ -85,35 +85,39 @@ module.exports = async (context) => { repositoryUrl = format({...parsed, protocol: protocol.includes('https') ? 'https' : 'http', href: null}); } - // Test if push is allowed without transforming the URL (e.g. is ssh keys are set up) - try { - debug('Verifying ssh auth by attempting to push to %s', repositoryUrl); - await verifyAuth(repositoryUrl, branch.name, {cwd, env}); - } catch { - debug('SSH key auth failed, falling back to https.'); - const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(env[envVar])); + if (skipRemoteCheck) { + debug('Skipping remote verification, blindly using url %s', repositoryUrl); + } else { + // Test if push is allowed without transforming the URL (e.g. is ssh keys are set up) + try { + debug('Verifying ssh auth by attempting to push to %s', repositoryUrl); + await verifyAuth(repositoryUrl, branch.name, {cwd, env}); + } catch { + debug('SSH key auth failed, falling back to https.'); + const envVars = Object.keys(GIT_TOKENS).filter((envVar) => !isNil(env[envVar])); - // Skip verification if there is no ambiguity on which env var to use for authentication - if (envVars.length === 1) { - const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`; - return formatAuthUrl(protocol, repositoryUrl, gitCredentials); - } + // Skip verification if there is no ambiguity on which env var to use for authentication + if (envVars.length === 1) { + const gitCredentials = `${GIT_TOKENS[envVars[0]] || ''}${env[envVars[0]]}`; + return formatAuthUrl(protocol, repositoryUrl, gitCredentials); + } - if (envVars.length > 1) { - debug(`Found ${envVars.length} credentials in environment, trying all of them`); + if (envVars.length > 1) { + debug(`Found ${envVars.length} credentials in environment, trying all of them`); - const candidateRepositoryUrls = []; - for (const envVar of envVars) { - const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`; - const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials); - candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl)); - } + const candidateRepositoryUrls = []; + for (const envVar of envVars) { + const gitCredentials = `${GIT_TOKENS[envVar] || ''}${env[envVar]}`; + const authUrl = formatAuthUrl(protocol, repositoryUrl, gitCredentials); + candidateRepositoryUrls.push(ensureValidAuthUrl(context, authUrl)); + } - const validRepositoryUrls = await Promise.all(candidateRepositoryUrls); - const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null); - if (chosenAuthUrlIndex > -1) { - debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`); - return validRepositoryUrls[chosenAuthUrlIndex]; + const validRepositoryUrls = await Promise.all(candidateRepositoryUrls); + const chosenAuthUrlIndex = validRepositoryUrls.findIndex((url) => url !== null); + if (chosenAuthUrlIndex > -1) { + debug(`Using "${envVars[chosenAuthUrlIndex]}" to authenticate`); + return validRepositoryUrls[chosenAuthUrlIndex]; + } } } } diff --git a/test/cli.test.js b/test/cli.test.js index 90715a379a..e7a4efb601 100644 --- a/test/cli.test.js +++ b/test/cli.test.js @@ -62,6 +62,7 @@ test.serial('Pass options to semantic-release API', async (t) => { 'fail2', '--debug', '-d', + '--skip-remote-check', ]; const cli = proxyquire('../cli', {'.': run, process: {...process, argv}}); @@ -82,6 +83,7 @@ test.serial('Pass options to semantic-release API', async (t) => { t.deepEqual(run.args[0][0].fail, ['fail1', 'fail2']); t.is(run.args[0][0].debug, true); t.is(run.args[0][0].dryRun, true); + t.is(run.args[0][0].skipRemoteCheck, true); t.is(exitCode, 0); }); diff --git a/test/get-git-auth-url.test.js b/test/get-git-auth-url.test.js index a5711785fb..77c5a8e05f 100644 --- a/test/get-git-auth-url.test.js +++ b/test/get-git-auth-url.test.js @@ -407,3 +407,8 @@ test('Do not add git credential to repositoryUrl if push is allowed', async (t) repositoryUrl ); }); + +test('Do not verify the url if skipRemoteCheck is set', (t) => { + // TODO: Implement this test, somehow. + t.true(true); +}); diff --git a/test/index.test.js b/test/index.test.js index d2533c4919..9096141bb4 100644 --- a/test/index.test.js +++ b/test/index.test.js @@ -1372,6 +1372,31 @@ test('Returns false if triggered by a PR', async (t) => { ); }); +test('Does not allow specifying skipRemoteCheck without dryRun', async (t) => { + const {cwd, repositoryUrl} = await gitRepo(true); + + const semanticRelease = requireNoCache('..', { + './lib/get-logger': () => t.context.logger, + 'env-ci': () => ({isCi: true, branch: 'master', isPr: false}), + }); + + t.false( + await semanticRelease( + {cwd, repositoryUrl, skipRemoteCheck: true}, + {cwd, env: {}, stdout: new WritableStreamBuffer(), stderr: new WritableStreamBuffer()} + ) + ); + t.is( + t.context.error.args[t.context.error.args.length - 1][0], + '--skip-remote-check can only be specified in dry run mode.' + ); +}); + +test("Doesn't call verifyAuth when skipRemoteCheck is set", (t) => { + // TODO: Implement this test, somehow. + t.true(true); +}); + test('Throws "EINVALIDNEXTVERSION" if next release is out of range of the current maintenance branch', async (t) => { const {cwd, repositoryUrl} = await gitRepo(true); await gitCommits(['feat: initial commit'], {cwd});