diff --git a/package-lock.json b/package-lock.json index 5069b12495..e313a88dc9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1742,6 +1742,29 @@ } } }, + "@kwsites/file-exists": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/file-exists/-/file-exists-1.1.1.tgz", + "integrity": "sha512-m9/5YGR18lIwxSFDwfE3oA7bWuq9kdau6ugN4H2rJeyhFQZcG9AgSHkQtSD15a8WvTgfz9aikZMrKPHvbpqFiw==", + "requires": { + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, + "@kwsites/promise-deferred": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@kwsites/promise-deferred/-/promise-deferred-1.1.1.tgz", + "integrity": "sha512-GaHYm+c0O9MjZRu0ongGBRbinu8gVAMd2UZjji6jVmqKtZluZnptXGWhz1E8j8D2HJ3f/yMxKAUC0b+57wncIw==" + }, "@lerna/add": { "version": "3.21.0", "resolved": "https://registry.npmjs.org/@lerna/add/-/add-3.21.0.tgz", @@ -15033,6 +15056,26 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==" }, + "simple-git": { + "version": "2.19.0", + "resolved": "https://registry.npmjs.org/simple-git/-/simple-git-2.19.0.tgz", + "integrity": "sha512-OZKxX9zHeH8JbCo8DMlERE8RsWox7Q9Jmh+lJKw/Zla8HQkiVP5I4LF5ZRfkud9XU27ZNgpccHezg1lbHw6VzQ==", + "requires": { + "@kwsites/file-exists": "^1.1.1", + "@kwsites/promise-deferred": "^1.1.1", + "debug": "^4.1.1" + }, + "dependencies": { + "debug": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", + "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", + "requires": { + "ms": "^2.1.1" + } + } + } + }, "simple-swizzle": { "version": "0.2.2", "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", diff --git a/packages/cli/index.js b/packages/cli/index.js index 34c4ccd84d..09213f634f 100755 --- a/packages/cli/index.js +++ b/packages/cli/index.js @@ -298,9 +298,7 @@ program const rootFolder = path.resolve(process.cwd()); const outputRoot = path.join(rootFolder, '_site'); new Site(rootFolder, outputRoot, undefined, undefined, options.siteConfig).deploy(options.travis) - .then(() => { - logger.info('Deployed!'); - }) + .then(depUrl => (depUrl !== null ? logger.info(`Deployed at ${depUrl}!`) : logger.info('Deployed!'))) .catch(handleError); printHeader(); }); diff --git a/packages/core/package.json b/packages/core/package.json index 5812c01a43..a6ee2dc46d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -58,6 +58,7 @@ "nunjucks": "^3.2.0", "path-is-inside": "^1.0.2", "progress": "^2.0.3", + "simple-git": "^2.17.0", "walk-sync": "^2.0.2", "winston": "^2.4.4" }, diff --git a/packages/core/src/Site/index.js b/packages/core/src/Site/index.js index f96be450d9..2bd6f8e159 100644 --- a/packages/core/src/Site/index.js +++ b/packages/core/src/Site/index.js @@ -6,6 +6,7 @@ const path = require('path'); const Promise = require('bluebird'); const ProgressBar = require('progress'); const walkSync = require('walk-sync'); +const simpleGit = require('simple-git'); const SiteConfig = require('./SiteConfig'); const Page = require('../Page'); @@ -18,6 +19,7 @@ const FsUtil = require('../utils/fsUtil'); const delay = require('../utils/delay'); const logger = require('../utils/logger'); const utils = require('../utils'); +const gitUtil = require('../utils/git'); const { LAYOUT_DEFAULT_NAME, @@ -1249,6 +1251,7 @@ class Site { branch: 'gh-pages', message: 'Site Update.', repo: '', + remote: 'origin', }; process.env.NODE_DEBUG = 'gh-pages'; return new Promise((resolve, reject) => { @@ -1298,13 +1301,70 @@ class Site { }; } - return publish(basePath, options); + publish(basePath, options); + return options; }) - .then(resolve) + .then((options) => { + const git = simpleGit({ baseDir: process.cwd() }); + options.remote = defaultDeployConfig.remote; + return Site.getDeploymentUrl(git, options); + }) + .then(depUrl => resolve(depUrl)) .catch(reject); }); } + /** + * Gets the deployed website's url, returning null if there was an error retrieving it. + */ + static getDeploymentUrl(git, options) { + const HTTPS_PREAMBLE = 'https://'; + const SSH_PREAMBLE = 'git@github.com:'; + const GITHUB_IO_PART = 'github.io'; + + // https://.github.io// + function constructGhPagesUrl(remoteUrl) { + if (!remoteUrl) { + return null; + } + if (remoteUrl.startsWith(HTTPS_PREAMBLE)) { + // https://github.com//.git (HTTPS) + const parts = remoteUrl.split('/'); + const repoName = parts[parts.length - 1].toLowerCase(); + const name = parts[parts.length - 2].toLowerCase(); + return `https://${name}.${GITHUB_IO_PART}/${repoName}`; + } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { + // git@github.com:/.git (SSH) + const parts = remoteUrl.split('/'); + const repoName = parts[parts.length - 1].toLowerCase(); + const name = parts[0].substring(SSH_PREAMBLE.length); + return `https://${name}.${GITHUB_IO_PART}/${repoName}`; + } + return null; + } + + const { remote, branch, repo } = options; + const cnamePromise = gitUtil.getRemoteBranchFile(git, 'blob', remote, branch, 'CNAME'); + const remoteUrlPromise = gitUtil.getRemoteUrl(git, remote); + const promises = [cnamePromise, remoteUrlPromise]; + + return Promise.all(promises) + .then((results) => { + const cname = results[0].trim(); + const remoteUrl = results[1].trim(); + if (cname) { + return cname; + } else if (repo) { + return constructGhPagesUrl(repo); + } + return constructGhPagesUrl(remoteUrl); + }) + .catch((err) => { + logger.error(err); + return null; + }); + } + _setTimestampVariable() { const options = { weekday: 'short', diff --git a/packages/core/src/utils/git.js b/packages/core/src/utils/git.js new file mode 100644 index 0000000000..2b9c571dde --- /dev/null +++ b/packages/core/src/utils/git.js @@ -0,0 +1,27 @@ +/** + * Contains methods using simple-git to perform commands relating to git. + */ +module.exports = { + /** + * Returns the contents of a remote file, undefined if an error was encountered. + * See: https://git-scm.com/docs/git-cat-file for accepted values for each input. + */ + async getRemoteBranchFile(simpleGit, type, remote, branch, fileName) { + try { + const catFileTarget = `${remote}/${branch}:${fileName}`; + return await simpleGit.catFile([type, catFileTarget]); + } catch (e) { + return undefined; + } + }, + /** + * Returns the contents of a remote url (https or ssh), undefined if an error was encountered. + */ + async getRemoteUrl(simpleGit, remote) { + try { + return await simpleGit.remote(['get-url', remote]); + } catch (e) { + return undefined; + } + }, +}; diff --git a/packages/core/test/unit/Site.test.js b/packages/core/test/unit/Site.test.js index e0f4d8ae77..1ac1ba7af0 100644 --- a/packages/core/test/unit/Site.test.js +++ b/packages/core/test/unit/Site.test.js @@ -27,6 +27,14 @@ jest.mock('fs'); jest.mock('walk-sync'); jest.mock('gh-pages'); jest.mock('../../src/Page'); +jest.mock('simple-git', () => () => ({ + ...jest.requireActual('simple-git')(), + // A test file should reduce dependencies on external libraries; use pure js functions instead. + // eslint-disable-next-line lodash/prefer-constant + catFile: jest.fn(() => 'mock-test-website.com'), + // eslint-disable-next-line lodash/prefer-constant + remote: jest.fn(() => 'https://github.com/mockName/mockRepo.git'), +})); afterEach(() => fs.vol.reset()); @@ -470,7 +478,12 @@ test('Site deploys with default settings', async () => { await site.deploy(); expect(ghpages.dir).toEqual('_site'); expect(ghpages.options) - .toEqual({ branch: 'gh-pages', message: 'Site Update.', repo: '' }); + .toEqual({ + branch: 'gh-pages', + message: 'Site Update.', + repo: '', + remote: 'origin', + }); }); test('Site deploys with custom settings', async () => { @@ -490,7 +503,12 @@ test('Site deploys with custom settings', async () => { await site.deploy(); expect(ghpages.dir).toEqual('_site'); expect(ghpages.options) - .toEqual({ branch: 'master', message: 'Custom Site Update.', repo: 'https://github.com/USER/REPO.git' }); + .toEqual({ + branch: 'master', + message: 'Custom Site Update.', + repo: 'https://github.com/USER/REPO.git', + remote: 'origin', + }); }); test('Site should not deploy without a built site', async () => {