add support for bitbucket and add more tests

This commit is contained in:
Kostas Bariotis 2017-08-20 23:46:44 +03:00
parent c66d2c92fc
commit 1baaf8de65
2 changed files with 187 additions and 34 deletions

View File

@ -5,6 +5,7 @@ const download = require('download');
const BbPromise = require('bluebird');
const fse = require('fs-extra');
const chalk = require('chalk');
const qs = require('querystring');
const { renameService } = require('./renameService');
const { ServerlessError } = require('../classes/Error');
@ -28,23 +29,15 @@ function dirExistsSync(dirPath) {
}
}
function downloadTemplateFromRepo(name, inputUrl) {
const url = URL.parse(inputUrl.replace(/\/$/, ''));
// check if url parameter is a valid url
if (!url.host) {
throw new ServerlessError('The URL you passed is not a valid URL');
}
function parseGitHubURL(url) {
const parts = url.pathname.split('/');
const parsedGitHubUrl = {
owner: parts[1],
repo: parts[2],
branch: parts[4] || 'master',
};
const isSubdirectory = parts.length > 4;
const owner = parts[1];
const repo = parts[2];
const branch = isSubdirectory ? parts[4] : 'master';
// validate if given url is a valid GitHub url
if (url.hostname !== 'github.com' || !parsedGitHubUrl.owner || !parsedGitHubUrl.repo) {
if (url.hostname !== 'github.com' || !owner || !repo) {
const errorMessage = [
'The URL must be a valid GitHub URL in the following format:',
' https://github.com/serverless/serverless',
@ -52,35 +45,120 @@ function downloadTemplateFromRepo(name, inputUrl) {
throw new ServerlessError(errorMessage);
}
let pathToDirectory = '';
for (let i = 5; i <= (parts.length - 1); i++) {
pathToDirectory = path.join(pathToDirectory, parts[i]);
}
const downloadUrl = [
'https://github.com/',
parsedGitHubUrl.owner,
owner,
'/',
parsedGitHubUrl.repo,
repo,
'/archive/',
parsedGitHubUrl.branch,
branch,
'.zip',
].join('');
const endIndex = parts.length - 1;
let dirName;
return {
owner,
repo,
branch,
downloadUrl,
isSubdirectory,
pathToDirectory,
};
}
function parseBitBucketURL(url) {
const parts = url.pathname.split('/');
const isSubdirectory = parts.length > 4;
const owner = parts[1];
const repo = parts[2];
const query = qs.parse(url.query);
const branch = 'at' in query ? query.at : 'master';
// validate if given url is a valid GitHub url
if (url.hostname !== 'bitbucket.org' || !owner || !repo) {
const errorMessage = [
'The URL must be a valid GitHub URL in the following format:',
' https://github.com/serverless/serverless',
].join('');
throw new ServerlessError(errorMessage);
}
let pathToDirectory = '';
for (let i = 5; i <= (parts.length - 1); i++) {
pathToDirectory = path.join(pathToDirectory, parts[i]);
}
const downloadUrl = [
'https://bitbucket.org/',
owner,
'/',
repo,
'/get/',
branch,
'.zip',
].join('');
return {
owner,
repo,
branch,
downloadUrl,
isSubdirectory,
pathToDirectory,
};
}
function parseRepoURL(inputUrl) {
if (!inputUrl) {
throw new ServerlessError('URL is required');
}
const url = URL.parse(inputUrl.replace(/\/$/, ''));
// check if url parameter is a valid url
if (!url.host) {
throw new ServerlessError('The URL you passed is not a valid URL');
}
switch (url.hostname) {
case 'github.com': {
return parseGitHubURL(url);
}
case 'bitbucket.org': {
return parseBitBucketURL(url);
}
default: {
const msg = 'The URL you passed is not one of the valid providers: "GitHub", "BitBucket".';
throw new ServerlessError(msg);
}
}
}
function downloadTemplateFromRepo(name, inputUrl) {
const repoInformation = parseRepoURL(inputUrl);
let serviceName;
let dirName;
let downloadServicePath;
// check if it's a directory or the whole repository
if (parts.length > 4) {
serviceName = parts[endIndex];
dirName = name || parts[endIndex];
// download the repo into a temporary directory
downloadServicePath = path.join(os.tmpdir(), parsedGitHubUrl.repo);
if (repoInformation.isSubdirectory) {
const folderName = repoInformation.pathToDirectory.split('/').splice(-1)[0];
serviceName = folderName;
dirName = name || folderName;
downloadServicePath = path.join(os.tmpdir(), repoInformation.repo);
} else {
serviceName = parsedGitHubUrl.repo;
dirName = name || parsedGitHubUrl.repo;
serviceName = repoInformation.repo;
dirName = name || repoInformation.repo;
downloadServicePath = path.join(process.cwd(), dirName);
}
const servicePath = path.join(process.cwd(), dirName);
const renamed = dirName !== (parts.length > 4 ? parts[endIndex] : parsedGitHubUrl.repo);
const renamed = dirName !== repoInformation.repo;
if (dirExistsSync(path.join(process.cwd(), dirName))) {
const errorMessage = `A folder named "${dirName}" already exists.`;
@ -91,16 +169,13 @@ function downloadTemplateFromRepo(name, inputUrl) {
// download service
return download(
downloadUrl,
repoInformation.downloadUrl,
downloadServicePath,
{ timeout: 30000, extract: true, strip: 1, mode: '755' }
).then(() => {
// if it's a directory inside of git
if (parts.length > 4) {
let directory = downloadServicePath;
for (let i = 5; i <= endIndex; i++) {
directory = path.join(directory, parts[i]);
}
if (repoInformation.isSubdirectory) {
const directory = path.join(downloadServicePath, repoInformation.pathToDirectory);
copyDirContentsSync(directory, servicePath);
fse.removeSync(downloadServicePath);
}
@ -113,4 +188,5 @@ function downloadTemplateFromRepo(name, inputUrl) {
module.exports = {
downloadTemplateFromRepo,
parseRepoURL,
};

View File

@ -13,6 +13,8 @@ const readFileSync = require('./fs/readFileSync');
const remove = BbPromise.promisify(fse.remove);
const { parseRepoURL } = require('./downloadTemplateFromRepo');
describe('downloadTemplateFromRepo', () => {
let downloadTemplateFromRepo;
let downloadStub;
@ -121,4 +123,79 @@ describe('downloadTemplateFromRepo', () => {
});
});
});
describe('parseRepoURL', () => {
it('should throw an error if no URL is provided', () => {
expect(parseRepoURL).to.throw(Error);
});
it('should throw an error if URL is not valid', () => {
try {
parseRepoURL('non_valid_url')
} catch(e) {
expect(e).to.be.an.instanceOf(Error);
}
});
it('should throw an error if URL is not of valid provider', () => {
try {
parseRepoURL('https://kostasbariotis.com/repo/owner');
} catch(e) {
expect(e).to.be.an.instanceOf(Error);
}
});
it('should parse a valid GitHub URL', () => {
const output = parseRepoURL('https://github.com/serverless/serverless');
expect(output).to.deep.eq({
owner: 'serverless',
repo: 'serverless',
branch: 'master',
downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip',
isSubdirectory: false,
pathToDirectory: '',
});
});
it('should parse a valid GitHub URL with subdirectory', () => {
const output = parseRepoURL('https://github.com/serverless/serverless/tree/master/assets');
expect(output).to.deep.eq({
owner: 'serverless',
repo: 'serverless',
branch: 'master',
downloadUrl: 'https://github.com/serverless/serverless/archive/master.zip',
isSubdirectory: true,
pathToDirectory: 'assets',
});
});
it('should parse a valid BitBucket URL ', () => {
const output = parseRepoURL('https://bitbucket.org/atlassian/localstack');
expect(output).to.deep.eq({
owner: 'atlassian',
repo: 'localstack',
branch: 'master',
downloadUrl: 'https://bitbucket.org/atlassian/localstack/get/master.zip',
isSubdirectory: false,
pathToDirectory: '',
});
});
it('should parse a valid BitBucket URL with subdirectory', () => {
const output = parseRepoURL('https://bitbucket.org/atlassian/localstack/src/85870856fd6941ae75c0fa946a51cf756ff2f53a/localstack/dashboard/?at=mvn');
expect(output).to.deep.eq({
owner: 'atlassian',
repo: 'localstack',
branch: 'mvn',
downloadUrl: 'https://bitbucket.org/atlassian/localstack/get/mvn.zip',
isSubdirectory: true,
pathToDirectory: 'localstack/dashboard',
});
});
});
});