diff --git a/packages/create-docusaurus/templates/facebook/README.md b/packages/create-docusaurus/templates/facebook/README.md index 1b65ca0890bd..6ffad61fe7c7 100644 --- a/packages/create-docusaurus/templates/facebook/README.md +++ b/packages/create-docusaurus/templates/facebook/README.md @@ -26,8 +26,16 @@ This command generates static content into the `build` directory and can be serv ### Deployment +Using SSH: + +``` +$ USE_SSH=true yarn deploy +``` + +Not using SSH: + ``` -$ GIT_USER= USE_SSH=true yarn deploy +$ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/packages/create-docusaurus/templates/shared/README.md b/packages/create-docusaurus/templates/shared/README.md index 55d0c3ef41c9..aaba2fa1e16e 100644 --- a/packages/create-docusaurus/templates/shared/README.md +++ b/packages/create-docusaurus/templates/shared/README.md @@ -26,8 +26,16 @@ This command generates static content into the `build` directory and can be serv ### Deployment +Using SSH: + +``` +$ USE_SSH=true yarn deploy +``` + +Not using SSH: + ``` -$ GIT_USER= USE_SSH=true yarn deploy +$ GIT_USER= yarn deploy ``` If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. diff --git a/packages/docusaurus/src/commands/__tests__/buildRemoteBranchUrl.test.ts b/packages/docusaurus/src/commands/__tests__/buildRemoteBranchUrl.test.ts deleted file mode 100644 index 46f445fc5bf0..000000000000 --- a/packages/docusaurus/src/commands/__tests__/buildRemoteBranchUrl.test.ts +++ /dev/null @@ -1,57 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -import {buildUrl} from '../buildRemoteBranchUrl'; - -describe('remoteeBranchUrl', () => { - test('should build a normal ssh url', async () => { - const url = buildUrl( - 'github.com', - undefined, - undefined, - 'facebook', - 'docusaurus', - true, - ); - expect(url).toEqual('git@github.com:facebook/docusaurus.git'); - }); - test('should build a ssh url with port', async () => { - const url = buildUrl( - 'github.com', - '422', - undefined, - 'facebook', - 'docusaurus', - true, - ); - expect(url).toEqual('ssh://git@github.com:422/facebook/docusaurus.git'); - }); - test('should build a normal http url', async () => { - const url = buildUrl( - 'github.com', - undefined, - 'user:pass', - 'facebook', - 'docusaurus', - false, - ); - expect(url).toEqual('https://user:pass@github.com/facebook/docusaurus.git'); - }); - test('should build a normal http url', async () => { - const url = buildUrl( - 'github.com', - '5433', - 'user:pass', - 'facebook', - 'docusaurus', - false, - ); - expect(url).toEqual( - 'https://user:pass@github.com:5433/facebook/docusaurus.git', - ); - }); -}); diff --git a/packages/docusaurus/src/commands/__tests__/deploy.test.ts b/packages/docusaurus/src/commands/__tests__/deploy.test.ts new file mode 100644 index 000000000000..f57bd9aae241 --- /dev/null +++ b/packages/docusaurus/src/commands/__tests__/deploy.test.ts @@ -0,0 +1,62 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {buildSshUrl, buildHttpsUrl, hasSSHProtocol} from '../deploy'; + +describe('remoteBranchUrl', () => { + test('should build a normal ssh url', () => { + const url = buildSshUrl('github.com', 'facebook', 'docusaurus'); + expect(url).toEqual('git@github.com:facebook/docusaurus.git'); + }); + test('should build a ssh url with port', () => { + const url = buildSshUrl('github.com', 'facebook', 'docusaurus', '422'); + expect(url).toEqual('ssh://git@github.com:422/facebook/docusaurus.git'); + }); + test('should build a normal http url', () => { + const url = buildHttpsUrl( + 'user:pass', + 'github.com', + 'facebook', + 'docusaurus', + ); + expect(url).toEqual('https://user:pass@github.com/facebook/docusaurus.git'); + }); + test('should build a normal http url', () => { + const url = buildHttpsUrl( + 'user:pass', + 'github.com', + 'facebook', + 'docusaurus', + '5433', + ); + expect(url).toEqual( + 'https://user:pass@github.com:5433/facebook/docusaurus.git', + ); + }); +}); + +describe('hasSSHProtocol', () => { + test('should recognize explicit SSH protocol', () => { + const url = 'ssh://git@github.com:422/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toEqual(true); + }); + + test('should recognize implied SSH protocol', () => { + const url = 'git@github.com:facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toEqual(true); + }); + + test('should not recognize HTTPS with credentials', () => { + const url = 'https://user:pass@github.com/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toEqual(false); + }); + + test('should not recognize plain HTTPS URL', () => { + const url = 'https://github.com:5433/facebook/docusaurus.git'; + expect(hasSSHProtocol(url)).toEqual(false); + }); +}); diff --git a/packages/docusaurus/src/commands/buildRemoteBranchUrl.ts b/packages/docusaurus/src/commands/buildRemoteBranchUrl.ts deleted file mode 100644 index cfb1b8c03dbd..000000000000 --- a/packages/docusaurus/src/commands/buildRemoteBranchUrl.ts +++ /dev/null @@ -1,50 +0,0 @@ -/** - * Copyright (c) Facebook, Inc. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - */ - -export function buildUrl( - githubHost: string, - githubPort: string | undefined, - gitCredentials: string | undefined, - organizationName: string, - projectName: string, - useSSH: boolean | undefined, -): string { - return useSSH - ? buildSshUrl(githubHost, organizationName, projectName, githubPort) - : buildHttpsUrl( - gitCredentials, - githubHost, - organizationName, - projectName, - githubPort, - ); -} - -function buildSshUrl( - githubHost: string, - organizationName: string, - projectName: string, - githubPort: string | undefined, -) { - if (githubPort) { - return `ssh://git@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; - } - return `git@${githubHost}:${organizationName}/${projectName}.git`; -} - -function buildHttpsUrl( - gitCredentials: string | undefined, - githubHost: string, - organizationName: string, - projectName: string, - githubPort: string | undefined, -) { - if (githubPort) { - return `https://${gitCredentials}@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; - } - return `https://${gitCredentials}@${githubHost}/${organizationName}/${projectName}.git`; -} diff --git a/packages/docusaurus/src/commands/deploy.ts b/packages/docusaurus/src/commands/deploy.ts index 6b7f0d135d92..75c37d05db25 100644 --- a/packages/docusaurus/src/commands/deploy.ts +++ b/packages/docusaurus/src/commands/deploy.ts @@ -13,7 +13,6 @@ import build from './build'; import {BuildCLIOptions} from '@docusaurus/types'; import path from 'path'; import os from 'os'; -import {buildUrl} from './buildRemoteBranchUrl'; // GIT_PASS env variable should not appear in logs function obfuscateGitPass(str: string) { @@ -38,6 +37,43 @@ function shellExecLog(cmd: string) { } } +export function buildSshUrl( + githubHost: string, + organizationName: string, + projectName: string, + githubPort?: string, +): string { + if (githubPort) { + return `ssh://git@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; + } + return `git@${githubHost}:${organizationName}/${projectName}.git`; +} + +export function buildHttpsUrl( + gitCredentials: string, + githubHost: string, + organizationName: string, + projectName: string, + githubPort?: string, +): string { + if (githubPort) { + return `https://${gitCredentials}@${githubHost}:${githubPort}/${organizationName}/${projectName}.git`; + } + return `https://${gitCredentials}@${githubHost}/${organizationName}/${projectName}.git`; +} + +export function hasSSHProtocol(sourceRepoUrl: string): boolean { + try { + if (new URL(sourceRepoUrl).protocol === 'ssh:') { + return true; + } + return false; + } catch { + // Fails when there isn't a protocol + return /^([\w-]+@)?[\w.-]+:[\w./_-]+(\.git)?/.test(sourceRepoUrl); // git@github.com:facebook/docusaurus.git + } +} + export default async function deploy( siteDir: string, cliOptions: Partial = {}, @@ -63,15 +99,32 @@ This behavior can have SEO impacts and create relative link issues. throw new Error('Git not installed or on the PATH!'); } - const gitUser = process.env.GIT_USER; - if (!gitUser) { - throw new Error('Please set the GIT_USER environment variable!'); - } + // Source repo is the repo from where the command is invoked + const sourceRepoUrl = shell + .exec('git config --get remote.origin.url', {silent: true}) + .stdout.trim(); - // The branch that contains the latest docs changes that will be deployed. - const currentBranch = + // The source branch; defaults to the currently checked out branch + const sourceBranch = process.env.CURRENT_BRANCH || - shell.exec('git rev-parse --abbrev-ref HEAD').stdout.trim(); + shell.exec('git rev-parse --abbrev-ref HEAD', {silent: true}).stdout.trim(); + + const gitUser = process.env.GIT_USER; + + let useSSH = + process.env.USE_SSH !== undefined && + process.env.USE_SSH.toLowerCase() === 'true'; + + if (!gitUser && !useSSH) { + // If USE_SSH is unspecified: try inferring from repo URL + if (process.env.USE_SSH === undefined && hasSSHProtocol(sourceRepoUrl)) { + useSSH = true; + } else { + throw new Error( + 'Please set the GIT_USER environment variable, or explicitly specify USE_SSH instead!', + ); + } + } const organizationName = process.env.ORGANIZATION_NAME || @@ -107,8 +160,7 @@ This behavior can have SEO impacts and create relative link issues. // Organization deploys looks like: // - Git repo: https://github.com//.github.io // - Site url: https://.github.io - const isGitHubPagesOrganizationDeploy = - projectName.indexOf('.github.io') !== -1; + const isGitHubPagesOrganizationDeploy = projectName.includes('.github.io'); if ( isGitHubPagesOrganizationDeploy && !process.env.DEPLOYMENT_BRANCH && @@ -127,38 +179,39 @@ You can also set the deploymentBranch property in docusaurus.config.js .`); process.env.GITHUB_HOST || siteConfig.githubHost || 'github.com'; const githubPort = process.env.GITHUB_PORT || siteConfig.githubPort; - const gitPass: string | undefined = process.env.GIT_PASS; - let gitCredentials = `${gitUser}`; - if (gitPass) { - gitCredentials = `${gitCredentials}:${gitPass}`; + let remoteBranch: string; + if (useSSH) { + remoteBranch = buildSshUrl( + githubHost, + organizationName, + projectName, + githubPort, + ); + } else { + const gitPass = process.env.GIT_PASS; + const gitCredentials = gitPass ? `${gitUser!}:${gitPass}` : gitUser!; + remoteBranch = buildHttpsUrl( + gitCredentials, + githubHost, + organizationName, + projectName, + githubPort, + ); } - const useSSH = process.env.USE_SSH; - const remoteBranch = buildUrl( - githubHost, - githubPort, - gitCredentials, - organizationName, - projectName, - useSSH !== undefined && useSSH.toLowerCase() === 'true', - ); - console.log( `${chalk.cyan('Remote branch:')} ${obfuscateGitPass(remoteBranch)}`, ); // Check if this is a cross-repo publish. - const currentRepoUrl = shell - .exec('git config --get remote.origin.url') - .stdout.trim(); - const crossRepoPublish = !currentRepoUrl.endsWith( + const crossRepoPublish = !sourceRepoUrl.endsWith( `${organizationName}/${projectName}.git`, ); // We don't allow deploying to the same branch unless it's a cross publish. - if (currentBranch === deploymentBranch && !crossRepoPublish) { + if (sourceBranch === deploymentBranch && !crossRepoPublish) { throw new Error( - `You cannot deploy from this branch (${currentBranch}).` + + `You cannot deploy from this branch (${sourceBranch}).` + '\nYou will need to checkout to a different branch!', ); } diff --git a/website/docs/deployment.mdx b/website/docs/deployment.mdx index 962d6052b334..09030520aecb 100644 --- a/website/docs/deployment.mdx +++ b/website/docs/deployment.mdx @@ -138,19 +138,12 @@ By default, GitHub Pages runs published files through [Jekyll](https://jekyllrb. ### Environment settings {#environment-settings} -Specify the Git user as an environment variable. - -| Name | Description | -| --- | --- | -| `GIT_USER` | The username for a GitHub account that **has push access to the deployment repo**. For your own repositories, this will usually be your GitHub username. | - -Optional parameters, also set as environment variables: - | Name | Description | | --- | --- | -| `USE_SSH` | Set to `true` to use SSH instead of the default HTTPS for the connection to the GitHub repo. | +| `USE_SSH` | Set to `true` to use SSH instead of the default HTTPS for the connection to the GitHub repo. If the source repo URL is an SSH URL (e.g. `git@github.com:facebook/docusaurus.git`), `USE_SSH` is inferred to be `true`. | +| `GIT_USER` | The username for a GitHub account that **has push access to the deployment repo**. For your own repositories, this will usually be your GitHub username. Required if not using SSH, and ignored otherwise. | +| `GIT_PASS` | Personal access token of the git user (specified by `GIT_USER`), to facilitate non-interactive deployment (e.g. continuous deployment) | | `CURRENT_BRANCH` | The source branch. Usually, the branch will be `main` or `master`, but it could be any branch except for `gh-pages`. If nothing is set for this variable, then the current branch from which `docusaurus deploy` is invoked will be used. | -| `GIT_PASS` | Personal access token of the `git` user (specified by `GIT_USER`), to facilitate non-interactive deployment (e.g. continuous deployment) | GitHub enterprise installations should work in the same manner as github.com; you only need to set the organization's GitHub Enterprise host as an environment variable: @@ -362,7 +355,6 @@ jobs: - name: Deploy to GitHub Pages env: USE_SSH: true - GIT_USER: git run: | git config --global user.email "actions@github.com" git config --global user.name "gh-actions" @@ -492,7 +484,6 @@ trigger: - yarn deploy environment: USE_SSH: true - GIT_USER: $DRONE_COMMIT_AUTHOR GITHUB_PRIVATE_KEY: from_secret: git_deploy_private_key ```