Skip to content

Commit

Permalink
feat: add --skip-remote-check option
Browse files Browse the repository at this point in the history
This new option will bypass checking remote access. It only works in dry
run mode. This addresses semantic-release#2232.
  • Loading branch information
thislooksfun committed Apr 29, 2022
1 parent ab45ab1 commit 604504f
Show file tree
Hide file tree
Showing 6 changed files with 87 additions and 41 deletions.
1 change: 1 addition & 0 deletions cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -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'})
Expand Down
39 changes: 24 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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);

Expand Down
56 changes: 30 additions & 26 deletions lib/get-git-auth-url.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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];
}
}
}
}
Expand Down
2 changes: 2 additions & 0 deletions test/cli.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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}});

Expand All @@ -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);
});
Expand Down
5 changes: 5 additions & 0 deletions test/get-git-auth-url.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
25 changes: 25 additions & 0 deletions test/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -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});
Expand Down

0 comments on commit 604504f

Please sign in to comment.