feat: allow GIT_USER env var to be unset if SSH is used (#5840)

* feat: allow GIT_USER env var to be unset if SSH is used

* fix: packages/docusaurus/src/commands/deploy.ts

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>

* feat: allow user to specify deploymentBranch property in docusaurus.config.js (#5841)

* feat: allow user to specify deploymentBranch property in docusaurus.config.js

* docs: remove extra backtick

* docs: fix broken code block

* docs: fix i18n routes to feature requests (#5843)

* docs: fix i18n routes to feature requests

* Add redirect rules

* feat: allow GIT_USER env var to be unset if SSH is used

* fix: packages/docusaurus/src/commands/deploy.ts

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>

* fix: avoid escaping hyphen in regex

* Refactor

* Update deployment.mdx

* Make SSH higher priority

* Only infer but not override

* Add tests

* Fix tests

* Fix

Co-authored-by: Joshua Chen <sidachen2003@gmail.com>
This commit is contained in:
William Poetra Yoga 2021-11-10 17:51:12 +07:00 committed by GitHub
parent ecce576bbc
commit f5732e7589
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
7 changed files with 166 additions and 151 deletions

View file

@ -26,8 +26,16 @@ This command generates static content into the `build` directory and can be serv
### Deployment
Using SSH:
```
$ GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> 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.

View file

@ -26,8 +26,16 @@ This command generates static content into the `build` directory and can be serv
### Deployment
Using SSH:
```
$ GIT_USER=<Your GitHub username> USE_SSH=true yarn deploy
$ USE_SSH=true yarn deploy
```
Not using SSH:
```
$ GIT_USER=<Your GitHub username> 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.

View file

@ -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',
);
});
});

View file

@ -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);
});
});

View file

@ -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`;
}

View file

@ -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<BuildCLIOptions> = {},
@ -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/<organization>/<organization>.github.io
// - Site url: https://<organization>.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}`;
}
const useSSH = process.env.USE_SSH;
const remoteBranch = buildUrl(
let remoteBranch: string;
if (useSSH) {
remoteBranch = buildSshUrl(
githubHost,
githubPort,
gitCredentials,
organizationName,
projectName,
useSSH !== undefined && useSSH.toLowerCase() === 'true',
githubPort,
);
} else {
const gitPass = process.env.GIT_PASS;
const gitCredentials = gitPass ? `${gitUser!}:${gitPass}` : gitUser!;
remoteBranch = buildHttpsUrl(
gitCredentials,
githubHost,
organizationName,
projectName,
githubPort,
);
}
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!',
);
}

View file

@ -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
```