diff --git a/cmd/cmd.js b/cmd/cmd.js index ea226f5c3..ff04da366 100644 --- a/cmd/cmd.js +++ b/cmd/cmd.js @@ -94,7 +94,7 @@ class Cmd { .description(__('New Application')) .option('--simple', __('create a barebones project meant only for contract development')) .option('--locale [locale]', __('language to use (default: en)')) - .option('--template [name/url]', __('download a template using a known name or a GitHub repository URL')) + .option('--template ', __('download a template using a known name or a git host URL')) .action(function(name, options) { i18n.setOrDetectLocale(options.locale); if (name === undefined) { diff --git a/lib/utils/template_generator.js b/lib/utils/template_generator.js index 59d042dbf..60db9ae12 100644 --- a/lib/utils/template_generator.js +++ b/lib/utils/template_generator.js @@ -1,4 +1,5 @@ let fs = require('../core/fs.js'); +let hostedGitInfo = require('hosted-git-info'); let utils = require('./utils.js'); class TemplateGenerator { @@ -17,9 +18,17 @@ class TemplateGenerator { const fspath = utils.joinPath(destinationFolder, name); this.checkPathExists(fspath); const self = this; - let {url, filePath} = this.getExternalProject(uri); + let ext; + try { + ext = this.getExternalProject(uri); + } catch (e) { + console.error(utils.errorMessage(e).red); + process.exit(1); + } + let {url, filePath, browse} = ext; let tmpFilePath = fs.tmpDir(filePath); - console.log(__('Installing Template from ' + uri + '....').green); + console.log(__('Installing template from ' + browse).green); + console.log(__('Downloading template...').green); fs.mkdirpSync(utils.dirname(tmpFilePath)); utils.downloadFile(url, tmpFilePath, (err) => { @@ -45,12 +54,15 @@ class TemplateGenerator { generate(destinationFolder, name) { const fspath = utils.joinPath(destinationFolder, name); this.checkPathExists(fspath); - console.log(__('Initializing Embark Template...').green); - + console.log(__('Initializing Embark template...').green); let templatePath = fs.embarkPath(utils.joinPath('templates', this.templateName)); fs.copySync(templatePath, fspath); - this.installTemplate(fspath, name, (name === 'embark_demo')); + this.installTemplate( + fspath, + name, + (this.templateName === 'boilerplate' || this.templateName === 'demo') + ); if (name === 'embark_demo') { console.log('-------------------'.yellow); @@ -74,79 +86,32 @@ class TemplateGenerator { if (installPackages) { console.log(__('Installing packages...').green); - utils.runCmd('npm install'); + utils.runCmd('npm install', null, (err) => { + if (err) { + return console.error(err.red); + } + console.log(__('Init complete').green); + console.log('\n' + __('App ready at ').green + templatePath); + }); } - - console.log(__('Init complete').green); - console.log('\n' + __('App ready at ').green + templatePath); - } - - extractGithubUrlAndFolder(uri){ - - /* first matching group is the url, second the repoPart and third the branch with a hash in the beginning (if existing) - e.g. (["git@github.com/status-im/dappcon-workshop-dapp#master", "status-im/dappcon-workshop-dapp", "#master" ]) - should work with all formats of the following: - * git@github.com/status-im/dappcon-workshop-dapp#start-here - * git@github.com/status-im/dappcon-workshop-dapp - * http://www.github.com/status-im/dappcon-workshop-dapp - * https://www.github.com/status-im/dappcon-workshop-dapp - * github.com/status-im/dappcon-workshop-dapp#start-here - - sadly it doesn't extract from http(s)://github.com/status-im/dappcon-workshop-dapp/tree/start-here - thats why we have a special case later - */ - const match = uri.match(/github\.com+\/(.+?)(#.*)?$/); - const githubPart = "https://github.com/"; - let repoPart = match !== null? match[1] : null; - let branchName = match !== null? match[2] : null; - - if (branchName && branchName !== '#'){ - branchName = branchName.substring(1); - } else { - branchName = "master"; - } - - let url, folder; - if (uri.includes("/tree")){ - //e.g http(s)://github.com/status-im/dappcon-workshop-dapp/tree/start-here - let repoPartAndBranch = repoPart.split("/tree/"); - repoPart = repoPartAndBranch[0]; - branchName = repoPartAndBranch[1]; - url = "https://github.com/" + repoPart + "/archive/"+ branchName +".zip"; - folder = repoPart + "/" + branchName; - } else if (repoPart !== undefined) { - url = githubPart + repoPart + "/archive/" + branchName + ".zip"; - folder = repoPart + "/" + branchName; - } - - return { - 'url': url, - 'folder': folder - }; } getExternalProject(uri) { - let url, folder; - const regex = /^((git@)?(www\.)?github\.com\/)|(https?:\/\/)/; - if (!uri.match(regex) && uri.split('/').length >= 2) { - //e.g embark-framework/embark, embark-framework/embark#branch, embark-framework/embark#features/branch - let repoPartAndBranch = uri.split('#'); - let repoPart = repoPartAndBranch[0]; - let branchName = (repoPartAndBranch.length === 2)? repoPartAndBranch[1] : "master"; - url = "https://github.com/" + repoPart + "/archive/"+ branchName + ".zip"; - folder = repoPart + "/" + branchName; - } else if (uri.indexOf('/') === -1) { - url = "https://github.com/embark-framework/embark-" + uri + "-template/archive/master.zip"; - folder = "embark-framework/embark-" + uri + "-template"; - } else { - let urlAndFolder = this.extractGithubUrlAndFolder(uri); - url = urlAndFolder.url; - folder = urlAndFolder.folder; + let url, folder, hgi; + hgi = hostedGitInfo.fromUrl(uri); + if (!hgi || hgi.user.includes('#')) { + let templateAndBranch = uri.split('#'); + templateAndBranch[0] = `embark-framework/embark-${templateAndBranch[0]}-template`; + hgi = hostedGitInfo.fromUrl(templateAndBranch.join('#')); } + if(!hgi) { throw new Error('Unsupported template name or git host URL'); } + url = hgi.tarball(); + folder = `${hgi.user}/${hgi.project}/${hgi.committish || 'master'}`; return { url, - filePath: utils.joinPath(".embark/templates/", folder, "archive.zip") + filePath: utils.joinPath(".embark/templates/", folder, "archive.zip"), + browse: decodeURIComponent(hgi.browse()) }; } } diff --git a/package.json b/package.json index a24c45d31..2fec445d4 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "glob": "7.1.3", "globule": "1.2.1", "hard-source-webpack-plugin": "0.11.2", + "hosted-git-info": "2.7.1", "http-proxy": "1.17.0", "http-shutdown": "1.2.0", "i18n": "0.8.3", diff --git a/test/template.js b/test/template.js index 796484ff6..aee28cc64 100644 --- a/test/template.js +++ b/test/template.js @@ -10,63 +10,117 @@ describe('TemplateGenerator', function () { templateGenerator = new TemplateGenerator(); }); - describe('with github link', function () { + describe('with named template', function () { - it('return correct zip filename for https link', function () { - let result = templateGenerator.getExternalProject("https://github.com/embark-framework/embark"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/master.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + it('returns correct info for named template', function () { + let result = templateGenerator.getExternalProject("typescript"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark-typescript-template/tar.gz/master"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark-typescript-template/master/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark-typescript-template"); + + result = templateGenerator.getExternalProject("typescript#features/branch"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark-typescript-template/tar.gz/features%2Fbranch"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark-typescript-template/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark-typescript-template/tree/features/branch"); }); - it('return correct zip filename for https link with branch specified', function () { - let result = templateGenerator.getExternalProject("https://github.com/embark-framework/embark/tree/develop"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/develop.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/develop/archive.zip"); - }); + }); - it('return correct zip filename for http link', function () { + describe('with git host URL', function () { + + it('returns correct info for GitHub URL', function () { let result = templateGenerator.getExternalProject("http://github.com/embark-framework/embark"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/master.zip"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/master"); + + result = templateGenerator.getExternalProject("https://github.com/embark-framework/embark"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/master"); assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark"); + + result = templateGenerator.getExternalProject("https://github.com/embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/features%2Fbranch"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark/tree/features/branch"); }); - it('return correct zip filename for http link with branch specified', function () { - let result = templateGenerator.getExternalProject("http://github.com/embark-framework/embark/tree/develop"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/develop.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/develop/archive.zip"); - }); - - it('return correct zip filename without protocol specified ', function () { - let result = templateGenerator.getExternalProject("github.com/embark-framework/embark"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/master.zip"); + it('returns correct info for Bitbucket URL', function () { + let result = templateGenerator.getExternalProject("https://bitbucket.org/embark-framework/embark"); + assert.strictEqual(result.url, "https://bitbucket.org/embark-framework/embark/get/master.tar.gz"); assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://bitbucket.org/embark-framework/embark"); + + result = templateGenerator.getExternalProject("https://bitbucket.org/embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://bitbucket.org/embark-framework/embark/get/features%2Fbranch.tar.gz"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://bitbucket.org/embark-framework/embark/src/features/branch"); }); - it('return correct zip filename without protocol with branch specified', function () { - let result = templateGenerator.getExternalProject("github.com/embark-framework/embark#develop"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/develop.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/develop/archive.zip"); - }); - - it('return correct zip filename with just username/repo specified', function () { - let result = templateGenerator.getExternalProject("embark-framework/embark"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/master.zip"); + it('returns correct info for GitLab URL', function () { + let result = templateGenerator.getExternalProject("https://gitlab.com/embark-framework/embark"); + assert.strictEqual(result.url, "https://gitlab.com/embark-framework/embark/repository/archive.tar.gz?ref=master"); assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://gitlab.com/embark-framework/embark"); + + result = templateGenerator.getExternalProject("https://gitlab.com/embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://gitlab.com/embark-framework/embark/repository/archive.tar.gz?ref=features%2Fbranch"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://gitlab.com/embark-framework/embark/tree/features/branch"); }); - it('return correct zip filename with just username/repo and branch specified', function () { - let result = templateGenerator.getExternalProject("embark-framework/embark#develop"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark/archive/develop.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/develop/archive.zip"); + }); + + describe('with git host shortcut', function () { + + it('returns correct info for GitHub shortcut', function () { + let result = templateGenerator.getExternalProject("github:embark-framework/embark"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/master"); + + result = templateGenerator.getExternalProject("embark-framework/embark"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/master"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark"); + + result = templateGenerator.getExternalProject("embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://codeload.github.com/embark-framework/embark/tar.gz/features%2Fbranch"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://github.com/embark-framework/embark/tree/features/branch"); }); - it('return correct zip filename with just embark template specified', function () { - let result = templateGenerator.getExternalProject("react"); - assert.strictEqual(result.url, "https://github.com/embark-framework/embark-react-template/archive/master.zip"); - assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark-react-template/archive.zip"); + it('returns correct info for Bitbucket shortcut', function () { + let result = templateGenerator.getExternalProject("bitbucket:embark-framework/embark"); + assert.strictEqual(result.url, "https://bitbucket.org/embark-framework/embark/get/master.tar.gz"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://bitbucket.org/embark-framework/embark"); + + result = templateGenerator.getExternalProject("bitbucket:embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://bitbucket.org/embark-framework/embark/get/features%2Fbranch.tar.gz"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://bitbucket.org/embark-framework/embark/src/features/branch"); }); + + it('returns correct info for GitLab shortcut', function () { + let result = templateGenerator.getExternalProject("gitlab:embark-framework/embark"); + assert.strictEqual(result.url, "https://gitlab.com/embark-framework/embark/repository/archive.tar.gz?ref=master"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/master/archive.zip"); + assert.strictEqual(result.browse, "https://gitlab.com/embark-framework/embark"); + + result = templateGenerator.getExternalProject("gitlab:embark-framework/embark#features/branch"); + assert.strictEqual(result.url, "https://gitlab.com/embark-framework/embark/repository/archive.tar.gz?ref=features%2Fbranch"); + assert.strictEqual(result.filePath.replace(/\\/g,'/'), ".embark/templates/embark-framework/embark/features/branch/archive.zip"); + assert.strictEqual(result.browse, "https://gitlab.com/embark-framework/embark/tree/features/branch"); + }); + + }); + + describe('with unsupported template specifier', function () { + + it('raises an exception', function () { + assert.throws(() => templateGenerator.getExternalProject("bad://format"), /Unsupported/); + assert.throws(() => templateGenerator.getExternalProject("bad://format#/also/bad"), /Unsupported/); + assert.throws(() => templateGenerator.getExternalProject(/force an error/), Error); + }); + }); }); }); -