Merge branch 'development' into releases/3.0.4

This commit is contained in:
tidy-dev 2022-07-13 06:40:50 -04:00
commit d334ccd859
55 changed files with 1125 additions and 750 deletions

6
.github/dependabot.yml vendored Normal file
View file

@ -0,0 +1,6 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: /
schedule:
interval: weekly

View file

@ -28,11 +28,11 @@ jobs:
# Needed for macOS arm64 until hosted macos-11.0 runners become available
SDKROOT: /Library/Developer/CommandLineTools/SDKs/MacOSX11.1.sdk
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
with:
submodules: recursive
- name: Use Node.js ${{ matrix.node }}
uses: actions/setup-node@v1
uses: actions/setup-node@v3
with:
node-version: ${{ matrix.node }}
@ -40,7 +40,7 @@ jobs:
id: yarn-cache-dir-path
run: echo "::set-output name=dir::$(yarn cache dir)"
- uses: actions/cache@v2
- uses: actions/cache@v3
id: yarn-cache # use this to check for `cache-hit` (`steps.yarn-cache.outputs.cache-hit != 'true'`)
with:
path: ${{ steps.yarn-cache-dir-path.outputs.dir }}

View file

@ -18,11 +18,11 @@ jobs:
steps:
- name: Checkout repository
uses: actions/checkout@v2
uses: actions/checkout@v3
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v1
uses: github/codeql-action/init@v2
with:
config-file: ./.github/codeql/codeql-config.yml
@ -32,7 +32,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below).
- name: Autobuild
uses: github/codeql-action/autobuild@v1
uses: github/codeql-action/autobuild@v2
# Command-line programs to run using the OS shell.
# 📚 https://git.io/JvXDl
@ -46,4 +46,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v1
uses: github/codeql-action/analyze@v2

View file

@ -9,7 +9,7 @@ jobs:
permissions:
pull-requests: write
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
if: |
startsWith(github.ref, 'refs/heads/releases/') && !contains(github.ref, 'test')

View file

@ -1,7 +1,7 @@
# [GitHub Desktop](https://desktop.github.com)
[GitHub Desktop](https://desktop.github.com/) is an open source [Electron](https://www.electronjs.org/)-based
GitHub app. It is written in [TypeScript](http://www.typescriptlang.org) and
GitHub app. It is written in [TypeScript](https://www.typescriptlang.org) and
uses [React](https://reactjs.org/).
![GitHub Desktop screenshot - Windows](https://cloud.githubusercontent.com/assets/359239/26094502/a1f56d02-3a5d-11e7-8799-23c7ba5e5106.png)

View file

@ -1,3 +1,3 @@
runtime = electron
disturl = https://electronjs.org/headers
target = 17.0.1
target = 19.0.0

View file

@ -26,7 +26,7 @@
"codemirror-mode-elixir": "^1.1.2",
"compare-versions": "^3.6.0",
"deep-equal": "^1.0.1",
"desktop-notifications": "^0.2.2",
"desktop-notifications": "^0.2.4",
"desktop-trampoline": "desktop/desktop-trampoline#v0.9.8",
"dexie": "^3.2.2",
"dompurify": "^2.3.3",

View file

@ -553,6 +553,25 @@ export interface ICommitSelection {
/** The commits currently selected in the app */
readonly shas: ReadonlyArray<string>
/**
* When multiple commits are selected, the diff is created using the rev range
* of firstSha^..lastSha in the selected shas. Thus comparing the trees of the
* the lastSha and the first parent of the first sha. However, our history
* list shows commits in chronological order. Thus, when a branch is merged,
* the commits from that branch are injected in their chronological order into
* the history list. Therefore, given a branch history of A, B, C, D,
* MergeCommit where B and C are from the merged branch, diffing on the
* selection of A through D would not have the changes from B an C.
*
* This is a list of the shas that are reachable by following the parent links
* (aka the graph) from the lastSha to the firstSha^ in the selection.
*
* Other notes: Given a selection A through D, executing `git diff A..D` would
* give us the changes since A but not including A; since the user will have
* selected A, we do `git diff A^..D` so that we include the changes of A.
* */
readonly shasInDiff: ReadonlyArray<string>
/**
* Whether the a selection of commits are group of adjacent to each other.
* Example: Given these are indexes of sha's in history, 3, 4, 5, 6 is contiguous as
@ -717,6 +736,9 @@ export interface ICompareState {
/** The SHAs of commits to render in the compare list */
readonly commitSHAs: ReadonlyArray<string>
/** The SHAs of commits to highlight in the compare list */
readonly shasToHighlight: ReadonlyArray<string>
/**
* A list of branches (remote and local) except the current branch, and
* Desktop fork remote branches (see `Branch.isDesktopForkRemoteBranch`)

View file

@ -3,7 +3,10 @@ import { UpstreamRemoteName } from './stores'
import {
RepositoryWithGitHubRepository,
getNonForkGitHubRepository,
isRepositoryWithGitHubRepository,
Repository,
} from '../models/repository'
import { IBranchesState } from './app-state'
/**
* Finds the default branch of the upstream repository of the passed repository.
@ -41,3 +44,23 @@ export function findDefaultUpstreamBranch(
return foundBranch !== undefined ? foundBranch : null
}
/**
*
* @param repository The repository to use.
* @param branchesState The branches state of the repository.
* @returns The default branch of the user's contribution target, or null if it's not known.
*
* This method will return the fork's upstream default branch, if the user
* is contributing to the parent repository.
*
* Otherwise, this method will return the default branch of the passed in repository.
*/
export function findContributionTargetDefaultBranch(
repository: Repository,
{ allBranches, defaultBranch }: IBranchesState
): Branch | null {
return isRepositoryWithGitHubRepository(repository)
? findDefaultUpstreamBranch(repository, allBranches) ?? defaultBranch
: defaultBranch
}

View file

@ -112,7 +112,7 @@ const allMenuIds: ReadonlyArray<MenuIDs> = [
'discard-all-changes',
'stash-all-changes',
'preferences',
'update-branch',
'update-branch-with-contribution-target-branch',
'compare-to-branch',
'merge-branch',
'rebase-branch',
@ -261,7 +261,7 @@ function getRepositoryMenuBuilder(state: IAppState): MenuStateBuilder {
onNonDefaultBranch && !branchIsUnborn && !onDetachedHead
)
menuStateBuilder.setEnabled(
'update-branch',
'update-branch-with-contribution-target-branch',
onNonDefaultBranch && hasDefaultBranch && !onDetachedHead
)
menuStateBuilder.setEnabled('merge-branch', onBranch)
@ -343,7 +343,7 @@ function getRepositoryMenuBuilder(state: IAppState): MenuStateBuilder {
menuStateBuilder.disable('delete-branch')
menuStateBuilder.disable('discard-all-changes')
menuStateBuilder.disable('stash-all-changes')
menuStateBuilder.disable('update-branch')
menuStateBuilder.disable('update-branch-with-contribution-target-branch')
menuStateBuilder.disable('merge-branch')
if (enableSquashMerging()) {
menuStateBuilder.disable('squash-and-merge-branch')

View file

@ -300,6 +300,7 @@ import {
import * as ipcRenderer from '../ipc-renderer'
import { pathExists } from '../../ui/lib/path-exists'
import { offsetFromNow } from '../offset-from'
import { findContributionTargetDefaultBranch } from '../branch'
import { ValidNotificationPullRequestReview } from '../valid-notification-pull-request-review'
import { determineMergeability } from '../git/merge-tree'
@ -1109,12 +1110,13 @@ export class AppStore extends TypedBaseStore<IAppState> {
}
/** This shouldn't be called directly. See `Dispatcher`. */
public async _changeCommitSelection(
public _changeCommitSelection(
repository: Repository,
shas: ReadonlyArray<string>,
isContiguous: boolean
): Promise<void> {
const { commitSelection } = this.repositoryStateCache.get(repository)
): void {
const { commitSelection, commitLookup } =
this.repositoryStateCache.get(repository)
if (
commitSelection.shas.length === shas.length &&
@ -1123,8 +1125,11 @@ export class AppStore extends TypedBaseStore<IAppState> {
return
}
const shasInDiff = this.getShasInDiff(shas, isContiguous, commitLookup)
this.repositoryStateCache.updateCommitSelection(repository, () => ({
shas,
shasInDiff,
isContiguous,
file: null,
changesetData: { files: [], linesAdded: 0, linesDeleted: 0 },
@ -1134,6 +1139,65 @@ export class AppStore extends TypedBaseStore<IAppState> {
this.emitUpdate()
}
/** This shouldn't be called directly. See `Dispatcher`. */
public async _updateShasToHighlight(
repository: Repository,
shasToHighlight: ReadonlyArray<string>
) {
this.repositoryStateCache.updateCompareState(repository, () => ({
shasToHighlight,
}))
this.emitUpdate()
}
/**
* When multiple commits are selected, the diff is created using the rev range
* of firstSha^..lastSha in the selected shas. Thus comparing the trees of the
* the lastSha and the first parent of the first sha. However, our history
* list shows commits in chronological order. Thus, when a branch is merged,
* the commits from that branch are injected in their chronological order into
* the history list. Therefore, given a branch history of A, B, C, D,
* MergeCommit where B and C are from the merged branch, diffing on the
* selection of A through D would not have the changes from B an C.
*
* This method traverses the ancestral path from the last commit in the
* selection back to the first commit via checking the parents. The
* commits on this path are the commits whose changes will be seen in the
* diff. This is equivalent to doing `git rev-list firstSha^..lastSha`.
*/
private getShasInDiff(
selectedShas: ReadonlyArray<string>,
isContiguous: boolean,
commitLookup: Map<string, Commit>
) {
const shasInDiff = new Array<string>()
if (selectedShas.length <= 1 || !isContiguous) {
return selectedShas
}
const shasToTraverse = [selectedShas.at(-1)]
do {
const currentSha = shasToTraverse.pop()
if (currentSha === undefined) {
continue
}
shasInDiff.push(currentSha)
// shas are selection of history -> should be in lookup -> `|| []` is for typing sake
const parentSHAs = commitLookup.get(currentSha)?.parentSHAs || []
const parentsInSelection = parentSHAs.filter(parentSha =>
selectedShas.includes(parentSha)
)
shasToTraverse.push(...parentsInSelection)
} while (shasToTraverse.length > 0)
return shasInDiff
}
private updateOrSelectFirstCommit(
repository: Repository,
commitSHAs: ReadonlyArray<string>
@ -2036,6 +2100,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
private updateMenuItemLabels(state: IRepositoryState | null) {
const {
selectedShell,
selectedRepository,
selectedExternalEditor,
askForConfirmationOnRepositoryRemoval,
askForConfirmationOnForcePush,
@ -2054,12 +2119,14 @@ export class AppStore extends TypedBaseStore<IAppState> {
}
const { changesState, branchesState, aheadBehind } = state
const { defaultBranch, currentPullRequest } = branchesState
const { currentPullRequest } = branchesState
const defaultBranchName =
defaultBranch === null || defaultBranch.upstreamWithoutRemote === null
? undefined
: defaultBranch.upstreamWithoutRemote
let contributionTargetDefaultBranch: string | undefined
if (selectedRepository instanceof Repository) {
contributionTargetDefaultBranch =
findContributionTargetDefaultBranch(selectedRepository, branchesState)
?.name ?? undefined
}
const isForcePushForCurrentRepository = isCurrentBranchForcePush(
branchesState,
@ -2074,7 +2141,7 @@ export class AppStore extends TypedBaseStore<IAppState> {
updatePreferredAppMenuItemLabels({
...labels,
defaultBranchName,
contributionTargetDefaultBranch,
isForcePushForCurrentRepository,
isStashedChangesVisible,
hasCurrentPullRequest: currentPullRequest !== null,

View file

@ -178,6 +178,7 @@ function getInitialRepositoryState(): IRepositoryState {
return {
commitSelection: {
shas: [],
shasInDiff: [],
isContiguous: true,
file: null,
changesetData: { files: [], linesAdded: 0, linesDeleted: 0 },
@ -219,6 +220,7 @@ function getInitialRepositoryState(): IRepositoryState {
showBranchList: false,
filterText: '',
commitSHAs: [],
shasToHighlight: [],
branches: new Array<Branch>(),
recentBranches: new Array<Branch>(),
defaultBranch: null,

View file

@ -25,6 +25,7 @@ import {
installNotificationCallback,
terminateDesktopNotifications,
} from './notifications'
import { addTrustedIPCSender } from './trusted-ipc-sender'
export class AppWindow {
private window: Electron.BrowserWindow
@ -77,6 +78,7 @@ export class AppWindow {
}
this.window = new BrowserWindow(windowOptions)
addTrustedIPCSender(this.window.webContents)
installNotificationCallback(this.window)

View file

@ -4,6 +4,7 @@ import { ICrashDetails, ErrorType } from '../crash/shared'
import { registerWindowStateChangedEvents } from '../lib/window-state'
import * as ipcMain from './ipc-main'
import * as ipcWebContents from './ipc-webcontents'
import { addTrustedIPCSender } from './trusted-ipc-sender'
const minWidth = 600
const minHeight = 500
@ -51,6 +52,7 @@ export class CrashWindow {
}
this.window = new BrowserWindow(windowOptions)
addTrustedIPCSender(this.window.webContents)
this.error = error
this.errorType = errorType

View file

@ -2,6 +2,17 @@ import { RequestChannels, RequestResponseChannels } from '../lib/ipc-shared'
// eslint-disable-next-line no-restricted-imports
import { ipcMain } from 'electron'
import { IpcMainEvent, IpcMainInvokeEvent } from 'electron/main'
import { isTrustedIPCSender } from './trusted-ipc-sender'
type RequestChannelListener<T extends keyof RequestChannels> = (
event: IpcMainEvent,
...args: Parameters<RequestChannels[T]>
) => void
type RequestResponseChannelListener<T extends keyof RequestResponseChannels> = (
event: IpcMainInvokeEvent,
...args: Parameters<RequestResponseChannels[T]>
) => ReturnType<RequestResponseChannels[T]>
/**
* Subscribes to the specified IPC channel and provides strong typing of
@ -10,12 +21,9 @@ import { IpcMainEvent, IpcMainInvokeEvent } from 'electron/main'
*/
export function on<T extends keyof RequestChannels>(
channel: T,
listener: (
event: IpcMainEvent,
...args: Parameters<RequestChannels[T]>
) => void
listener: RequestChannelListener<T>
) {
ipcMain.on(channel, (event, ...args) => listener(event, ...(args as any)))
ipcMain.on(channel, safeListener(listener))
}
/**
@ -25,12 +33,9 @@ export function on<T extends keyof RequestChannels>(
*/
export function once<T extends keyof RequestChannels>(
channel: T,
listener: (
event: IpcMainEvent,
...args: Parameters<RequestChannels[T]>
) => void
listener: RequestChannelListener<T>
) {
ipcMain.once(channel, (event, ...args) => listener(event, ...(args as any)))
ipcMain.once(channel, safeListener(listener))
}
/**
@ -40,10 +45,22 @@ export function once<T extends keyof RequestChannels>(
*/
export function handle<T extends keyof RequestResponseChannels>(
channel: T,
listener: (
event: IpcMainInvokeEvent,
...args: Parameters<RequestResponseChannels[T]>
) => ReturnType<RequestResponseChannels[T]>
listener: RequestResponseChannelListener<T>
) {
ipcMain.handle(channel, (event, ...args) => listener(event, ...(args as any)))
ipcMain.handle(channel, safeListener(listener))
}
function safeListener<E extends IpcMainEvent | IpcMainInvokeEvent, R>(
listener: (event: E, ...a: any) => R
) {
return (event: E, ...args: any) => {
if (!isTrustedIPCSender(event.sender)) {
log.error(
`IPC message received from invalid sender: ${event.senderFrame.url}`
)
return
}
return listener(event, ...args)
}
}

View file

@ -38,12 +38,15 @@ export function buildDefaultMenu({
askForConfirmationOnForcePush,
askForConfirmationOnRepositoryRemoval,
hasCurrentPullRequest = false,
defaultBranchName = defaultBranchNameValue,
contributionTargetDefaultBranch = defaultBranchNameValue,
isForcePushForCurrentRepository = false,
isStashedChangesVisible = false,
askForConfirmationWhenStashingAllChanges = true,
}: MenuLabelsEvent): Electron.Menu {
defaultBranchName = truncateWithEllipsis(defaultBranchName, 25)
contributionTargetDefaultBranch = truncateWithEllipsis(
contributionTargetDefaultBranch,
25
)
const removeRepoLabel = askForConfirmationOnRepositoryRemoval
? confirmRepositoryRemovalLabel
@ -376,11 +379,11 @@ export function buildDefaultMenu({
separator,
{
label: __DARWIN__
? `Update from ${defaultBranchName}`
: `&Update from ${defaultBranchName}`,
id: 'update-branch',
? `Update from ${contributionTargetDefaultBranch}`
: `&Update from ${contributionTargetDefaultBranch}`,
id: 'update-branch-with-contribution-target-branch',
accelerator: 'CmdOrCtrl+Shift+U',
click: emit('update-branch'),
click: emit('update-branch-with-contribution-target-branch'),
},
{
label: __DARWIN__ ? 'Compare to Branch' : '&Compare to branch',

View file

@ -16,7 +16,7 @@ export type MenuEvent =
| 'show-preferences'
| 'choose-repository'
| 'open-working-directory'
| 'update-branch'
| 'update-branch-with-contribution-target-branch'
| 'compare-to-branch'
| 'merge-branch'
| 'squash-and-merge-branch'

View file

@ -11,6 +11,11 @@ import * as ipcWebContents from './ipc-webcontents'
let windowsToastActivatorClsid: string | undefined = undefined
export function initializeDesktopNotifications() {
if (__LINUX__) {
// notifications not currently supported
return
}
if (__DARWIN__) {
initializeNotifications({})
return
@ -24,7 +29,7 @@ export function initializeDesktopNotifications() {
if (windowsToastActivatorClsid === undefined) {
log.error(
'Toast activator CLSID not found in any of the shortucts. Falling back to known CLSIDs.'
'Toast activator CLSID not found in any of the shortcuts. Falling back to known CLSIDs.'
)
// This is generated by Squirrel.Windows here:

View file

@ -17,16 +17,13 @@ export function showUncaughtException(isLaunchError: boolean, error: Error) {
setCrashMenu()
const crashWindow = new CrashWindow(
isLaunchError ? 'launch' : 'generic',
error
)
const window = new CrashWindow(isLaunchError ? 'launch' : 'generic', error)
crashWindow.onDidLoad(() => {
crashWindow.show()
window.onDidLoad(() => {
window.show()
})
crashWindow.onFailedToLoad(async () => {
window.onFailedToLoad(async () => {
await dialog.showMessageBox({
type: 'error',
title: __DARWIN__ ? `Unrecoverable Error` : 'Unrecoverable error',
@ -44,12 +41,12 @@ export function showUncaughtException(isLaunchError: boolean, error: Error) {
app.quit()
})
crashWindow.onClose(() => {
window.onClose(() => {
if (!__DEV__) {
app.relaunch()
}
app.quit()
})
crashWindow.load()
window.load()
}

View file

@ -0,0 +1,16 @@
import { WebContents } from 'electron'
// WebContents id of trusted senders of IPC messages. This is used to verify
// that only IPC messages sent from trusted senders are handled, as recommended
// by the Electron security documentation:
// https://github.com/electron/electron/blob/main/docs/tutorial/security.md#17-validate-the-sender-of-all-ipc-messages
const trustedSenders = new Set<number>()
/** Adds a WebContents instance to the set of trusted IPC senders. */
export const addTrustedIPCSender = (wc: WebContents) => {
trustedSenders.add(wc.id)
wc.on('destroyed', () => trustedSenders.delete(wc.id))
}
/** Returns true if the given WebContents is a trusted sender of IPC messages. */
export const isTrustedIPCSender = (wc: WebContents) => trustedSenders.has(wc.id)

View file

@ -5,7 +5,7 @@ export type MenuIDs =
| 'discard-all-changes'
| 'stash-all-changes'
| 'preferences'
| 'update-branch'
| 'update-branch-with-contribution-target-branch'
| 'merge-branch'
| 'squash-and-merge-branch'
| 'rebase-branch'

View file

@ -28,11 +28,16 @@ export type MenuLabelsEvent = {
readonly askForConfirmationOnRepositoryRemoval: boolean
/**
* Specify the default branch associated with the current repository.
* Specify the default branch of the user's contribution target.
*
* This value should be the fork's upstream default branch, if the user
* is contributing to the parent repository.
*
* Otherwise, this value should be the default branch of the repository.
*
* Omit this value to indicate that the default branch is unknown.
*/
readonly defaultBranchName?: string
readonly contributionTargetDefaultBranch?: string
/**
* Is the current branch in a state where it can be force pushed to the remote?

View file

@ -104,7 +104,10 @@ import { TutorialStep, isValidTutorialStep } from '../models/tutorial-step'
import { WorkflowPushRejectedDialog } from './workflow-push-rejected/workflow-push-rejected'
import { SAMLReauthRequiredDialog } from './saml-reauth-required/saml-reauth-required'
import { CreateForkDialog } from './forks/create-fork-dialog'
import { findDefaultUpstreamBranch } from '../lib/branch'
import {
findContributionTargetDefaultBranch,
findDefaultUpstreamBranch,
} from '../lib/branch'
import {
GitHubRepository,
hasWritePermission,
@ -147,6 +150,7 @@ import { PullRequestChecksFailed } from './notifications/pull-request-checks-fai
import { CICheckRunRerunDialog } from './check-runs/ci-check-run-rerun-dialog'
import { WarnForcePushDialog } from './multi-commit-operation/dialog/warn-force-push-dialog'
import { clamp } from '../lib/clamp'
import { generateRepositoryListContextMenu } from './repositories-list/repository-list-item-context-menu'
import * as ipcRenderer from '../lib/ipc-renderer'
import { showNotification } from '../lib/notifications/show-notification'
import { DiscardChangesRetryDialog } from './discard-changes/discard-changes-retry-dialog'
@ -155,6 +159,7 @@ import { PullRequestReview } from './notifications/pull-request-review'
import { getPullRequestCommitRef } from '../models/pull-request'
import { getRepositoryType } from '../lib/git'
import { SSHUserPassword } from './ssh/ssh-user-password'
import { showContextualMenu } from '../lib/menu-item'
const MinuteInMilliseconds = 1000 * 60
const HourInMilliseconds = MinuteInMilliseconds * 60
@ -382,9 +387,9 @@ export class App extends React.Component<IAppProps, IAppState> {
return this.props.dispatcher.showPopup({ type: PopupType.Preferences })
case 'open-working-directory':
return this.openCurrentRepositoryWorkingDirectory()
case 'update-branch':
case 'update-branch-with-contribution-target-branch':
this.props.dispatcher.recordMenuInitiatedUpdate()
return this.updateBranch()
return this.updateBranchWithContributionTargetBranch()
case 'compare-to-branch':
return this.showHistory(true)
case 'merge-branch':
@ -635,7 +640,7 @@ export class App extends React.Component<IAppProps, IAppState> {
return enterpriseAccount || null
}
private updateBranch() {
private updateBranchWithContributionTargetBranch() {
const { selectedState } = this.state
if (
selectedState == null ||
@ -645,19 +650,27 @@ export class App extends React.Component<IAppProps, IAppState> {
}
const { state, repository } = selectedState
const defaultBranch = state.branchesState.defaultBranch
if (!defaultBranch) {
const contributionTargetDefaultBranch = findContributionTargetDefaultBranch(
repository,
state.branchesState
)
if (!contributionTargetDefaultBranch) {
return
}
this.props.dispatcher.initializeMergeOperation(
repository,
false,
defaultBranch
contributionTargetDefaultBranch
)
const { mergeStatus } = state.compareState
this.props.dispatcher.mergeBranch(repository, defaultBranch, mergeStatus)
this.props.dispatcher.mergeBranch(
repository,
contributionTargetDefaultBranch,
mergeStatus
)
}
private mergeBranch(isSquash: boolean = false) {
@ -2555,6 +2568,7 @@ export class App extends React.Component<IAppProps, IAppState> {
description={__DARWIN__ ? 'Current Repository' : 'Current repository'}
tooltip={tooltip}
foldoutStyle={foldoutStyle}
onContextMenu={this.onRepositoryToolbarButtonContextMenu}
onDropdownStateChanged={this.onRepositoryDropdownStateChanged}
dropdownContentRenderer={this.renderRepositoryList}
dropdownState={currentState}
@ -2562,6 +2576,43 @@ export class App extends React.Component<IAppProps, IAppState> {
)
}
private onRepositoryToolbarButtonContextMenu = () => {
const repository = this.state.selectedState?.repository
if (repository === undefined) {
return
}
const externalEditorLabel = this.state.selectedExternalEditor ?? undefined
const onChangeRepositoryAlias = (repository: Repository) => {
this.props.dispatcher.showPopup({
type: PopupType.ChangeRepositoryAlias,
repository,
})
}
const onRemoveRepositoryAlias = (repository: Repository) => {
this.props.dispatcher.changeRepositoryAlias(repository, null)
}
const items = generateRepositoryListContextMenu({
onRemoveRepository: this.removeRepository,
onShowRepository: this.showRepository,
onOpenInShell: this.openInShell,
onOpenInExternalEditor: this.openInExternalEditor,
askForConfirmationOnRemoveRepository:
this.state.askForConfirmationOnRepositoryRemoval,
externalEditorLabel: externalEditorLabel,
onChangeRepositoryAlias: onChangeRepositoryAlias,
onRemoveRepositoryAlias: onRemoveRepositoryAlias,
onViewOnGitHub: this.viewOnGitHub,
repository: repository,
shellLabel: this.state.selectedShell,
})
showContextualMenu(items)
}
private renderPushPullToolbarButton() {
const selection = this.state.selectedState
if (!selection || selection.type !== SelectionType.Repository) {

View file

@ -0,0 +1,40 @@
import { IMenuItem } from '../../lib/menu-item'
import { clipboard } from 'electron'
interface IBranchContextMenuConfig {
name: string
isLocal: boolean
onRenameBranch?: (branchName: string) => void
onDeleteBranch?: (branchName: string) => void
}
export function generateBranchContextMenuItems(
config: IBranchContextMenuConfig
): IMenuItem[] {
const { name, isLocal, onRenameBranch, onDeleteBranch } = config
const items = new Array<IMenuItem>()
if (onRenameBranch !== undefined) {
items.push({
label: 'Rename…',
action: () => onRenameBranch(name),
enabled: isLocal,
})
}
items.push({
label: __DARWIN__ ? 'Copy Branch Name' : 'Copy branch name',
action: () => clipboard.writeText(name),
})
items.push({ type: 'separator' })
if (onDeleteBranch !== undefined) {
items.push({
label: 'Delete…',
action: () => onDeleteBranch(name),
})
}
return items
}

View file

@ -1,4 +1,3 @@
import { clipboard } from 'electron'
import * as React from 'react'
import { IMatches } from '../../lib/fuzzy-find'
@ -7,12 +6,12 @@ import { Octicon } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { HighlightText } from '../lib/highlight-text'
import { showContextualMenu } from '../../lib/menu-item'
import { IMenuItem } from '../../lib/menu-item'
import { dragAndDropManager } from '../../lib/drag-and-drop-manager'
import { DragType, DropTargetType } from '../../models/drag-drop'
import { TooltippedContent } from '../lib/tooltipped-content'
import { RelativeTime } from '../relative-time'
import classNames from 'classnames'
import { generateBranchContextMenuItems } from './branch-list-item-context-menu'
interface IBranchListItemProps {
/** The name of the branch */
@ -74,30 +73,13 @@ export class BranchListItem extends React.Component<
return
}
const items: Array<IMenuItem> = []
if (onRenameBranch !== undefined) {
items.push({
label: 'Rename…',
action: () => onRenameBranch(name),
enabled: isLocal,
})
}
items.push({
label: __DARWIN__ ? 'Copy Branch Name' : 'Copy branch name',
action: () => clipboard.writeText(name),
const items = generateBranchContextMenuItems({
name,
isLocal,
onRenameBranch,
onDeleteBranch,
})
items.push({ type: 'separator' })
if (onDeleteBranch !== undefined) {
items.push({
label: 'Delete…',
action: () => onDeleteBranch(name),
})
}
showContextualMenu(items)
}

View file

@ -5,7 +5,7 @@ import {
Repository,
isRepositoryWithGitHubRepository,
} from '../../models/repository'
import { Branch, BranchType } from '../../models/branch'
import { Branch } from '../../models/branch'
import { BranchesTab } from '../../models/branches-tab'
import { PopupType } from '../../models/popup'
@ -40,6 +40,8 @@ interface IBranchesContainerProps {
readonly currentBranch: Branch | null
readonly recentBranches: ReadonlyArray<Branch>
readonly pullRequests: ReadonlyArray<PullRequest>
readonly onRenameBranch: (branchName: string) => void
readonly onDeleteBranch: (branchName: string) => void
/** The pull request associated with the current branch. */
readonly currentPullRequest: PullRequest | null
@ -204,8 +206,8 @@ export class BranchesContainer extends React.Component<
item,
matches,
this.props.currentBranch,
this.onRenameBranch,
this.onDeleteBranch,
this.props.onRenameBranch,
this.props.onDeleteBranch,
this.onDropOntoBranch,
this.onDropOntoCurrentBranch
)
@ -401,52 +403,6 @@ export class BranchesContainer extends React.Component<
this.setState({ selectedPullRequest })
}
private getBranchWithName(branchName: string): Branch | undefined {
return this.props.allBranches.find(branch => branch.name === branchName)
}
private onRenameBranch = (branchName: string) => {
const branch = this.getBranchWithName(branchName)
if (branch === undefined) {
return
}
this.props.dispatcher.showPopup({
type: PopupType.RenameBranch,
repository: this.props.repository,
branch: branch,
})
}
private onDeleteBranch = async (branchName: string) => {
const branch = this.getBranchWithName(branchName)
if (branch === undefined) {
return
}
if (branch.type === BranchType.Remote) {
this.props.dispatcher.showPopup({
type: PopupType.DeleteRemoteBranch,
repository: this.props.repository,
branch,
})
return
}
const aheadBehind = await this.props.dispatcher.getBranchAheadBehind(
this.props.repository,
branch
)
this.props.dispatcher.showPopup({
type: PopupType.DeleteBranch,
repository: this.props.repository,
branch,
existsOnRemote: aheadBehind !== null,
})
}
/**
* Method is to handle when something is dragged and dropped onto a branch
* in the branch dropdown.

View file

@ -237,10 +237,18 @@ export class Dispatcher {
repository: Repository,
shas: ReadonlyArray<string>,
isContiguous: boolean
): Promise<void> {
): void {
return this.appStore._changeCommitSelection(repository, shas, isContiguous)
}
/** Update the shas that should be highlighted */
public updateShasToHighlight(
repository: Repository,
shasToHighlight: ReadonlyArray<string>
) {
this.appStore._updateShasToHighlight(repository, shasToHighlight)
}
/**
* Change the selected changed file in the history view.
*

View file

@ -6,6 +6,7 @@ import { CommitListItem } from './commit-list-item'
import { List } from '../lib/list'
import { arrayEquals } from '../../lib/equality'
import { DragData, DragType } from '../../models/drag-drop'
import classNames from 'classnames'
const RowHeight = 50
@ -132,6 +133,9 @@ interface ICommitListProps {
/** Whether squashing should be enabled on the commit list */
readonly disableSquashing?: boolean
/** Shas that should be highlighted */
readonly shasToHighlight: ReadonlyArray<string>
}
/** A component which displays the list of commits. */
@ -350,19 +354,42 @@ export class CommitList extends React.Component<ICommitListProps, {}> {
return this.props.commitSHAs.findIndex(s => s === sha)
}
public render() {
if (this.props.commitSHAs.length === 0) {
return (
<div className="panel blankslate">{this.props.emptyListMessage}</div>
)
private getRowCustomClassMap = () => {
const { commitSHAs, shasToHighlight } = this.props
if (shasToHighlight.length === 0) {
return undefined
}
const rowsForShasNotInDiff = commitSHAs
.filter(sha => shasToHighlight.includes(sha))
.map(sha => this.rowForSHA(sha))
if (rowsForShasNotInDiff.length === 0) {
return undefined
}
const rowClassMap = new Map<string, ReadonlyArray<number>>()
rowClassMap.set('highlighted', rowsForShasNotInDiff)
return rowClassMap
}
public render() {
const { commitSHAs, selectedSHAs, shasToHighlight, emptyListMessage } =
this.props
if (commitSHAs.length === 0) {
return <div className="panel blankslate">{emptyListMessage}</div>
}
const classes = classNames({
'has-highlighted-commits': shasToHighlight.length > 0,
})
return (
<div id="commit-list">
<div id="commit-list" className={classes}>
<List
rowCount={this.props.commitSHAs.length}
rowCount={commitSHAs.length}
rowHeight={RowHeight}
selectedRows={this.props.selectedSHAs.map(sha => this.rowForSHA(sha))}
selectedRows={selectedSHAs.map(sha => this.rowForSHA(sha))}
rowRenderer={this.renderCommit}
onDropDataInsertion={this.onDropDataInsertion}
onSelectionChanged={this.onSelectionChanged}
@ -377,8 +404,10 @@ export class CommitList extends React.Component<ICommitListProps, {}> {
localCommitSHAs: this.props.localCommitSHAs,
commitLookupHash: this.commitsHash(this.getVisibleCommits()),
tagsToPush: this.props.tagsToPush,
shasToHighlight: this.props.shasToHighlight,
}}
setScrollTop={this.props.compareListScrollTop}
rowCustomClassNameMap={this.getRowCustomClassMap()}
/>
</div>
)

View file

@ -19,10 +19,12 @@ import { clipboard } from 'electron'
import { TooltipDirection } from '../lib/tooltip'
import { AppFileStatusKind } from '../../models/status'
import _ from 'lodash'
import { LinkButton } from '../lib/link-button'
interface ICommitSummaryProps {
readonly repository: Repository
readonly commits: ReadonlyArray<Commit>
readonly selectedCommits: ReadonlyArray<Commit>
readonly shasInDiff: ReadonlyArray<string>
readonly changesetData: IChangesetData
readonly emoji: Map<string, string>
@ -52,6 +54,9 @@ interface ICommitSummaryProps {
/** Called when the user opens the diff options popover */
readonly onDiffOptionsOpened: () => void
/** Called to highlight certain shas in the history */
readonly onHighlightShas: (shasToHighlight: ReadonlyArray<string>) => void
}
interface ICommitSummaryState {
@ -62,6 +67,11 @@ interface ICommitSummaryState {
*/
readonly summary: ReadonlyArray<TokenResult>
/**
* Whether the commit summary was empty.
*/
readonly hasEmptySummary: boolean
/**
* The commit message body, i.e. anything after the first line of text in the
* commit message. Note that this may differ from the body property in the
@ -99,36 +109,49 @@ function createState(
isOverflowed: boolean,
props: ICommitSummaryProps
): ICommitSummaryState {
const { emoji, repository, commits } = props
const { emoji, repository, selectedCommits } = props
const tokenizer = new Tokenizer(emoji, repository)
const plainTextBody =
commits.length > 1
? commits
.map(
c =>
`${c.shortSha} - ${c.summary}${
c.body.trim() !== '' ? `\n${c.body}` : ''
}`
)
.join('\n\n')
: commits[0].body
const { summary, body } = wrapRichTextCommitMessage(
commits[0].summary,
plainTextBody,
getCommitSummary(selectedCommits),
selectedCommits[0].body,
tokenizer
)
const allAvatarUsers = commits.flatMap(c =>
const hasEmptySummary =
selectedCommits.length === 1 && selectedCommits[0].summary.length === 0
const allAvatarUsers = selectedCommits.flatMap(c =>
getAvatarUsersForCommit(repository.gitHubRepository, c)
)
const avatarUsers = _.uniqWith(
allAvatarUsers,
(a, b) => a.email === b.email && a.name === b.name
)
return { isOverflowed, summary, body, avatarUsers }
return { isOverflowed, summary, body, avatarUsers, hasEmptySummary }
}
function getCommitSummary(selectedCommits: ReadonlyArray<Commit>) {
return selectedCommits[0].summary.length === 0
? 'Empty commit message'
: selectedCommits[0].summary
}
function getCountCommitsNotInDiff(
selectedCommits: ReadonlyArray<Commit>,
shasInDiff: ReadonlyArray<string>
) {
if (selectedCommits.length === 1) {
return 0
}
const excludedCommits = selectedCommits.filter(
({ sha }) => !shasInDiff.includes(sha)
)
return excludedCommits.length
}
/**
@ -260,9 +283,9 @@ export class CommitSummary extends React.Component<
public componentWillUpdate(nextProps: ICommitSummaryProps) {
if (
nextProps.commits.length !== this.props.commits.length ||
!nextProps.commits.every((nextCommit, i) =>
messageEquals(nextCommit, this.props.commits[i])
nextProps.selectedCommits.length !== this.props.selectedCommits.length ||
!nextProps.selectedCommits.every((nextCommit, i) =>
messageEquals(nextCommit, this.props.selectedCommits[i])
)
) {
this.setState(createState(false, nextProps))
@ -316,17 +339,150 @@ export class CommitSummary extends React.Component<
}
private getShaRef = (useShortSha?: boolean) => {
const { commits } = this.props
const oldest = useShortSha ? commits[0].shortSha : commits[0].sha
const { selectedCommits } = this.props
return useShortSha ? selectedCommits[0].shortSha : selectedCommits[0].sha
}
if (commits.length === 1) {
return oldest
private onHighlightShasInDiff = () => {
this.props.onHighlightShas(this.props.shasInDiff)
}
private onHighlightShasNotInDiff = () => {
const { onHighlightShas, selectedCommits, shasInDiff } = this.props
onHighlightShas(
selectedCommits.filter(c => !shasInDiff.includes(c.sha)).map(c => c.sha)
)
}
private onRemoveHighlightOfShas = () => {
this.props.onHighlightShas([])
}
private showUnreachableCommits() {
// TODO: open to dialog with commits list of unreachable commits
}
private renderCommitsNotReachable = () => {
const { selectedCommits, shasInDiff } = this.props
if (selectedCommits.length === 1) {
return
}
const latestCommit = commits.at(-1)
const latest = useShortSha ? latestCommit?.shortSha : latestCommit?.sha
const excludedCommitsCount = getCountCommitsNotInDiff(
selectedCommits,
shasInDiff
)
return `${oldest}^..${latest}`
if (excludedCommitsCount === 0) {
return
}
const commitsPluralized = excludedCommitsCount > 1 ? 'commits' : 'commit'
return (
<div
className="commit-unreachable-info"
onMouseOver={this.onHighlightShasNotInDiff}
onMouseOut={this.onRemoveHighlightOfShas}
>
<Octicon symbol={OcticonSymbol.info} />
<LinkButton onClick={this.showUnreachableCommits}>
{excludedCommitsCount} unreachable {commitsPluralized}
</LinkButton>{' '}
not included.
</div>
)
}
private renderAuthors = () => {
const { selectedCommits, repository } = this.props
const { avatarUsers } = this.state
if (selectedCommits.length > 1) {
return
}
return (
<li
className="commit-summary-meta-item without-truncation"
aria-label="Author"
>
<AvatarStack users={avatarUsers} />
<CommitAttribution
gitHubRepository={repository.gitHubRepository}
commits={selectedCommits}
/>
</li>
)
}
private renderCommitRef = () => {
const { selectedCommits } = this.props
if (selectedCommits.length > 1) {
return
}
return (
<li
className="commit-summary-meta-item without-truncation"
aria-label="SHA"
>
<Octicon symbol={OcticonSymbol.gitCommit} />
<TooltippedContent
className="sha"
tooltip={this.renderShaTooltip()}
tooltipClassName="sha-hint"
interactive={true}
direction={TooltipDirection.SOUTH}
>
{this.getShaRef(true)}
</TooltippedContent>
</li>
)
}
private renderSummary = () => {
const { selectedCommits, shasInDiff } = this.props
const { summary, hasEmptySummary } = this.state
const summaryClassNames = classNames('commit-summary-title', {
'empty-summary': hasEmptySummary,
})
if (selectedCommits.length === 1) {
return (
<RichText
className={summaryClassNames}
emoji={this.props.emoji}
repository={this.props.repository}
text={summary}
/>
)
}
const commitsNotInDiff = getCountCommitsNotInDiff(
selectedCommits,
shasInDiff
)
const numInDiff = selectedCommits.length - commitsNotInDiff
const commitsPluralized = numInDiff > 1 ? 'commits' : 'commit'
return (
<div className={summaryClassNames}>
Showing changes from{' '}
{commitsNotInDiff > 0 ? (
<LinkButton
onMouseOver={this.onHighlightShasInDiff}
onMouseOut={this.onRemoveHighlightOfShas}
onClick={this.showUnreachableCommits}
>
{numInDiff} {commitsPluralized}
</LinkButton>
) : (
<>
{' '}
{numInDiff} {commitsPluralized}
</>
)}
</div>
)
}
public render() {
@ -337,55 +493,13 @@ export class CommitSummary extends React.Component<
'hide-description-border': this.props.hideDescriptionBorder,
})
const hasEmptySummary = this.state.summary.length === 0
const commitSummary = hasEmptySummary
? 'Empty commit message'
: this.props.commits.length > 1
? `Viewing the diff of ${this.props.commits.length} commits`
: this.state.summary
const summaryClassNames = classNames('commit-summary-title', {
'empty-summary': hasEmptySummary,
})
return (
<div id="commit-summary" className={className}>
<div className="commit-summary-header">
<RichText
className={summaryClassNames}
emoji={this.props.emoji}
repository={this.props.repository}
text={commitSummary}
/>
{this.renderSummary()}
<ul className="commit-summary-meta">
<li
className="commit-summary-meta-item without-truncation"
aria-label="Author"
>
<AvatarStack users={this.state.avatarUsers} />
<CommitAttribution
gitHubRepository={this.props.repository.gitHubRepository}
commits={this.props.commits}
/>
</li>
<li
className="commit-summary-meta-item without-truncation"
aria-label="SHA"
>
<Octicon symbol={OcticonSymbol.gitCommit} />
<TooltippedContent
className="sha"
tooltip={this.renderShaTooltip()}
tooltipClassName="sha-hint"
interactive={true}
direction={TooltipDirection.SOUTH}
>
{this.getShaRef(true)}
</TooltippedContent>
</li>
{this.renderAuthors()}
{this.renderCommitRef()}
{this.renderChangedFilesDescription()}
{this.renderLinesChanged()}
{this.renderTags()}
@ -411,6 +525,7 @@ export class CommitSummary extends React.Component<
</div>
{this.renderDescription()}
{this.renderCommitsNotReachable()}
</div>
)
}
@ -528,10 +643,15 @@ export class CommitSummary extends React.Component<
}
private renderTags() {
const tags = this.props.commits.flatMap(c => c.tags) || []
const { selectedCommits } = this.props
if (selectedCommits.length > 1) {
return
}
const tags = selectedCommits[0].tags
if (tags.length === 0) {
return null
return
}
return (

View file

@ -55,6 +55,7 @@ interface ICompareSidebarProps {
readonly tagsToPush: ReadonlyArray<string> | null
readonly aheadBehindStore: AheadBehindStore
readonly isCherryPickInProgress: boolean
readonly shasToHighlight: ReadonlyArray<string>
}
interface ICompareSidebarState {
@ -230,6 +231,7 @@ export class CompareSidebar extends React.Component<
commitLookup={this.props.commitLookup}
commitSHAs={commitSHAs}
selectedSHAs={this.props.selectedCommitShas}
shasToHighlight={this.props.shasToHighlight}
localCommitSHAs={this.props.localCommitSHAs}
canResetToCommits={formState.kind === HistoryTabMode.History}
canUndoCommits={formState.kind === HistoryTabMode.History}

View file

@ -1,2 +1,2 @@
export { SelectedCommits } from './selected-commit'
export { SelectedCommits } from './selected-commits'
export { CompareSidebar } from './compare'

View file

@ -42,6 +42,7 @@ interface ISelectedCommitsProps {
readonly dispatcher: Dispatcher
readonly emoji: Map<string, string>
readonly selectedCommits: ReadonlyArray<Commit>
readonly shasInDiff: ReadonlyArray<string>
readonly localCommitSHAs: ReadonlyArray<string>
readonly changesetData: IChangesetData
readonly selectedFile: CommittedFileChange | null
@ -166,7 +167,8 @@ export class SelectedCommits extends React.Component<
private renderCommitSummary(commits: ReadonlyArray<Commit>) {
return (
<CommitSummary
commits={commits}
selectedCommits={commits}
shasInDiff={this.props.shasInDiff}
changesetData={this.props.changesetData}
emoji={this.props.emoji}
repository={this.props.repository}
@ -179,10 +181,18 @@ export class SelectedCommits extends React.Component<
onHideWhitespaceInDiffChanged={this.onHideWhitespaceInDiffChanged}
onShowSideBySideDiffChanged={this.onShowSideBySideDiffChanged}
onDiffOptionsOpened={this.props.onDiffOptionsOpened}
onHighlightShas={this.onHighlightShas}
/>
)
}
private onHighlightShas = (shasToHighlight: ReadonlyArray<string>) => {
this.props.dispatcher.updateShasToHighlight(
this.props.repository,
shasToHighlight
)
}
private onExpandChanged = (isExpanded: boolean) => {
this.setState({ isExpanded })
}

View file

@ -202,6 +202,13 @@ process.on(
}
)
// HACK: this is a workaround for a known crash in the Dev Tools on Electron 19
// See https://github.com/electron/electron/issues/34350
if (__DEV__) {
window.onerror = e =>
e === 'Uncaught EvalError: Possible side-effect in debug-evaluate'
}
/**
* Chromium won't crash on an unhandled rejection (similar to how it won't crash
* on an unhandled error). We've taken the approach that unhandled errors should

View file

@ -8,10 +8,18 @@ export interface IButtonProps {
* A callback which is invoked when the button is clicked
* using a pointer device or keyboard. The source event is
* passed along and can be used to prevent the default action
* or stop the even from bubbling.
* or stop the event from bubbling.
*/
readonly onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* A callback which is invoked when the button's context menu
* is activated using a pointer device or keyboard. The source
* event is passed along and can be used to prevent the default
* action or stop the event from bubbling.
*/
readonly onContextMenu?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* A function that's called when the user moves over the button with
* a pointer device.
@ -118,6 +126,7 @@ export class Button extends React.Component<IButtonProps, {}> {
<button
className={className}
onClick={disabled ? preventDefault : this.onClick}
onContextMenu={disabled ? preventDefault : this.onContextMenu}
type={this.props.type || 'button'}
ref={this.innerButtonRef}
tabIndex={this.props.tabIndex}
@ -151,6 +160,14 @@ export class Button extends React.Component<IButtonProps, {}> {
event.preventDefault()
}
}
private onContextMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onContextMenu?.(event)
if (this.props.type === undefined) {
event.preventDefault()
}
}
}
const preventDefault = (e: Event | React.SyntheticEvent) => e.preventDefault()

View file

@ -11,6 +11,12 @@ interface ILinkButtonProps {
/** A function to call on click. */
readonly onClick?: () => void
/** A function to call when mouse is hovered over */
readonly onMouseOver?: () => void
/** A function to call when mouse is moved off */
readonly onMouseOut?: () => void
/** CSS classes attached to the component */
readonly className?: string
@ -42,6 +48,8 @@ export class LinkButton extends React.Component<ILinkButtonProps, {}> {
ref={this.anchorRef}
className={className}
href={href}
onMouseOver={this.props.onMouseOver}
onMouseOut={this.props.onMouseOut}
onClick={this.onClick}
tabIndex={this.props.tabIndex}
>

View file

@ -77,6 +77,11 @@ interface IListProps {
*/
readonly selectedRows: ReadonlyArray<number>
/**
* Used to attach special classes to specific rows
*/
readonly rowCustomClassNameMap?: Map<string, ReadonlyArray<number>>
/**
* This function will be called when a pointer device is pressed and then
* released on a selectable row. Note that this follows the conventions
@ -791,10 +796,30 @@ export class List extends React.Component<IListProps, IListState> {
this.focusRow = -1
}
private getCustomRowClassNames = (rowIndex: number) => {
const { rowCustomClassNameMap } = this.props
if (rowCustomClassNameMap === undefined) {
return undefined
}
const customClasses = new Array<string>()
rowCustomClassNameMap.forEach(
(rows: ReadonlyArray<number>, className: string) => {
if (rows.includes(rowIndex)) {
customClasses.push(className)
}
}
)
return customClasses.length === 0 ? undefined : customClasses.join(' ')
}
private renderRow = (params: IRowRendererParams) => {
const rowIndex = params.rowIndex
const selectable = this.canSelectRow(rowIndex)
const selected = this.props.selectedRows.indexOf(rowIndex) !== -1
const customClasses = this.getCustomRowClassNames(rowIndex)
const focused = rowIndex === this.focusRow
// An unselectable row shouldn't be focusable
@ -843,13 +868,13 @@ export class List extends React.Component<IListProps, IListState> {
tabIndex={tabIndex}
children={element}
selectable={selectable}
className={customClasses}
/>
)
}
public render() {
let content: JSX.Element[] | JSX.Element | null
if (this.resizeObserver) {
content = this.renderContents(
this.state.width ?? 0,
@ -932,7 +957,6 @@ export class List extends React.Component<IListProps, IListState> {
// it with keyboard navigation and select an item.
const tabIndex =
this.props.selectedRows.length < 1 && this.props.rowCount > 0 ? 0 : -1
return (
<FocusContainer
className="list-focus-container"

View file

@ -1,7 +1,6 @@
import * as React from 'react'
import classNames from 'classnames'
import { createUniqueId, releaseUniqueId } from './id-pool'
import { LinkButton } from './link-button'
import { showContextualMenu } from '../../lib/menu-item'
export interface ITextBoxProps {
@ -47,34 +46,6 @@ export interface ITextBoxProps {
/** The type of the input. Defaults to `text`. */
readonly type?: 'text' | 'search' | 'password' | 'email'
/**
* An optional text for a link label element. A link label is, for the purposes
* of this control an anchor element that's rendered alongside (ie on the same)
* row as the the label element.
*
* Note that the link label will only be rendered if the textbox has a
* label text (specified through the label prop). A link label is used for
* presenting the user with a contextual link related to a specific text
* input such as a password recovery link for a password text box.
*/
readonly labelLinkText?: string
/**
* An optional URL to be opened when the label link (if present, see the
* labelLinkText prop for more details) is clicked. The link will be opened using the
* standard semantics of a LinkButton, i.e. in the configured system browser.
*
* If not specified consumers need to subscribe to the onLabelLinkClick event.
*/
readonly labelLinkUri?: string
/**
* An optional event handler which is invoked when the label link (if present,
* see the labelLinkText prop for more details) is clicked. See the onClick
* event on the LinkButton component for more details.
*/
readonly onLabelLinkClick?: () => void
/** The tab index of the input element. */
readonly tabIndex?: number
@ -213,35 +184,6 @@ export class TextBox extends React.Component<ITextBoxProps, ITextBoxState> {
}
}
private renderLabelLink() {
if (!this.props.labelLinkText) {
return null
}
return (
<LinkButton
uri={this.props.labelLinkUri}
onClick={this.props.onLabelLinkClick}
className="link-label"
>
{this.props.labelLinkText}
</LinkButton>
)
}
private renderLabel() {
if (!this.props.label) {
return null
}
return (
<div className="label-container">
<label htmlFor={this.state.inputId}>{this.props.label}</label>
{this.renderLabelLink()}
</div>
)
}
private onContextMenu = (event: React.MouseEvent<any>) => {
event.preventDefault()
showContextualMenu([{ role: 'editMenu' }])
@ -290,12 +232,12 @@ export class TextBox extends React.Component<ITextBoxProps, ITextBoxState> {
}
public render() {
const className = classNames('text-box-component', this.props.className)
const inputId = this.props.label ? this.state.inputId : undefined
const { label, className } = this.props
const inputId = label ? this.state.inputId : undefined
return (
<div className={className}>
{this.renderLabel()}
<div className={classNames('text-box-component', className)}>
{label && <label htmlFor={inputId}>{label}</label>}
<input
id={inputId}

View file

@ -0,0 +1,102 @@
import { Repository } from '../../models/repository'
import { IMenuItem } from '../../lib/menu-item'
import { enableRepositoryAliases } from '../../lib/feature-flag'
import { Repositoryish } from './group-repositories'
import { clipboard } from 'electron'
import {
RevealInFileManagerLabel,
DefaultEditorLabel,
} from '../lib/context-menu'
interface IRepositoryListItemContextMenuConfig {
repository: Repositoryish
shellLabel: string
externalEditorLabel: string | undefined
askForConfirmationOnRemoveRepository: boolean
onViewOnGitHub: (repository: Repositoryish) => void
onOpenInShell: (repository: Repositoryish) => void
onShowRepository: (repository: Repositoryish) => void
onOpenInExternalEditor: (repository: Repositoryish) => void
onRemoveRepository: (repository: Repositoryish) => void
onChangeRepositoryAlias: (repository: Repository) => void
onRemoveRepositoryAlias: (repository: Repository) => void
}
export const generateRepositoryListContextMenu = (
config: IRepositoryListItemContextMenuConfig
) => {
const { repository } = config
const missing = repository instanceof Repository && repository.missing
const github =
repository instanceof Repository && repository.gitHubRepository != null
const openInExternalEditor = config.externalEditorLabel
? `Open in ${config.externalEditorLabel}`
: DefaultEditorLabel
const items: ReadonlyArray<IMenuItem> = [
...buildAliasMenuItems(config),
{
label: __DARWIN__ ? 'Copy Repo Name' : 'Copy repo name',
action: () => clipboard.writeText(repository.name),
},
{
label: __DARWIN__ ? 'Copy Repo Path' : 'Copy repo path',
action: () => clipboard.writeText(repository.path),
},
{ type: 'separator' },
{
label: 'View on GitHub',
action: () => config.onViewOnGitHub(repository),
enabled: github,
},
{
label: `Open in ${config.shellLabel}`,
action: () => config.onOpenInShell(repository),
enabled: !missing,
},
{
label: RevealInFileManagerLabel,
action: () => config.onShowRepository(repository),
enabled: !missing,
},
{
label: openInExternalEditor,
action: () => config.onOpenInExternalEditor(repository),
enabled: !missing,
},
{ type: 'separator' },
{
label: config.askForConfirmationOnRemoveRepository ? 'Remove…' : 'Remove',
action: () => config.onRemoveRepository(repository),
},
]
return items
}
const buildAliasMenuItems = (
config: IRepositoryListItemContextMenuConfig
): ReadonlyArray<IMenuItem> => {
const { repository } = config
if (!(repository instanceof Repository) || !enableRepositoryAliases()) {
return []
}
const verb = repository.alias == null ? 'Create' : 'Change'
const items: Array<IMenuItem> = [
{
label: __DARWIN__ ? `${verb} Alias` : `${verb} alias`,
action: () => config.onChangeRepositoryAlias(repository),
},
]
if (repository.alias !== null) {
items.push({
label: __DARWIN__ ? 'Remove Alias' : 'Remove alias',
action: () => config.onRemoveRepositoryAlias(repository),
})
}
return items
}

View file

@ -1,24 +1,18 @@
import * as React from 'react'
import { clipboard } from 'electron'
import { Repository } from '../../models/repository'
import { Octicon, iconForRepository } from '../octicons'
import * as OcticonSymbol from '../octicons/octicons.generated'
import { showContextualMenu } from '../../lib/menu-item'
import { Repositoryish } from './group-repositories'
import { IMenuItem } from '../../lib/menu-item'
import { HighlightText } from '../lib/highlight-text'
import { IMatches } from '../../lib/fuzzy-find'
import { IAheadBehind } from '../../models/branch'
import {
RevealInFileManagerLabel,
DefaultEditorLabel,
} from '../lib/context-menu'
import { enableRepositoryAliases } from '../../lib/feature-flag'
import classNames from 'classnames'
import { createObservableRef } from '../lib/observable-ref'
import { Tooltip } from '../lib/tooltip'
import { TooltippedContent } from '../lib/tooltipped-content'
import { generateRepositoryListContextMenu } from './repository-list-item-context-menu'
interface IRepositoryListItemProps {
readonly repository: Repositoryish
@ -154,121 +148,23 @@ export class RepositoryListItem extends React.Component<
private onContextMenu = (event: React.MouseEvent<any>) => {
event.preventDefault()
const repository = this.props.repository
const missing = repository instanceof Repository && repository.missing
const github =
repository instanceof Repository && repository.gitHubRepository != null
const openInExternalEditor = this.props.externalEditorLabel
? `Open in ${this.props.externalEditorLabel}`
: DefaultEditorLabel
const items: ReadonlyArray<IMenuItem> = [
...this.buildAliasMenuItems(),
{
label: __DARWIN__ ? 'Copy Repo Name' : 'Copy repo name',
action: this.copyNameToClipboard,
},
{
label: __DARWIN__ ? 'Copy Repo Path' : 'Copy repo path',
action: this.copyPathToClipboard,
},
{ type: 'separator' },
{
label: 'View on GitHub',
action: this.viewOnGitHub,
enabled: github,
},
{
label: `Open in ${this.props.shellLabel}`,
action: this.openInShell,
enabled: !missing,
},
{
label: RevealInFileManagerLabel,
action: this.showRepository,
enabled: !missing,
},
{
label: openInExternalEditor,
action: this.openInExternalEditor,
enabled: !missing,
},
{ type: 'separator' },
{
label: this.props.askForConfirmationOnRemoveRepository
? 'Remove…'
: 'Remove',
action: this.removeRepository,
},
]
const items = generateRepositoryListContextMenu({
onRemoveRepository: this.props.onRemoveRepository,
onShowRepository: this.props.onShowRepository,
onOpenInShell: this.props.onOpenInShell,
onOpenInExternalEditor: this.props.onOpenInExternalEditor,
askForConfirmationOnRemoveRepository:
this.props.askForConfirmationOnRemoveRepository,
externalEditorLabel: this.props.externalEditorLabel,
onChangeRepositoryAlias: this.props.onChangeRepositoryAlias,
onRemoveRepositoryAlias: this.props.onRemoveRepositoryAlias,
onViewOnGitHub: this.props.onViewOnGitHub,
repository: this.props.repository,
shellLabel: this.props.shellLabel,
})
showContextualMenu(items)
}
private buildAliasMenuItems(): ReadonlyArray<IMenuItem> {
const repository = this.props.repository
if (!(repository instanceof Repository) || !enableRepositoryAliases()) {
return []
}
const verb = repository.alias == null ? 'Create' : 'Change'
const items: Array<IMenuItem> = [
{
label: __DARWIN__ ? `${verb} Alias` : `${verb} alias`,
action: this.changeAlias,
},
]
if (repository.alias !== null) {
items.push({
label: __DARWIN__ ? 'Remove Alias' : 'Remove alias',
action: this.removeAlias,
})
}
return items
}
private removeRepository = () => {
this.props.onRemoveRepository(this.props.repository)
}
private showRepository = () => {
this.props.onShowRepository(this.props.repository)
}
private viewOnGitHub = () => {
this.props.onViewOnGitHub(this.props.repository)
}
private openInShell = () => {
this.props.onOpenInShell(this.props.repository)
}
private openInExternalEditor = () => {
this.props.onOpenInExternalEditor(this.props.repository)
}
private changeAlias = () => {
if (this.props.repository instanceof Repository) {
this.props.onChangeRepositoryAlias(this.props.repository)
}
}
private removeAlias = () => {
if (this.props.repository instanceof Repository) {
this.props.onRemoveRepositoryAlias(this.props.repository)
}
}
private copyNameToClipboard = () => {
clipboard.writeText(this.props.repository.name)
}
private copyPathToClipboard = () => {
clipboard.writeText(this.props.repository.path)
}
}
const renderRepoIndicators: React.FunctionComponent<{

View file

@ -265,6 +265,7 @@ export class RepositoryView extends React.Component<
isLocalRepository={remote === null}
compareState={compareState}
selectedCommitShas={shas}
shasToHighlight={compareState.shasToHighlight}
currentBranch={currentBranch}
emoji={emoji}
commitLookup={commitLookup}
@ -373,7 +374,8 @@ export class RepositoryView extends React.Component<
private renderContentForHistory(): JSX.Element {
const { commitSelection, commitLookup, localCommitSHAs } = this.props.state
const { changesetData, file, diff, shas, isContiguous } = commitSelection
const { changesetData, file, diff, shas, shasInDiff, isContiguous } =
commitSelection
const selectedCommits = []
for (const sha of shas) {
@ -393,6 +395,7 @@ export class RepositoryView extends React.Component<
isLocalRepository={this.props.state.remote === null}
dispatcher={this.props.dispatcher}
selectedCommits={selectedCommits}
shasInDiff={shasInDiff}
isContiguous={isContiguous}
localCommitSHAs={localCommitSHAs}
changesetData={changesetData}

View file

@ -265,8 +265,6 @@ export class SignIn extends React.Component<ISignInProps, ISignInState> {
label="Authentication code"
value={this.state.otpToken}
onValueChanged={this.onOTPTokenChanged}
labelLinkText={`What's this?`}
labelLinkUri="https://help.github.com/articles/providing-your-2fa-authentication-code/"
autoFocus={true}
/>
</Row>

View file

@ -19,6 +19,10 @@ import { dragAndDropManager } from '../../lib/drag-and-drop-manager'
import { DragType } from '../../models/drag-drop'
import { CICheckRunPopover } from '../check-runs/ci-check-run-popover'
import { TooltipTarget } from '../lib/tooltip'
import { BranchType, Branch } from '../../models/branch'
import { PopupType } from '../../models/popup'
import { generateBranchContextMenuItems } from '../branches/branch-list-item-context-menu'
import { showContextualMenu } from '../../lib/menu-item'
interface IBranchDropdownProps {
readonly dispatcher: Dispatcher
@ -84,7 +88,6 @@ export class BranchDropdown extends React.Component<
const tip = repositoryState.branchesState.tip
const currentBranch = tip.kind === TipState.Valid ? tip.branch : null
return (
<BranchesContainer
allBranches={branchesState.allBranches}
@ -98,6 +101,8 @@ export class BranchDropdown extends React.Component<
currentPullRequest={this.props.currentPullRequest}
isLoadingPullRequests={this.props.isLoadingPullRequests}
emoji={this.props.emoji}
onDeleteBranch={this.onDeleteBranch}
onRenameBranch={this.onRenameBranch}
/>
)
}
@ -190,6 +195,7 @@ export class BranchDropdown extends React.Component<
iconClassName={iconClassName}
title={title}
description={description}
onContextMenu={this.onBranchToolbarButtonContextMenu}
tooltip={isOpen ? undefined : tooltip}
onDropdownStateChanged={this.onDropDownStateChanged}
dropdownContentRenderer={this.renderBranchFoldout}
@ -222,6 +228,76 @@ export class BranchDropdown extends React.Component<
}
}
private onBranchToolbarButtonContextMenu = (
event: React.MouseEvent<HTMLButtonElement>
): void => {
event.preventDefault()
const { tip } = this.props.repositoryState.branchesState
if (tip.kind !== TipState.Valid) {
return
}
const items = generateBranchContextMenuItems({
name: tip.branch.name,
isLocal: tip.branch.type === BranchType.Local,
onRenameBranch: this.onRenameBranch,
onDeleteBranch: this.onDeleteBranch,
})
showContextualMenu(items)
}
private getBranchWithName(branchName: string): Branch | undefined {
return this.props.repositoryState.branchesState.allBranches.find(
branch => branch.name === branchName
)
}
private onRenameBranch = (branchName: string) => {
const branch = this.getBranchWithName(branchName)
if (branch === undefined) {
return
}
this.props.dispatcher.showPopup({
type: PopupType.RenameBranch,
repository: this.props.repository,
branch,
})
}
private onDeleteBranch = async (branchName: string) => {
const branch = this.getBranchWithName(branchName)
const { dispatcher, repository } = this.props
if (branch === undefined) {
return
}
if (branch.type === BranchType.Remote) {
dispatcher.showPopup({
type: PopupType.DeleteRemoteBranch,
repository,
branch,
})
return
}
const aheadBehind = await dispatcher.getBranchAheadBehind(
repository,
branch
)
dispatcher.showPopup({
type: PopupType.DeleteBranch,
repository,
branch,
existsOnRemote: aheadBehind !== null,
})
}
private onBadgeClick = () => {
// The badge can't be clicked while the CI status popover is shown, because
// in that case the Popover component will recognize the "click outside"

View file

@ -38,6 +38,13 @@ export interface IToolbarButtonProps {
*/
readonly onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* An optional event handler for when the button's context menu
* is activated by a pointer event or by hitting the menu key
* while focused.
*/
readonly onContextMenu?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* A function that's called when the user hovers over the button with
* a pointer device.
@ -206,6 +213,7 @@ export class ToolbarButton extends React.Component<IToolbarButtonProps, {}> {
)}
<Button
onClick={this.onClick}
onContextMenu={this.props.onContextMenu}
ref={this.onButtonRef}
disabled={this.props.disabled}
onMouseEnter={this.props.onMouseEnter}

View file

@ -65,6 +65,13 @@ export interface IToolbarDropdownProps {
*/
readonly dropdownContentRenderer: () => JSX.Element | null
/**
* A callback which is invoked when the button's context menu
* is activated. The source event is passed along and can be
* used to prevent the default action or stop the event from bubbling.
*/
readonly onContextMenu?: (event: React.MouseEvent<HTMLButtonElement>) => void
/**
* A function that's called whenever something is dragged over the
* dropdown.
@ -238,6 +245,10 @@ export class ToolbarDropdown extends React.Component<
this.props.onDropdownStateChanged(newState, source)
}
private onContextMenu = (event: React.MouseEvent<HTMLButtonElement>) => {
this.props.onContextMenu?.(event)
}
private updateClientRectIfNecessary() {
if (this.props.dropdownState === 'open' && this.innerButton) {
const newRect = this.innerButton.getButtonBoundingClientRect()
@ -380,6 +391,7 @@ export class ToolbarDropdown extends React.Component<
description={this.props.description}
tooltip={this.props.tooltip}
onClick={this.onClick}
onContextMenu={this.onContextMenu}
onMouseEnter={this.props.onMouseEnter}
style={this.props.style}
iconClassName={this.props.iconClassName}

View file

@ -5,22 +5,8 @@
flex-direction: column;
flex: 1;
.label-container {
display: flex;
label {
@include ellipsis;
flex-grow: 1;
}
.link-label {
@include ellipsis;
justify-content: flex-end;
flex-shrink: 0;
flex-grow: 0;
}
& > label {
overflow-wrap: anywhere;
margin-bottom: var(--spacing-third);
}

View file

@ -52,6 +52,22 @@
}
}
&.has-highlighted-commits {
.list-item {
transition: opacity 0.5s;
&:not(.highlighted) {
opacity: 30%;
}
}
}
&:not(.has-highlighted-commits) {
.list-item {
transition: opacity 0.25s;
}
}
.focus-within {
.list-item.selected {
.commit {

View file

@ -60,6 +60,21 @@
}
}
}
.commit-unreachable-info {
padding: var(--spacing-half) var(--spacing);
border-bottom: var(--base-border);
display: flex;
align-items: center;
.octicon {
margin-right: var(--spacing-half);
}
.link-button-component {
margin-right: var(--spacing-half);
}
}
}
// NOTE: This isn't a real class, it's an SCSS hack to allow us to only write

View file

@ -363,10 +363,10 @@ delegates@^1.0.0:
resolved "https://registry.yarnpkg.com/delegates/-/delegates-1.0.0.tgz#84c6e159b81904fdca59a0ef44cd870d31250f9a"
integrity sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=
desktop-notifications@^0.2.2:
version "0.2.2"
resolved "https://registry.yarnpkg.com/desktop-notifications/-/desktop-notifications-0.2.2.tgz#197a32ee504a894ad074793dab285681c533e5ac"
integrity sha512-XMnxdWV6ZfiCzLZNC00n6h30Jt3z8yv21FAPBt/kK6hANI0PaoYWsRKEYya0zMUAX/9lrh429R5OtRGOKWZQSw==
desktop-notifications@^0.2.4:
version "0.2.4"
resolved "https://registry.yarnpkg.com/desktop-notifications/-/desktop-notifications-0.2.4.tgz#d51db18be583784a296748efd361fe3a5aefea70"
integrity sha512-Au0CbBE4TQ09vsC4/L15FhDsCr/daIyYUY3vkx4S3Y0Sp9EPpbXxBdmz0ry1cOnQ8A47K45zTYsuKP7a+xHm7Q==
dependencies:
node-addon-api "^5.0.0"
prebuild-install "^7.0.1"

View file

@ -1,6 +1,7 @@
{
"releases": {
"3.0.4": ["[Improved] Upgrade embedded Git to 2.35.4"],
"3.0.4-beta1": ["[Improved] Upgrade embedded Git to 2.35.4"],
"3.0.3": [
"[Added] Add Aptana Studio support - #14669. Thanks @tsvetilian-ty!",
"[Fixed] Fix crash when user's locale is unsupported by the spellchecker - #14817. Thanks @tsvetilian-ty!",

View file

@ -54,17 +54,15 @@ $ set GITHUB_ACCESS_TOKEN={your token here}
$ $env:GITHUB_ACCESS_TOKEN="{your token here}"
```
### 2. Create Release Branch
### 2. Switch to the Commit of the Release
Create a new branch to represent the work that will be released to users:
You have to switch to the commit that represents the work that will be released to users:
- for `beta` releases, branch from `development` to ensure the latest changes are published
- for `production` releases, branch from the latest beta tag
- for `beta` releases, switch to `development` to ensure the latest changes are published
- for `production` releases, check out the latest beta tag
- to find this tag: `git tag | grep 'beta' | sort -r | head -n 1`
When naming the branch, ensure you use the `releases/[version]` pattern to ensure all CI platforms are aware of the branch and will build any PRs that target the branch.
### 3. Create Draft Release
### 3. Create Draft Release and Release Branch
Run the script below (which relies on the your personal access token being set), which will determine the next version from what was previously published, based on the desired channel.
@ -78,12 +76,11 @@ If you are creating a new beta release, the `yarn draft-release beta` command wi
If you are create a new `production` release, you should just combine and sort the previous `beta` changelog entries.
The script will output a draft changelog, which covers everything that's been merged, and probably needs some love.
The output will then explain the next steps:
The script will output a draft changelog, which covers everything that's been merged, and probably needs some love. It will also create the release branch for you. If that fails for whatever reason and you must create the branch manually, ensure you use the `releases/[version]` pattern for its name to ensure all CI platforms are aware of the branch and will build any PRs that target the branch.
If you have pretext release note drafted in `app/static/common/pretext-draft.md`, you can add the `--pretext` flag to generate a pretext change log entry it. Example: `yarn draft-release test --pretext`
The output will then explain the next steps:
```shellsession
Here's what you should do next:
@ -135,6 +132,8 @@ Add your new changelog entries to `changelog.json`, update the version in `app/p
If a maintainer would like to backport a pull request to the next release, it is their responsibility to co-ordinate with the release owner and ensure they are fine with accepting this work.
After pushing the branch, a [GitHub Action](https://github.com/desktop/desktop/blob/development/.github/workflows/release-pr.yml) will create a release Pull Request for it. If that action fails for whatever reason, you can fall back to using the `yarn draft-release:pr` command, or create it manually.
Once your release branch is ready to review and ship, ask the other maintainers to review and approve the changes!
IMPORTANT NOTE: Do NOT "Update branch" and merge development into the release branch. This might be tempting if the "branch is out-of-date with the base branch" dotcom feature is enabled. However, doing so would inadvertently release everything on development to production or beta 🙀

View file

@ -1,14 +1,22 @@
# Roadmap
The following are the larger areas of upcoming work the GitHub Desktop team intends to explore. This is not inclusive of everything we're working on, and it's not written in stone. We'll continue to update it as our priorities evolve.
The following are the larger areas of upcoming work the GitHub Desktop team intends to explore and has recently shipped. This is not inclusive of everything we're working on (see existing open pull requests for that), and it's not written in stone. We'll attempt to update this intermittently as priorities evolve, but can't guarantee that it stays up to date over time.
#### Native arm64 support for macOS and Windows
## Major features shipped in previous releases
- Provide support for arm64 Windows machines: [#9691](https://github.com/desktop/desktop/pull/9691)
#### High signal notifications (2.9.10 and 3.0.0)
## Shipped in previous releases
- Receive a notification when checks fail: [#13655](https://github.com/desktop/desktop/pull/14175)
- Receive a notification when your pull request is reviewed: [#14175](https://github.com/desktop/desktop/pull/14175)
#### Native support for Apple Silicon (M1)
#### Improved commit history: reorder commits, squash commits, amend commits, and create a branch from previous commit (2.9.0)
- Reorder commits via drag/drop: [#12384](https://github.com/desktop/desktop/pull/12384)
- Squash commits via drag/drop: [#12235](https://github.com/desktop/desktop/pull/12235)
- Amend last commit: [#12353](https://github.com/desktop/desktop/pull/12353)
- Create a branch from a previous commit: [#12160](https://github.com/desktop/desktop/pull/12160)
#### Native support for Apple Silicon (M1) machines (2.8.2)
- Provide support for Apple Silicon (M1) machines: [#9691](https://github.com/desktop/desktop/pull/9691)

View file

@ -153,7 +153,7 @@
"@types/webpack-hot-middleware": "^2.25.6",
"@types/webpack-merge": "^5.0.0",
"@types/xml2js": "^0.4.0",
"electron": "17.0.1",
"electron": "19.0.0",
"electron-builder": "^22.7.0",
"electron-packager": "^15.1.0",
"electron-winstaller": "^5.0.0",

View file

@ -48,12 +48,21 @@ function capitalized(str: string): string {
*/
export function findReleaseNote(body: string): string | null | undefined {
const re = /^Notes: (.+)$/gm
const matches = re.exec(body)
if (!matches || matches.length < 2) {
let lastMatches = null
// There might be multiple lines starting with "Notes: ", but we're only
// interested in the last one.
let matches = re.exec(body)
while (matches) {
lastMatches = matches
matches = re.exec(body)
}
if (!lastMatches || lastMatches.length < 2) {
return undefined
}
const note = matches[1].replace(/\.$/, '')
const note = lastMatches[1].replace(/\.$/, '')
return note === 'no-notes' ? null : note
}

View file

@ -84,6 +84,19 @@ Notes: [Fixed] Fix lorem impsum dolor sit amet.
)
})
it('looks for the last Notes entry if there are several', () => {
const body = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sollicitudin turpis
tempor euismod fermentum. Nullam hendrerit neque eget risus faucibus volutpat. Donec
ultrices, orci quis auctor ultrices, nulla lacus gravida lectus, non rutrum dolor
quam vel augue.
Notes: ignore this notes
Notes: These are valid notes
`
expect(findReleaseNote(body)).toBe('These are valid notes')
})
it('detected no release notes wanted for the PR', () => {
const body = `
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Integer sollicitudin turpis

466
yarn.lock
View file

@ -506,10 +506,10 @@
ajv "^6.12.0"
ajv-keywords "^3.4.1"
"@electron/get@^1.13.0":
version "1.13.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.13.1.tgz#42a0aa62fd1189638bd966e23effaebb16108368"
integrity sha512-U5vkXDZ9DwXtkPqlB45tfYnnYBN8PePp1z/XDCupnSpdrxT8/ThCv9WCwPLf9oqiSGZTkH6dx2jDUPuoXpjkcA==
"@electron/get@^1.14.1":
version "1.14.1"
resolved "https://registry.yarnpkg.com/@electron/get/-/get-1.14.1.tgz#16ba75f02dffb74c23965e72d617adc721d27f40"
integrity sha512-BrZYyL/6m0ZXz/lDxy/nlVhQz+WF+iPS6qXolEU8atw7h6v1aYkjwJZ63m+bJMBTxDE66X+r2tPS4a/8C82sZw==
dependencies:
debug "^4.1.1"
env-paths "^2.2.0"
@ -838,6 +838,11 @@
dependencies:
defer-to-connect "^1.0.1"
"@tootallnate/once@1":
version "1.1.2"
resolved "https://registry.yarnpkg.com/@tootallnate/once/-/once-1.1.2.tgz#ccb91445360179a04e7fe6aff78c00ffc1eeaf82"
integrity sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==
"@types/babel__core@^7.0.0", "@types/babel__core@^7.1.7":
version "7.1.12"
resolved "https://registry.yarnpkg.com/@types/babel__core/-/babel__core-7.1.12.tgz#4d8e9e51eb265552a7e4f1ff2219ab6133bdfb2d"
@ -1158,10 +1163,10 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.11.tgz#6ea7342dfb379ea1210835bada87b3c512120234"
integrity sha512-KB0sixD67CeecHC33MYn+eYARkqTheIRNuu97y2XMjR7Wu3XibO1vaY6VBV6O/a89SPI81cEUIYT87UqUWlZNw==
"@types/node@^14.6.2":
version "14.17.5"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.17.5.tgz#b59daf6a7ffa461b5648456ca59050ba8e40ed54"
integrity sha512-bjqH2cX/O33jXT/UmReo2pM7DIJREPMnarixbQ57DOOzzFaI6D2+IcwaJQaJpv0M1E9TIhPCYVxrkcityLjlqA==
"@types/node@^16.11.26":
version "16.11.40"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.40.tgz#bcf85f3febe74436107aeb2d3fb5fd0d30818600"
integrity sha512-7bOWglXUO6f21NG3YDI7hIpeMX3M59GG+DzZuzX2EkFKYUnRoxq3EOg4R0KNv2hxryY9M3UUqG5akwwsifrukw==
"@types/normalize-package-data@^2.4.0":
version "2.4.0"
@ -1659,10 +1664,10 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
abab@^2.0.3:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
integrity sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==
abab@^2.0.3, abab@^2.0.5:
version "2.0.6"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.6.tgz#41b80f2c871d19686216b82309231cfd3cb3d291"
integrity sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==
accepts@~1.3.7:
version "1.3.7"
@ -1700,20 +1705,22 @@ acorn-walk@^8.0.0:
resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1"
integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA==
acorn@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.3.1.tgz#85010754db53c3fbaf3b9ea3e083aa5c5d147ffd"
integrity sha512-tLc0wSnatxAQHVHUapaHdz72pi9KUyHjq5KyHjGg9Y8Ifdc79pTh2XvI6I1/chZbnM7QtNKzh66ooDogPZSleA==
acorn@^7.1.1, acorn@^7.3.1:
version "7.4.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
acorn@^7.3.1:
version "7.4.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.0.tgz#e1ad486e6c54501634c6c397c5c121daa383607c"
integrity sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==
acorn@^8.0.4, acorn@^8.2.4, acorn@^8.4.1:
version "8.7.1"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.1.tgz#0197122c843d1bf6d0a5e83220a788f278f63c30"
integrity sha512-Xx54uLJQZ19lKygFXOWsscKUbsBZW0CPykPhVQdhIeIwrbPmJzqeASDInc8nKBnp/JT6igTs82qPXz069H8I/A==
acorn@^8.0.4, acorn@^8.4.1:
version "8.7.0"
resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf"
integrity sha512-V/LGr1APy+PXIwKebEWrkZPwoeoF+w1jiOBUmuxuiUIaOHtob8Qc9BTrYo7VuI5fR8tqsy+buA2WFooR5olqvQ==
agent-base@6:
version "6.0.2"
resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77"
integrity sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==
dependencies:
debug "4"
airbnb-browser-shims@^3.0.0:
version "3.0.0"
@ -1785,16 +1792,6 @@ ajv@^4.9.2:
co "^4.6.0"
json-stable-stringify "^1.0.1"
ajv@^5.1.0:
version "5.3.0"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-5.3.0.tgz#4414ff74a50879c208ee5fdc826e32c303549eda"
integrity sha1-RBT/dKUIecII7l/cgm4ywwNUnto=
dependencies:
co "^4.6.0"
fast-deep-equal "^1.0.0"
fast-json-stable-stringify "^2.0.0"
json-schema-traverse "^0.3.0"
ajv@^6.10.0, ajv@^6.12.0, ajv@^6.12.5, ajv@^6.9.1:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
@ -2103,7 +2100,7 @@ async@^3.2.3:
asynckit@^0.4.0:
version "0.4.0"
resolved "https://registry.yarnpkg.com/asynckit/-/asynckit-0.4.0.tgz#c79ed97f7f34cb8f2ba1bc9790bcc366474b4b79"
integrity sha1-x57Zf380y48robyXkLzDZkdLS3k=
integrity sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==
at-least-node@^1.0.0:
version "1.0.0"
@ -2132,11 +2129,6 @@ aws-sign2@~0.7.0:
resolved "https://registry.yarnpkg.com/aws-sign2/-/aws-sign2-0.7.0.tgz#b46e890934a9591f2d2f6f86d7e6a9f1b3fe76a8"
integrity sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=
aws4@^1.6.0:
version "1.6.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.6.0.tgz#83ef5ca860b2b32e4a0deedee8c771b9db57471e"
integrity sha1-g+9cqGCysy5KDe7e6MdxudtXRx4=
aws4@^1.8.0:
version "1.8.0"
resolved "https://registry.yarnpkg.com/aws4/-/aws4-1.8.0.tgz#f0e003d9ca9e7f59c7a508945d7b2ef9a04a542f"
@ -2320,20 +2312,6 @@ boolean@^3.0.1:
resolved "https://registry.yarnpkg.com/boolean/-/boolean-3.0.2.tgz#df1baa18b6a2b0e70840475e1d93ec8fe75b2570"
integrity sha512-RwywHlpCRc3/Wh81MiCKun4ydaIFyW5Ea6JbL6sRCVx5q5irDw7pMXBUFYF/jArQ6YrG36q0kpovc9P/Kd3I4g==
boom@4.x.x:
version "4.3.1"
resolved "https://registry.yarnpkg.com/boom/-/boom-4.3.1.tgz#4f8a3005cb4a7e3889f749030fd25b96e01d2e31"
integrity sha1-T4owBctKfjiJ90kDD9JbluAdLjE=
dependencies:
hoek "4.x.x"
boom@5.x.x:
version "5.2.0"
resolved "https://registry.yarnpkg.com/boom/-/boom-5.2.0.tgz#5dd9da6ee3a5f302077436290cb717d3f4a54e02"
integrity sha512-Z5BTk6ZRe4tXXQlkqftmsAUANpXmuwlsF5Oov8ThoMbQRzdGTA1ngYRW160GexgOgjsFOKJz0LYhoNi+2AMBUw==
dependencies:
hoek "4.x.x"
boxen@^4.2.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/boxen/-/boxen-4.2.0.tgz#e411b62357d6d6d36587c8ac3d5d974daa070e64"
@ -2748,10 +2726,10 @@ combined-stream@1.0.6, combined-stream@~1.0.6:
dependencies:
delayed-stream "~1.0.0"
combined-stream@^1.0.5, combined-stream@~1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.5.tgz#938370a57b4a51dea2c77c15d5c5fdf895164009"
integrity sha1-k4NwpXtKUd6ix3wV1cX9+JUWQAk=
combined-stream@^1.0.8:
version "1.0.8"
resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
integrity sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==
dependencies:
delayed-stream "~1.0.0"
@ -2921,13 +2899,6 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
cryptiles@3.x.x:
version "3.1.2"
resolved "https://registry.yarnpkg.com/cryptiles/-/cryptiles-3.1.2.tgz#a89fbb220f5ce25ec56e8c4aa8a4fd7b5b0d29fe"
integrity sha1-qJ+7Ig9c4l7FboxKqKT9e1sNKf4=
dependencies:
boom "5.x.x"
crypto-random-string@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"
@ -2978,7 +2949,7 @@ cssom@~0.3.6:
resolved "https://registry.yarnpkg.com/cssom/-/cssom-0.3.8.tgz#9f1276f5b2b463f2114d3f2c75250af8c1a36f4a"
integrity sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==
cssstyle@^2.2.0:
cssstyle@^2.3.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/cssstyle/-/cssstyle-2.3.0.tgz#ff665a0ddbdc31864b09647f34163443d90b0852"
integrity sha512-AZL67abkUzIuvcHqk7c09cezpGNcxUxU4Ioi/05xHk4DQeTkWmGYftIE6ctU6AEt+Gn4n1lDStOtj7FKycP71A==
@ -3018,6 +2989,13 @@ debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9:
dependencies:
ms "2.0.0"
debug@4:
version "4.3.4"
resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865"
integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==
dependencies:
ms "2.1.2"
debug@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/debug/-/debug-3.1.0.tgz#5bb5a0672628b64149566ba16819e61518c67261"
@ -3063,10 +3041,10 @@ decamelize@^1.2.0:
resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-1.2.0.tgz#f6534d15148269b20352e7bee26f501f9a191290"
integrity sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=
decimal.js@^10.2.0:
version "10.2.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.2.1.tgz#238ae7b0f0c793d3e3cea410108b35a2c01426a3"
integrity sha512-KaL7+6Fw6i5A2XSnsbhm/6B+NuEA7TZ4vqxnd5tXz9sbKtrN9Srj8ab4vKVdK8YAqZO9P1kg45Y6YLoduPf+kw==
decimal.js@^10.2.1:
version "10.3.1"
resolved "https://registry.yarnpkg.com/decimal.js/-/decimal.js-10.3.1.tgz#d8c3a444a9c6774ba60ca6ad7261c3a94fd5e783"
integrity sha512-V0pfhfr8suzyPGOx3nmq4aHqabehUZn6Ch9kyFpV79TGDTWFmHqUqXdabR7QHqxzrYolF4+tVmJhUG4OURg5dQ==
decode-uri-component@^0.2.0:
version "0.2.0"
@ -3085,11 +3063,16 @@ deep-extend@^0.6.0:
resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
deep-is@^0.1.3, deep-is@~0.1.3:
deep-is@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.3.tgz#b369d6fb5dbc13eecf524f91b070feedc357cf34"
integrity sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=
deep-is@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
integrity sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==
deepmerge@^4.2.2:
version "4.2.2"
resolved "https://registry.yarnpkg.com/deepmerge/-/deepmerge-4.2.2.tgz#44d2ea3679b8f4d4ffba33f03d865fc1e7bf4955"
@ -3132,7 +3115,7 @@ define-property@^2.0.2:
delayed-stream@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619"
integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk=
integrity sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==
depd@~1.1.2:
version "1.1.2"
@ -3419,13 +3402,13 @@ electron-winstaller@*, electron-winstaller@^5.0.0:
lodash.template "^4.2.2"
temp "^0.9.0"
electron@17.0.1:
version "17.0.1"
resolved "https://registry.yarnpkg.com/electron/-/electron-17.0.1.tgz#e6c7ad2be26e7be8a5a9bac16b21920ad2671224"
integrity sha512-CBReR/QEOpgwMdt59lWCtj9wC8oHB6aAjMF1lhXcGew132xtp+C5N6EaXb/fmDceVYLouziYjbNcpeXsWrqdpA==
electron@19.0.0:
version "19.0.0"
resolved "https://registry.yarnpkg.com/electron/-/electron-19.0.0.tgz#f6b742b708ec118676ba3b38d0f3712d8f0311cf"
integrity sha512-VXwqLQxuIUr0SI8vOYDj5OLPwtKa/trn5DVKd/BFGT/U/IerfVoSZuydGLOjSL5yJlckfmKQpiq+8PW4gI8hXA==
dependencies:
"@electron/get" "^1.13.0"
"@types/node" "^14.6.2"
"@electron/get" "^1.14.1"
"@types/node" "^16.11.26"
extract-zip "^1.0.3"
element-closest@^2.0.2:
@ -3632,13 +3615,13 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
escodegen@^1.14.1:
version "1.14.3"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-1.14.3.tgz#4e7b81fba61581dc97582ed78cab7f0e8d63f503"
integrity sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==
escodegen@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/escodegen/-/escodegen-2.0.0.tgz#5e32b12833e8aa8fa35e1bf0befa89380484c7dd"
integrity sha512-mmHKys/C8BFUGI+MAWNcSYoORYLMdPzjrknd2Vc+bUsjN5bXcr8EhrNB+UTqfL1y3I9c4fw2ihgtMPQLBRiQxw==
dependencies:
esprima "^4.0.1"
estraverse "^4.2.0"
estraverse "^5.2.0"
esutils "^2.0.2"
optionator "^0.8.1"
optionalDependencies:
@ -3835,7 +3818,7 @@ esrecurse@^4.3.0:
dependencies:
estraverse "^5.2.0"
estraverse@^4.1.0, estraverse@^4.1.1, estraverse@^4.2.0:
estraverse@^4.1.0, estraverse@^4.1.1:
version "4.2.0"
resolved "https://registry.yarnpkg.com/estraverse/-/estraverse-4.2.0.tgz#0dee3fed31fcd469618ce7342099fc1afa0bdb13"
integrity sha1-De4/7TH81GlhjOc0IJn8GvoL2xM=
@ -3851,9 +3834,9 @@ estraverse@^5.2.0:
integrity sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==
esutils@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.2.tgz#0abf4f1caa5bcb1f7a9d8acc6dea4faaa04bac9b"
integrity sha1-Cr9PHKpbyx96nYrMbepPqqBLrJs=
version "2.0.3"
resolved "https://registry.yarnpkg.com/esutils/-/esutils-2.0.3.tgz#74d2eb4de0b8da1293711910d50775b9b710ef64"
integrity sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==
etag@~1.8.1:
version "1.8.1"
@ -3996,7 +3979,7 @@ extend-shallow@^3.0.2:
assign-symbols "^1.0.0"
is-extendable "^1.0.1"
extend@^3.0.2, extend@~3.0.1, extend@~3.0.2:
extend@^3.0.2, extend@~3.0.2:
version "3.0.2"
resolved "https://registry.yarnpkg.com/extend/-/extend-3.0.2.tgz#f8b1136b4071fbd8eb140aff858b1019ec2915fa"
integrity sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==
@ -4050,11 +4033,6 @@ fake-indexeddb@^2.0.4:
realistic-structured-clone "^2.0.1"
setimmediate "^1.0.5"
fast-deep-equal@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-1.0.0.tgz#96256a3bc975595eb36d82e9929d060d893439ff"
integrity sha1-liVqO8l1WV6zbYLpkp0GDYk0Of8=
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@ -4081,7 +4059,7 @@ fast-json-stable-stringify@2.x, fast-json-stable-stringify@^2.0.0:
resolved "https://registry.yarnpkg.com/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz#874bf69c6f404c2b5d99c481341399fd55892633"
integrity sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.4:
fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
version "2.0.6"
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
@ -4220,13 +4198,13 @@ forever-agent@~0.6.1:
resolved "https://registry.yarnpkg.com/forever-agent/-/forever-agent-0.6.1.tgz#fbc71f0c41adeb37f96c577ad1ed42d8fdacca91"
integrity sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=
form-data@~2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-2.3.1.tgz#6fb94fbd71885306d73d15cc497fe4cc4ecd44bf"
integrity sha1-b7lPvXGIUwbXPRXMSX/kzE7NRL8=
form-data@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/form-data/-/form-data-3.0.1.tgz#ebd53791b78356a99af9a300d4282c4d5eb9755f"
integrity sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==
dependencies:
asynckit "^0.4.0"
combined-stream "^1.0.5"
combined-stream "^1.0.8"
mime-types "^2.1.12"
form-data@~2.3.2:
@ -4595,14 +4573,6 @@ har-schema@^2.0.0:
resolved "https://registry.yarnpkg.com/har-schema/-/har-schema-2.0.0.tgz#a94c2224ebcac04782a0d9035521f24735b7ec92"
integrity sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=
har-validator@~5.0.3:
version "5.0.3"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.0.3.tgz#ba402c266194f15956ef15e0fcf242993f6a7dfd"
integrity sha1-ukAsJmGU8VlW7xXg/PJCmT9qff0=
dependencies:
ajv "^5.1.0"
har-schema "^2.0.0"
har-validator@~5.1.3:
version "5.1.5"
resolved "https://registry.yarnpkg.com/har-validator/-/har-validator-5.1.5.tgz#1f0803b9f8cb20c0fa13822df1ecddb36bde1efd"
@ -4718,26 +4688,11 @@ hash-base@^3.0.0:
inherits "^2.0.1"
safe-buffer "^5.0.1"
hawk@~6.0.2:
version "6.0.2"
resolved "https://registry.yarnpkg.com/hawk/-/hawk-6.0.2.tgz#af4d914eb065f9b5ce4d9d11c1cb2126eecc3038"
integrity sha512-miowhl2+U7Qle4vdLqDdPt9m09K6yZhkLDTWGoUiUzrQCn+mHHSmfJgAyGaLRZbPmTqfFFjRV1QWCW0VWUJBbQ==
dependencies:
boom "4.x.x"
cryptiles "3.x.x"
hoek "4.x.x"
sntp "2.x.x"
he@^1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f"
integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==
hoek@4.x.x:
version "4.2.0"
resolved "https://registry.yarnpkg.com/hoek/-/hoek-4.2.0.tgz#72d9d0754f7fe25ca2d01ad8f8f9a9449a89526d"
integrity sha512-v0XCLxICi9nPfYrS9RL8HbYnXi9obYAeLbSP00BmnZwCK9+Ih9WOjoZ8YoHCoav2csqn4FOz4Orldsy2dmDwmQ==
hosted-git-info@^2.1.4:
version "2.8.9"
resolved "https://registry.yarnpkg.com/hosted-git-info/-/hosted-git-info-2.8.9.tgz#dffc0bf9a21c02209090f2aa69429e1414daf3f9"
@ -4817,6 +4772,15 @@ http-errors@1.7.2, http-errors@~1.7.2:
statuses ">= 1.5.0 < 2"
toidentifier "1.0.0"
http-proxy-agent@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz#8a8c8ef7f5932ccf953c296ca8291b95aa74aa3a"
integrity sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==
dependencies:
"@tootallnate/once" "1"
agent-base "6"
debug "4"
http-signature@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/http-signature/-/http-signature-1.2.0.tgz#9aecd925114772f3d95b65a60abb8f7c18fbace1"
@ -4826,6 +4790,14 @@ http-signature@~1.2.0:
jsprim "^1.2.2"
sshpk "^1.7.0"
https-proxy-agent@^5.0.0:
version "5.0.1"
resolved "https://registry.yarnpkg.com/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz#c59ef224a04fe8b754f3db0063a25ea30d0005d6"
integrity sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==
dependencies:
agent-base "6"
debug "4"
human-signals@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-1.1.1.tgz#c5b1cd14f50aeae09ab6c59fe63ba3395fe4dfa3"
@ -4938,11 +4910,6 @@ interpret@^1.0.1:
resolved "https://registry.yarnpkg.com/interpret/-/interpret-1.0.4.tgz#820cdd588b868ffb191a809506d6c9c8f212b1b0"
integrity sha1-ggzdWIuGj/sZGoCVBtbJyPISsbA=
ip-regex@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/ip-regex/-/ip-regex-2.1.0.tgz#fa78bf5d2e6913c911ce9f819ee5146bb6d844e9"
integrity sha1-+ni/XS5pE8kRzp+BnuUUa7bYROk=
ipaddr.js@1.9.0:
version "1.9.0"
resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.0.tgz#37df74e430a0e47550fe54a2defe30d8acd95f65"
@ -5193,10 +5160,10 @@ is-plain-object@^2.0.1, is-plain-object@^2.0.3, is-plain-object@^2.0.4:
dependencies:
isobject "^3.0.1"
is-potential-custom-element-name@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.0.tgz#0c52e54bcca391bb2c494b21e8626d7336c6e397"
integrity sha1-DFLlS8yjkbssSUsh6GJtczbG45c=
is-potential-custom-element-name@^1.0.1:
version "1.0.1"
resolved "https://registry.yarnpkg.com/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz#171ed6f19e3ac554394edf78caa05784a45bebb5"
integrity sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==
is-regex@^1.0.4:
version "1.0.4"
@ -5902,35 +5869,36 @@ jsdoc-type-pratt-parser@~2.2.2:
integrity sha512-zRokSWcPLSWkoNzsWn9pq7YYSwDhKyEe+cJYT2qaPqLOOJb5sFSi46BPj81vP+e8chvCNdQL9RG86Bi9EI6MDw==
jsdom@^16.4.0:
version "16.4.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.4.0.tgz#36005bde2d136f73eee1a830c6d45e55408edddb"
integrity sha512-lYMm3wYdgPhrl7pDcRmvzPhhrGVBeVhPIqeHjzeiHN3DFmD1RBpbExbi8vU7BJdH8VAZYovR8DMt0PNNDM7k8w==
version "16.7.0"
resolved "https://registry.yarnpkg.com/jsdom/-/jsdom-16.7.0.tgz#918ae71965424b197c819f8183a754e18977b710"
integrity sha512-u9Smc2G1USStM+s/x1ru5Sxrl6mPYCbByG1U/hUmqaVsm4tbNyS7CicOSRyuGQYZhTu0h84qkZZQ/I+dzizSVw==
dependencies:
abab "^2.0.3"
acorn "^7.1.1"
abab "^2.0.5"
acorn "^8.2.4"
acorn-globals "^6.0.0"
cssom "^0.4.4"
cssstyle "^2.2.0"
cssstyle "^2.3.0"
data-urls "^2.0.0"
decimal.js "^10.2.0"
decimal.js "^10.2.1"
domexception "^2.0.1"
escodegen "^1.14.1"
escodegen "^2.0.0"
form-data "^3.0.0"
html-encoding-sniffer "^2.0.1"
is-potential-custom-element-name "^1.0.0"
http-proxy-agent "^4.0.1"
https-proxy-agent "^5.0.0"
is-potential-custom-element-name "^1.0.1"
nwsapi "^2.2.0"
parse5 "5.1.1"
request "^2.88.2"
request-promise-native "^1.0.8"
saxes "^5.0.0"
parse5 "6.0.1"
saxes "^5.0.1"
symbol-tree "^3.2.4"
tough-cookie "^3.0.1"
tough-cookie "^4.0.0"
w3c-hr-time "^1.0.2"
w3c-xmlserializer "^2.0.0"
webidl-conversions "^6.1.0"
whatwg-encoding "^1.0.5"
whatwg-mimetype "^2.3.0"
whatwg-url "^8.0.0"
ws "^7.2.3"
whatwg-url "^8.5.0"
ws "^7.4.6"
xml-name-validator "^3.0.0"
jsesc@^2.5.1:
@ -5960,11 +5928,6 @@ json-parse-even-better-errors@^2.3.0:
resolved "https://registry.yarnpkg.com/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz#7c47805a94319928e05777405dc12e1f7a4ee02d"
integrity sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==
json-schema-traverse@^0.3.0:
version "0.3.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.3.1.tgz#349a6d44c53a51de89b40805c5d5e59b417d3340"
integrity sha1-NJptRMU6Ud6JtAgFxdXlm0F9M0A=
json-schema-traverse@^0.4.1:
version "0.4.1"
resolved "https://registry.yarnpkg.com/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz#69f6a87d9513ab8bb8fe63bdb0979c448e684660"
@ -6171,7 +6134,7 @@ levn@^0.4.1:
levn@~0.3.0:
version "0.3.0"
resolved "https://registry.yarnpkg.com/levn/-/levn-0.3.0.tgz#3b09924edf9f083c0490fdd4c0bc4421e04764ee"
integrity sha1-OwmSTt+fCDwEkP3UwLxEIeBHZO4=
integrity sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==
dependencies:
prelude-ls "~1.1.2"
type-check "~0.3.2"
@ -6286,7 +6249,7 @@ lodash.templatesettings@^4.0.0:
dependencies:
lodash._reinterpolate "~3.0.0"
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21:
lodash@^4.17.10, lodash@^4.17.11, lodash@^4.17.19, lodash@^4.17.20, lodash@^4.17.21, lodash@^4.7.0:
version "4.17.21"
resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c"
integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==
@ -6470,22 +6433,22 @@ mime-db@1.51.0:
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.51.0.tgz#d9ff62451859b18342d960850dc3cfb77e63fb0c"
integrity sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==
mime-db@~1.30.0:
version "1.30.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.30.0.tgz#74c643da2dd9d6a45399963465b26d5ca7d71f01"
integrity sha1-dMZD2i3Z1qRTmZY0ZbJtXKfXHwE=
mime-db@1.52.0:
version "1.52.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.52.0.tgz#bbabcdc02859f4987301c856e3387ce5ec43bf70"
integrity sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==
mime-db@~1.36.0:
version "1.36.0"
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.36.0.tgz#5020478db3c7fe93aad7bbcc4dcf869c43363397"
integrity sha512-L+xvyD9MkoYMXb1jAmzI/lWYAxAMCPvIBSWur0PZ5nOf5euahRLVqH//FKW9mWp2lkqUgYiXPgkzfMUFi4zVDw==
mime-types@^2.1.12, mime-types@~2.1.17:
version "2.1.17"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.17.tgz#09d7a393f03e995a79f8af857b70a9e0ab16557a"
integrity sha1-Cdejk/A+mVp5+K+Fe3Cp4KsWVXo=
mime-types@^2.1.12:
version "2.1.35"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a"
integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==
dependencies:
mime-db "~1.30.0"
mime-db "1.52.0"
mime-types@^2.1.27, mime-types@^2.1.31:
version "2.1.34"
@ -6774,11 +6737,6 @@ nwsapi@^2.2.0:
resolved "https://registry.yarnpkg.com/nwsapi/-/nwsapi-2.2.0.tgz#204879a9e3d068ff2a55139c2c772780681a38b7"
integrity sha512-h2AatdwYH+JHiZpv7pt/gSX1XoRGb7L/qSIeuqA6GwYoF9w1vP1cw42TO0aI2pNyshRK5893hNSl+1//vHK7hQ==
oauth-sign@~0.8.2:
version "0.8.2"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.8.2.tgz#46a6ab7f0aead8deae9ec0565780b7d4efeb9d43"
integrity sha1-Rqarfwrq2N6unsBWV4C31O/rnUM=
oauth-sign@~0.9.0:
version "0.9.0"
resolved "https://registry.yarnpkg.com/oauth-sign/-/oauth-sign-0.9.0.tgz#47a7b016baa68b5fa0ecf3dee08a85c679ac6455"
@ -6942,16 +6900,16 @@ opener@^1.5.2:
integrity sha512-ur5UIdyw5Y7yEj9wLzhqXiy6GZ3Mwx0yGI+5sMn2r0N0v3cKJvUmFH5yPP+WXh9e0xfyzyJX95D8l088DNFj7A==
optionator@^0.8.1:
version "0.8.2"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.2.tgz#364c5e409d3f4d6301d6c0b4c05bba50180aeb64"
integrity sha1-NkxeQJ0/TWMB1sC0wFu6UBgK62Q=
version "0.8.3"
resolved "https://registry.yarnpkg.com/optionator/-/optionator-0.8.3.tgz#84fa1d036fe9d3c7e21d99884b601167ec8fb495"
integrity sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==
dependencies:
deep-is "~0.1.3"
fast-levenshtein "~2.0.4"
fast-levenshtein "~2.0.6"
levn "~0.3.0"
prelude-ls "~1.1.2"
type-check "~0.3.2"
wordwrap "~1.0.0"
word-wrap "~1.2.3"
optionator@^0.9.1:
version "0.9.1"
@ -7088,10 +7046,10 @@ parse-json@^5.0.0:
json-parse-even-better-errors "^2.3.0"
lines-and-columns "^1.1.6"
parse5@5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-5.1.1.tgz#f68e4e5ba1852ac2cadc00f4555fff6c2abb6178"
integrity sha512-ugq4DFI0Ptb+WWjAdOK16+u/nHfiIrcE+sh8kZMaM0WllQKLI9rOUq6c2b7cwPkXdzfQESqvoqK6ug7U/Yyzug==
parse5@6.0.1:
version "6.0.1"
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
parseurl@~1.3.3:
version "1.3.3"
@ -7284,7 +7242,7 @@ prelude-ls@^1.2.1:
prelude-ls@~1.1.2:
version "1.1.2"
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
integrity sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==
prepend-http@^2.0.0:
version "2.0.0"
@ -7418,7 +7376,7 @@ pseudomap@^1.0.2:
resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
psl@^1.1.28:
psl@^1.1.28, psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
integrity sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==
@ -7431,11 +7389,6 @@ pump@^3.0.0:
end-of-stream "^1.1.0"
once "^1.3.1"
punycode@^1.4.1:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
punycode@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-2.1.0.tgz#5f863edc89b96db09074bad7947bf09056ca4e7d"
@ -7458,11 +7411,6 @@ qs@6.7.0:
resolved "https://registry.yarnpkg.com/qs/-/qs-6.7.0.tgz#41dc1a015e3d581f1621776be31afb2876a9b1bc"
integrity sha512-VCdBRNFTX1fyE7Nb6FYoURo/SPe62QCaAyzJvUjwRaIsc+NePBEniHlvxFmmX56+HZphIGtV0XeCirBtpDrTyQ==
qs@~6.5.1:
version "6.5.1"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.1.tgz#349cdf6eef89ec45c12d7d5eb3fc0c870343a6d8"
integrity sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==
qs@~6.5.2:
version "6.5.2"
resolved "https://registry.yarnpkg.com/qs/-/qs-6.5.2.tgz#cb3ae806e8740444584ef154ce8ee98d403f3e36"
@ -7761,51 +7709,7 @@ repeat-string@^1.6.1:
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
request-promise-core@1.1.4:
version "1.1.4"
resolved "https://registry.yarnpkg.com/request-promise-core/-/request-promise-core-1.1.4.tgz#3eedd4223208d419867b78ce815167d10593a22f"
integrity sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==
dependencies:
lodash "^4.17.19"
request-promise-native@^1.0.8:
version "1.0.9"
resolved "https://registry.yarnpkg.com/request-promise-native/-/request-promise-native-1.0.9.tgz#e407120526a5efdc9a39b28a5679bf47b9d9dc28"
integrity sha512-wcW+sIUiWnKgNY0dqCpOZkUbF/I+YPi+f09JZIDa39Ec+q82CpSYniDp+ISgTTbKmnpJWASeJBPZmoxH84wt3g==
dependencies:
request-promise-core "1.1.4"
stealthy-require "^1.1.1"
tough-cookie "^2.3.3"
request@^2.72.0:
version "2.83.0"
resolved "https://registry.yarnpkg.com/request/-/request-2.83.0.tgz#ca0b65da02ed62935887808e6f510381034e3356"
integrity sha512-lR3gD69osqm6EYLk9wB/G1W/laGWjzH90t1vEa2xuxHD5KUrSzp9pUSfTm+YC5Nxt2T8nMPEvKlhbQayU7bgFw==
dependencies:
aws-sign2 "~0.7.0"
aws4 "^1.6.0"
caseless "~0.12.0"
combined-stream "~1.0.5"
extend "~3.0.1"
forever-agent "~0.6.1"
form-data "~2.3.1"
har-validator "~5.0.3"
hawk "~6.0.2"
http-signature "~1.2.0"
is-typedarray "~1.0.0"
isstream "~0.1.2"
json-stringify-safe "~5.0.1"
mime-types "~2.1.17"
oauth-sign "~0.8.2"
performance-now "^2.1.0"
qs "~6.5.1"
safe-buffer "^5.1.1"
stringstream "~0.0.5"
tough-cookie "~2.3.3"
tunnel-agent "^0.6.0"
uuid "^3.1.0"
request@^2.86.0, request@^2.88.2:
request@^2.72.0, request@^2.86.0:
version "2.88.2"
resolved "https://registry.yarnpkg.com/request/-/request-2.88.2.tgz#d73c918731cb5a87da047e207234146f664d12b3"
integrity sha512-MsvtOrfG9ZcrOwAW+Qi+F6HbD0CWXEh9ou77uOb7FM2WPhwT7smM833PzanhJLsgXjN89Ir6V2PczXNnMpwKhw==
@ -7975,7 +7879,7 @@ safe-buffer@5.1.2, safe-buffer@^5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.1:
safe-buffer@^5.0.1, safe-buffer@^5.1.0:
version "5.1.1"
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853"
integrity sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==
@ -8047,7 +7951,7 @@ sax@>=0.6.0, sax@^1.2.4:
resolved "https://registry.yarnpkg.com/sax/-/sax-1.2.4.tgz#2816234e2378bddc4e5354fab5caa895df7100d9"
integrity sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==
saxes@^5.0.0:
saxes@^5.0.1:
version "5.0.1"
resolved "https://registry.yarnpkg.com/saxes/-/saxes-5.0.1.tgz#eebab953fa3b7608dbe94e5dadb15c888fa6696d"
integrity sha512-5LBh1Tls8c9xgGjw3QrMwETmTMVk0oFgvrFSvWx62llR2hcEInrKNZ2GZCCuuy2lvWrdl5jhbpeqc5hRYKFOcw==
@ -8336,13 +8240,6 @@ snapdragon@^0.8.1:
source-map-resolve "^0.5.0"
use "^2.0.0"
sntp@2.x.x:
version "2.1.0"
resolved "https://registry.yarnpkg.com/sntp/-/sntp-2.1.0.tgz#2c6cec14fedc2222739caf9b5c3d85d1cc5a2cc8"
integrity sha512-FL1b58BDrqS3A11lJ0zEdnJ3UOKqVxawAkF3k7F0CVN7VQ34aZrV+G8BZ1WC9ZL7NyrwsW0oviwsWDgRuVYtJg==
dependencies:
hoek "4.x.x"
source-map-js@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/source-map-js/-/source-map-js-1.0.2.tgz#adbc361d9c62df380125e7f161f71c826f1e490c"
@ -8507,11 +8404,6 @@ static-extend@^0.1.1:
resolved "https://registry.yarnpkg.com/statuses/-/statuses-1.5.0.tgz#161c7dac177659fd9811f43771fa99381478628c"
integrity sha1-Fhx9rBd2Wf2YEfQ3cfqZOBR4Yow=
stealthy-require@^1.1.1:
version "1.1.1"
resolved "https://registry.yarnpkg.com/stealthy-require/-/stealthy-require-1.1.1.tgz#35b09875b4ff49f26a777e509b3090a3226bf24b"
integrity sha1-NbCYdbT/SfJqd35QmzCQoyJr8ks=
string-length@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/string-length/-/string-length-4.0.1.tgz#4a973bf31ef77c4edbceadd6af2611996985f8a1"
@ -8616,11 +8508,6 @@ string_decoder@~1.1.1:
dependencies:
safe-buffer "~5.1.0"
stringstream@~0.0.5:
version "0.0.5"
resolved "https://registry.yarnpkg.com/stringstream/-/stringstream-0.0.5.tgz#4e484cd4de5a0bbbee18e46307710a8a81621878"
integrity sha1-TkhM1N5aC7vuGORjB3EKioFiGHg=
strip-ansi@^3.0.0:
version "3.0.1"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-3.0.1.tgz#6a385fb8853d952d5ff05d0e8aaf94278dc63dcf"
@ -8933,7 +8820,16 @@ totalist@^1.0.0:
resolved "https://registry.yarnpkg.com/totalist/-/totalist-1.1.0.tgz#a4d65a3e546517701e3e5c37a47a70ac97fe56df"
integrity sha512-gduQwd1rOdDMGxFG1gEvhV88Oirdo2p+KjoYFU7k2g+i7n6AFFbDQ5kMPUsW0pNbfQsB/cwXvT1i4Bue0s9g5g==
tough-cookie@^2.3.3, tough-cookie@~2.5.0:
tough-cookie@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-4.0.0.tgz#d822234eeca882f991f0f908824ad2622ddbece4"
integrity sha512-tHdtEpQCMrc1YLrMaqXXcj6AxhYi/xgit6mZu1+EDWUn+qhUf8wMQoFIy9NXuq23zAwtcB0t/MjACGR18pcRbg==
dependencies:
psl "^1.1.33"
punycode "^2.1.1"
universalify "^0.1.2"
tough-cookie@~2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.5.0.tgz#cd9fb2a0aa1d5a12b473bd9fb96fa3dcff65ade2"
integrity sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==
@ -8941,22 +8837,6 @@ tough-cookie@^2.3.3, tough-cookie@~2.5.0:
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@^3.0.1:
version "3.0.1"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-3.0.1.tgz#9df4f57e739c26930a018184887f4adb7dca73b2"
integrity sha512-yQyJ0u4pZsv9D4clxO69OEjLWYw+jbgspjTue4lTQZLfV0c5l1VmK2y1JK8E9ahdpltPOaAThPcp5nKPUgSnsg==
dependencies:
ip-regex "^2.1.0"
psl "^1.1.28"
punycode "^2.1.1"
tough-cookie@~2.3.3:
version "2.3.3"
resolved "https://registry.yarnpkg.com/tough-cookie/-/tough-cookie-2.3.3.tgz#0b618a5565b6dea90bf3425d04d55edc475a7561"
integrity sha1-C2GKVWW23qkL80JdBNVe3EdadWE=
dependencies:
punycode "^1.4.1"
tr46@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-1.0.1.tgz#a8b13fd6bfd2489519674ccde55ba3693b706d09"
@ -8964,10 +8844,10 @@ tr46@^1.0.0:
dependencies:
punycode "^2.1.0"
tr46@^2.0.2:
version "2.0.2"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.0.2.tgz#03273586def1595ae08fedb38d7733cee91d2479"
integrity sha512-3n1qG+/5kg+jrbTzwAykB5yRYtQCTqOGKq5U5PE3b0a1/mzo6snDhjGS0zJVJunO0NrT3Dg1MLy5TjWP/UJppg==
tr46@^2.1.0:
version "2.1.0"
resolved "https://registry.yarnpkg.com/tr46/-/tr46-2.1.0.tgz#fa87aa81ca5d5941da8cbf1f9b749dc969a4e240"
integrity sha512-15Ih7phfcdP5YxqiB+iDtLoaTz4Nd35+IiAv0kQ5FNKHzXgdWqPoTIqEDDJmXceQt4JZk6lVPT8lnDlPpGDppw==
dependencies:
punycode "^2.1.1"
@ -9085,7 +8965,7 @@ type-check@^0.4.0, type-check@~0.4.0:
type-check@~0.3.2:
version "0.3.2"
resolved "https://registry.yarnpkg.com/type-check/-/type-check-0.3.2.tgz#5884cab512cf1d355e3fb784f30804b2b520db72"
integrity sha1-WITKtRLPHTVeP7eE8wgEsrUg23I=
integrity sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==
dependencies:
prelude-ls "~1.1.2"
@ -9191,7 +9071,7 @@ unique-string@^2.0.0:
dependencies:
crypto-random-string "^2.0.0"
universalify@^0.1.0:
universalify@^0.1.0, universalify@^0.1.2:
version "0.1.2"
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
integrity sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==
@ -9296,7 +9176,7 @@ uuid@^3.0.0:
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.4.0.tgz#b23e4358afa8a202fe7a100af1f5f883f02007ee"
integrity sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==
uuid@^3.1.0, uuid@^3.3.2:
uuid@^3.3.2:
version "3.3.2"
resolved "https://registry.yarnpkg.com/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131"
integrity sha512-yXJmeNaw3DnnKAOKJE51sL/ZaYfWJRl1pK9dr19YFCu0ObS231AB1/LbqTKRAQ5kw8A90rA6fr4riOUpTZvQZA==
@ -9527,13 +9407,13 @@ whatwg-url@6.4.0:
tr46 "^1.0.0"
webidl-conversions "^4.0.1"
whatwg-url@^8.0.0:
version "8.4.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.4.0.tgz#50fb9615b05469591d2b2bd6dfaed2942ed72837"
integrity sha512-vwTUFf6V4zhcPkWp/4CQPr1TW9Ml6SF4lVyaIMBdJw5i6qUUJ1QWM4Z6YYVkfka0OUIzVo/0aNtGVGk256IKWw==
whatwg-url@^8.0.0, whatwg-url@^8.5.0:
version "8.7.0"
resolved "https://registry.yarnpkg.com/whatwg-url/-/whatwg-url-8.7.0.tgz#656a78e510ff8f3937bc0bcbe9f5c0ac35941b77"
integrity sha512-gAojqb/m9Q8a5IV96E3fHJM70AzCkgt4uXYX2O7EmuyOnLrViCQlsEBmF9UQIu3/aeAIp2U17rtbpZWNntQqdg==
dependencies:
lodash.sortby "^4.7.0"
tr46 "^2.0.2"
lodash "^4.7.0"
tr46 "^2.1.0"
webidl-conversions "^6.1.0"
which-boxed-primitive@^1.0.2:
@ -9583,16 +9463,11 @@ window-location-origin@^0.1.0:
resolved "https://registry.yarnpkg.com/window-location-origin/-/window-location-origin-0.1.0.tgz#e0a0b3cbe8802c4966b358f859315355d3a15e04"
integrity sha1-4KCzy+iALElms1j4WTFTVdOhXgQ=
word-wrap@^1.2.3:
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
integrity sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==
wordwrap@~1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/wordwrap/-/wordwrap-1.0.0.tgz#27584810891456a4171c8d0226441ade90cbcaeb"
integrity sha1-J1hIEIkUVqQXHI0CJkQa3pDLyus=
worker-farm@^1.3.1:
version "1.5.1"
resolved "https://registry.yarnpkg.com/worker-farm/-/worker-farm-1.5.1.tgz#8e9f4a7da4f3c595aa600903051b969390423fa1"
@ -9632,15 +9507,10 @@ write@1.0.3:
dependencies:
mkdirp "^0.5.1"
ws@^7.2.3:
version "7.3.1"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.3.1.tgz#d0547bf67f7ce4f12a72dfe31262c68d7dc551c8"
integrity sha512-D3RuNkynyHmEJIpD2qrgVkc9DQ23OrN/moAwZX4L8DfvszsJxpjQuUq3LMx6HoYji9fbIOBY18XWBsAux1ZZUA==
ws@^7.3.1:
version "7.5.7"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.7.tgz#9e0ac77ee50af70d58326ecff7e85eb3fa375e67"
integrity sha512-KMvVuFzpKBuiIXW3E4u3mySRO2/mCHSyZDJQM5NQ9Q9KHWHWh0NHgfbRMLLrceUK5qAL4ytALJbpRMjixFZh8A==
ws@^7.3.1, ws@^7.4.6:
version "7.5.8"
resolved "https://registry.yarnpkg.com/ws/-/ws-7.5.8.tgz#ac2729881ab9e7cbaf8787fe3469a48c5c7f636a"
integrity sha512-ri1Id1WinAX5Jqn9HejiGb8crfRio0Qgu8+MtL36rlTA6RLsMdWt1Az/19A2Qij6uSHUMphEFaTKa4WG+UNHNw==
xdg-basedir@^4.0.0:
version "4.0.0"