DevTools error boundary: Search for pre-existing GH issues (#21279)
This commit is contained in:
parent
9eddfbf5af
commit
f3337aa544
|
@ -8,6 +8,7 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.11.2",
|
||||
"@octokit/rest": "^18.5.2",
|
||||
"@reach/menu-button": "^0.1.17",
|
||||
"@reach/tooltip": "^0.2.2",
|
||||
"clipboard-js": "^0.3.6",
|
||||
|
|
|
@ -14,3 +14,4 @@
|
|||
************************************************************************/
|
||||
|
||||
export const enableProfilerChangedHookIndices = false;
|
||||
export const isInternalFacebookBuild = false;
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
************************************************************************/
|
||||
|
||||
export const enableProfilerChangedHookIndices = true;
|
||||
export const isInternalFacebookBuild = true;
|
||||
|
||||
/************************************************************************
|
||||
* Do not edit the code below.
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
************************************************************************/
|
||||
|
||||
export const enableProfilerChangedHookIndices = false;
|
||||
export const isInternalFacebookBuild = false;
|
||||
|
||||
/************************************************************************
|
||||
* Do not edit the code below.
|
||||
|
|
|
@ -49,6 +49,9 @@ export const CHANGE_LOG_URL =
|
|||
export const UNSUPPORTED_VERSION_URL =
|
||||
'https://reactjs.org/blog/2019/08/15/new-react-devtools.html#how-do-i-get-the-old-version-back';
|
||||
|
||||
export const REACT_DEVTOOLS_WORKPLACE_URL =
|
||||
'https://fburl.com/react-devtools-workplace-group';
|
||||
|
||||
// HACK
|
||||
//
|
||||
// Extracting during build time avoids a temporarily invalid state for the inline target.
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
.ErrorBoundary {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--color-background);
|
||||
padding: 0.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Header {
|
||||
font-size: var(--font-size-sans-large);
|
||||
font-weight: bold;
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
.Stack {
|
||||
margin-top: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--font-size-monospace-normal);
|
||||
-webkit-font-smoothing: initial;
|
||||
background-color: var(--color-error-background);
|
||||
border: 1px solid var(--color-error-border);
|
||||
color: var(--color-error-text);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.IconAndLinkRow {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-top: 0.5rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
|
||||
.RetryIcon {
|
||||
margin-right: 0.25rem;
|
||||
color: var(--color-button-active);
|
||||
}
|
||||
|
||||
.RetryButton {
|
||||
margin-right: 0.25rem;
|
||||
color: var(--color-text);
|
||||
}
|
||||
.RetryButton:hover {
|
||||
color: var(--color-button-hover);
|
||||
}
|
||||
|
||||
.ReportIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.ReportLink {
|
||||
color: var(--color-link);
|
||||
}
|
|
@ -1,154 +0,0 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Component} from 'react';
|
||||
import Button from './Button';
|
||||
import ButtonIcon from './ButtonIcon';
|
||||
import Icon from './Icon';
|
||||
import styles from './ErrorBoundary.css';
|
||||
import Store from 'react-devtools-shared/src/devtools/store';
|
||||
|
||||
type Props = {|
|
||||
children: React$Node,
|
||||
onRetry?: (store: Store) => void,
|
||||
store: Store,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
callStack: string | null,
|
||||
componentStack: string | null,
|
||||
errorMessage: string | null,
|
||||
hasError: boolean,
|
||||
|};
|
||||
|
||||
const InitialState: State = {
|
||||
callStack: null,
|
||||
componentStack: null,
|
||||
errorMessage: null,
|
||||
hasError: false,
|
||||
};
|
||||
|
||||
export default class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = InitialState;
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
const errorMessage =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
error.hasOwnProperty('message')
|
||||
? error.message
|
||||
: error;
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, {componentStack}: any) {
|
||||
const callStack =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
error.hasOwnProperty('stack')
|
||||
? error.stack
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n')
|
||||
: null;
|
||||
|
||||
this.setState({
|
||||
callStack,
|
||||
componentStack,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
const {callStack, componentStack, errorMessage, hasError} = this.state;
|
||||
|
||||
let bugURL = process.env.GITHUB_URL;
|
||||
if (bugURL) {
|
||||
const title = `Error: "${errorMessage || ''}"`;
|
||||
const label = 'Component: Developer Tools';
|
||||
|
||||
let body = 'Describe what you were doing when the bug occurred:';
|
||||
body += '\n1. ';
|
||||
body += '\n2. ';
|
||||
body += '\n3. ';
|
||||
body += '\n\n---------------------------------------------';
|
||||
body += '\nPlease do not remove the text below this line';
|
||||
body += '\n---------------------------------------------';
|
||||
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
|
||||
if (callStack) {
|
||||
body += `\n\nCall stack: ${callStack.trim()}`;
|
||||
}
|
||||
if (componentStack) {
|
||||
body += `\n\nComponent stack: ${componentStack.trim()}`;
|
||||
}
|
||||
|
||||
bugURL += `/issues/new?labels=${encodeURI(label)}&title=${encodeURI(
|
||||
title,
|
||||
)}&body=${encodeURI(body)}`;
|
||||
}
|
||||
|
||||
if (hasError) {
|
||||
return (
|
||||
<div className={styles.ErrorBoundary}>
|
||||
<div className={styles.Header}>
|
||||
Uncaught Error: {errorMessage || ''}
|
||||
</div>
|
||||
<div className={styles.IconAndLinkRow}>
|
||||
<Button
|
||||
className={styles.RetryButton}
|
||||
title="Retry"
|
||||
onClick={this.handleRetry}>
|
||||
<ButtonIcon className={styles.RetryIcon} type="reload" />
|
||||
Retry
|
||||
</Button>
|
||||
{bugURL && (
|
||||
<>
|
||||
<Icon className={styles.ReportIcon} type="bug" />
|
||||
<a
|
||||
className={styles.ReportLink}
|
||||
href={bugURL}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Report bug">
|
||||
Report this issue
|
||||
</a>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
{!!callStack && (
|
||||
<div className={styles.Stack}>
|
||||
The error was thrown {callStack.trim()}
|
||||
</div>
|
||||
)}
|
||||
{!!componentStack && (
|
||||
<div className={styles.Stack}>
|
||||
The error occurred {componentStack.trim()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
|
||||
handleRetry = () => {
|
||||
const {onRetry, store} = this.props;
|
||||
if (typeof onRetry === 'function') {
|
||||
onRetry(store);
|
||||
}
|
||||
|
||||
this.setState(InitialState);
|
||||
};
|
||||
}
|
91
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
vendored
Normal file
91
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorBoundary.js
vendored
Normal file
|
@ -0,0 +1,91 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {Component, Suspense} from 'react';
|
||||
import ErrorView from './ErrorView';
|
||||
import SearchingGitHubIssues from './SearchingGitHubIssues';
|
||||
import SuspendingErrorView from './SuspendingErrorView';
|
||||
|
||||
type Props = {|
|
||||
children: React$Node,
|
||||
|};
|
||||
|
||||
type State = {|
|
||||
callStack: string | null,
|
||||
componentStack: string | null,
|
||||
errorMessage: string | null,
|
||||
hasError: boolean,
|
||||
|};
|
||||
|
||||
const InitialState: State = {
|
||||
callStack: null,
|
||||
componentStack: null,
|
||||
errorMessage: null,
|
||||
hasError: false,
|
||||
};
|
||||
|
||||
export default class ErrorBoundary extends Component<Props, State> {
|
||||
state: State = InitialState;
|
||||
|
||||
static getDerivedStateFromError(error: any) {
|
||||
const errorMessage =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
error.hasOwnProperty('message')
|
||||
? error.message
|
||||
: '' + error;
|
||||
|
||||
return {
|
||||
errorMessage,
|
||||
hasError: true,
|
||||
};
|
||||
}
|
||||
|
||||
componentDidCatch(error: any, {componentStack}: any) {
|
||||
const callStack =
|
||||
typeof error === 'object' &&
|
||||
error !== null &&
|
||||
error.hasOwnProperty('stack')
|
||||
? error.stack
|
||||
.split('\n')
|
||||
.slice(1)
|
||||
.join('\n')
|
||||
: null;
|
||||
|
||||
this.setState({
|
||||
callStack,
|
||||
componentStack,
|
||||
});
|
||||
}
|
||||
|
||||
render() {
|
||||
const {children} = this.props;
|
||||
const {callStack, componentStack, errorMessage, hasError} = this.state;
|
||||
|
||||
if (hasError) {
|
||||
return (
|
||||
<ErrorView
|
||||
callStack={callStack}
|
||||
componentStack={componentStack}
|
||||
errorMessage={errorMessage}>
|
||||
<Suspense fallback={<SearchingGitHubIssues />}>
|
||||
<SuspendingErrorView
|
||||
callStack={callStack}
|
||||
componentStack={componentStack}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
</Suspense>
|
||||
</ErrorView>
|
||||
);
|
||||
}
|
||||
|
||||
return children;
|
||||
}
|
||||
}
|
46
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorView.js
vendored
Normal file
46
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ErrorView.js
vendored
Normal file
|
@ -0,0 +1,46 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import styles from './shared.css';
|
||||
|
||||
type Props = {|
|
||||
callStack: string | null,
|
||||
children: React$Node,
|
||||
componentStack: string | null,
|
||||
errorMessage: string | null,
|
||||
|};
|
||||
|
||||
export default function ErrorView({
|
||||
callStack,
|
||||
children,
|
||||
componentStack,
|
||||
errorMessage,
|
||||
}: Props) {
|
||||
return (
|
||||
<div className={styles.ErrorBoundary}>
|
||||
{children}
|
||||
<div className={styles.ErrorInfo}>
|
||||
<div className={styles.Header}>
|
||||
Uncaught Error: {errorMessage || ''}
|
||||
</div>
|
||||
{!!callStack && (
|
||||
<div className={styles.Stack}>
|
||||
The error was thrown {callStack.trim()}
|
||||
</div>
|
||||
)}
|
||||
{!!componentStack && (
|
||||
<div className={styles.Stack}>
|
||||
The error occurred {componentStack.trim()}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
68
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ReportNewIssue.js
vendored
Normal file
68
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/ReportNewIssue.js
vendored
Normal file
|
@ -0,0 +1,68 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import Icon from '../Icon';
|
||||
import styles from './shared.css';
|
||||
|
||||
type Props = {|
|
||||
callStack: string | null,
|
||||
componentStack: string | null,
|
||||
errorMessage: string | null,
|
||||
|};
|
||||
|
||||
export default function ReportNewIssue({
|
||||
callStack,
|
||||
componentStack,
|
||||
errorMessage,
|
||||
}: Props) {
|
||||
let bugURL = process.env.GITHUB_URL;
|
||||
if (!bugURL) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const title = `Error: "${errorMessage || ''}"`;
|
||||
const label = 'Component: Developer Tools';
|
||||
|
||||
let body = 'Describe what you were doing when the bug occurred:';
|
||||
body += '\n1. ';
|
||||
body += '\n2. ';
|
||||
body += '\n3. ';
|
||||
body += '\n\n---------------------------------------------';
|
||||
body += '\nPlease do not remove the text below this line';
|
||||
body += '\n---------------------------------------------';
|
||||
body += `\n\nDevTools version: ${process.env.DEVTOOLS_VERSION || ''}`;
|
||||
if (callStack) {
|
||||
body += `\n\nCall stack: ${callStack.trim()}`;
|
||||
}
|
||||
if (componentStack) {
|
||||
body += `\n\nComponent stack: ${componentStack.trim()}`;
|
||||
}
|
||||
|
||||
bugURL += `/issues/new?labels=${encodeURI(label)}&title=${encodeURI(
|
||||
title,
|
||||
)}&body=${encodeURI(body)}`;
|
||||
|
||||
return (
|
||||
<div className={styles.GitHubLinkRow}>
|
||||
<Icon className={styles.ReportIcon} type="bug" />
|
||||
<a
|
||||
className={styles.ReportLink}
|
||||
href={bugURL}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Report bug">
|
||||
Report this issue
|
||||
</a>
|
||||
<div className={styles.ReproSteps}>
|
||||
(Please include steps on how to reproduce it and the components used.)
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
21
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SearchingGitHubIssues.js
vendored
Normal file
21
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SearchingGitHubIssues.js
vendored
Normal file
|
@ -0,0 +1,21 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import LoadingAnimation from 'react-devtools-shared/src/devtools/views/Components/LoadingAnimation';
|
||||
import styles from './shared.css';
|
||||
|
||||
export default function SearchingGitHubIssues() {
|
||||
return (
|
||||
<div className={styles.GitHubLinkRow}>
|
||||
<LoadingAnimation className={styles.LoadingIcon} />
|
||||
Searching GitHub for reports of this error...
|
||||
</div>
|
||||
);
|
||||
}
|
49
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SuspendingErrorView.js
vendored
Normal file
49
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/SuspendingErrorView.js
vendored
Normal file
|
@ -0,0 +1,49 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {findGitHubIssue} from './cache';
|
||||
import UpdateExistingIssue from './UpdateExistingIssue';
|
||||
import ReportNewIssue from './ReportNewIssue';
|
||||
import WorkplaceGroup from './WorkplaceGroup';
|
||||
|
||||
type Props = {|
|
||||
callStack: string | null,
|
||||
componentStack: string | null,
|
||||
errorMessage: string | null,
|
||||
|};
|
||||
|
||||
export default function SuspendingErrorView({
|
||||
callStack,
|
||||
componentStack,
|
||||
errorMessage,
|
||||
}: Props) {
|
||||
const maybeItem =
|
||||
errorMessage !== null ? findGitHubIssue(errorMessage) : null;
|
||||
|
||||
let GitHubUI;
|
||||
if (maybeItem != null) {
|
||||
GitHubUI = <UpdateExistingIssue gitHubIssue={maybeItem} />;
|
||||
} else {
|
||||
GitHubUI = (
|
||||
<ReportNewIssue
|
||||
callStack={callStack}
|
||||
componentStack={componentStack}
|
||||
errorMessage={errorMessage}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{GitHubUI}
|
||||
<WorkplaceGroup />
|
||||
</>
|
||||
);
|
||||
}
|
38
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UpdateExistingIssue.js
vendored
Normal file
38
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/UpdateExistingIssue.js
vendored
Normal file
|
@ -0,0 +1,38 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {GitHubIssue} from './githubAPI';
|
||||
|
||||
import * as React from 'react';
|
||||
import Icon from '../Icon';
|
||||
import styles from './shared.css';
|
||||
|
||||
export default function UpdateExistingIssue({
|
||||
gitHubIssue,
|
||||
}: {|
|
||||
gitHubIssue: GitHubIssue,
|
||||
|}) {
|
||||
const {title, url} = gitHubIssue;
|
||||
return (
|
||||
<div className={styles.GitHubLinkRow}>
|
||||
<Icon className={styles.ReportIcon} type="bug" />
|
||||
<div className={styles.UpdateExistingIssuePrompt}>
|
||||
Update existing issue:
|
||||
</div>
|
||||
<a
|
||||
className={styles.ReportLink}
|
||||
href={url}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Report bug">
|
||||
{title}
|
||||
</a>
|
||||
</div>
|
||||
);
|
||||
}
|
35
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/WorkplaceGroup.js
vendored
Normal file
35
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/WorkplaceGroup.js
vendored
Normal file
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import * as React from 'react';
|
||||
import {isInternalFacebookBuild} from 'react-devtools-feature-flags';
|
||||
import {REACT_DEVTOOLS_WORKPLACE_URL} from 'react-devtools-shared/src/constants';
|
||||
import Icon from '../Icon';
|
||||
import styles from './shared.css';
|
||||
|
||||
export default function WorkplaceGroup() {
|
||||
if (!isInternalFacebookBuild) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<div className={styles.WorkplaceGroupRow}>
|
||||
<Icon className={styles.ReportIcon} type="facebook" />
|
||||
<a
|
||||
className={styles.ReportLink}
|
||||
href={REACT_DEVTOOLS_WORKPLACE_URL}
|
||||
rel="noopener noreferrer"
|
||||
target="_blank"
|
||||
title="Report bug">
|
||||
Report this on Workplace
|
||||
</a>
|
||||
<div className={styles.FacebookOnly}>(Facebook employees only.)</div>
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -0,0 +1,134 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import type {Wakeable} from 'shared/ReactTypes';
|
||||
import type {GitHubIssue} from './githubAPI';
|
||||
|
||||
import {unstable_getCacheForType} from 'react';
|
||||
import {searchGitHubIssues} from './githubAPI';
|
||||
|
||||
const API_TIMEOUT = 3000;
|
||||
|
||||
const Pending = 0;
|
||||
const Resolved = 1;
|
||||
const Rejected = 2;
|
||||
|
||||
type PendingRecord = {|
|
||||
status: 0,
|
||||
value: Wakeable,
|
||||
|};
|
||||
|
||||
type ResolvedRecord<T> = {|
|
||||
status: 1,
|
||||
value: T,
|
||||
|};
|
||||
|
||||
type RejectedRecord = {|
|
||||
status: 2,
|
||||
value: null,
|
||||
|};
|
||||
|
||||
type Record<T> = PendingRecord | ResolvedRecord<T> | RejectedRecord;
|
||||
|
||||
function readRecord<T>(record: Record<T>): ResolvedRecord<T> | RejectedRecord {
|
||||
if (record.status === Resolved) {
|
||||
// This is just a type refinement.
|
||||
return record;
|
||||
} else if (record.status === Rejected) {
|
||||
// This is just a type refinement.
|
||||
return record;
|
||||
} else {
|
||||
throw record.value;
|
||||
}
|
||||
}
|
||||
|
||||
type GitHubIssueMap = Map<string, Record<GitHubIssue>>;
|
||||
|
||||
function createMap(): GitHubIssueMap {
|
||||
return new Map();
|
||||
}
|
||||
|
||||
function getRecordMap(): Map<string, Record<GitHubIssue>> {
|
||||
return unstable_getCacheForType(createMap);
|
||||
}
|
||||
|
||||
export function findGitHubIssue(errorMessage: string): GitHubIssue | null {
|
||||
errorMessage = normalizeErrorMessage(errorMessage);
|
||||
|
||||
const map = getRecordMap();
|
||||
let record = map.get(errorMessage);
|
||||
|
||||
if (!record) {
|
||||
const callbacks = new Set();
|
||||
const wakeable: Wakeable = {
|
||||
then(callback) {
|
||||
callbacks.add(callback);
|
||||
},
|
||||
};
|
||||
const wake = () => {
|
||||
// This assumes they won't throw.
|
||||
callbacks.forEach(callback => callback());
|
||||
callbacks.clear();
|
||||
};
|
||||
const newRecord: Record<GitHubIssue> = (record = {
|
||||
status: Pending,
|
||||
value: wakeable,
|
||||
});
|
||||
|
||||
let didTimeout = false;
|
||||
|
||||
searchGitHubIssues(errorMessage)
|
||||
.then(maybeItem => {
|
||||
if (didTimeout) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (maybeItem) {
|
||||
const resolvedRecord = ((newRecord: any): ResolvedRecord<GitHubIssue>);
|
||||
resolvedRecord.status = Resolved;
|
||||
resolvedRecord.value = maybeItem;
|
||||
} else {
|
||||
const notFoundRecord = ((newRecord: any): RejectedRecord);
|
||||
notFoundRecord.status = Rejected;
|
||||
notFoundRecord.value = null;
|
||||
}
|
||||
|
||||
wake();
|
||||
})
|
||||
.catch(error => {
|
||||
const thrownRecord = ((newRecord: any): RejectedRecord);
|
||||
thrownRecord.status = Rejected;
|
||||
thrownRecord.value = null;
|
||||
|
||||
wake();
|
||||
});
|
||||
|
||||
// Only wait a little while for GitHub results before showing a fallback.
|
||||
setTimeout(() => {
|
||||
didTimeout = true;
|
||||
|
||||
const timedoutRecord = ((newRecord: any): RejectedRecord);
|
||||
timedoutRecord.status = Rejected;
|
||||
timedoutRecord.value = null;
|
||||
|
||||
wake();
|
||||
}, API_TIMEOUT);
|
||||
|
||||
map.set(errorMessage, record);
|
||||
}
|
||||
|
||||
const response = readRecord(record).value;
|
||||
return response;
|
||||
}
|
||||
|
||||
function normalizeErrorMessage(errorMessage: string): string {
|
||||
// Remove Fiber IDs from error message (as those will be unique).
|
||||
errorMessage = errorMessage.replace(/"[0-9]+"/, '');
|
||||
return errorMessage;
|
||||
}
|
50
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/githubAPI.js
vendored
Normal file
50
packages/react-devtools-shared/src/devtools/views/ErrorBoundary/githubAPI.js
vendored
Normal file
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import {Octokit} from '@octokit/rest';
|
||||
|
||||
export type GitHubIssue = {|
|
||||
title: string,
|
||||
url: string,
|
||||
|};
|
||||
|
||||
export async function searchGitHubIssues(
|
||||
message: string,
|
||||
): Promise<GitHubIssue | null> {
|
||||
// Remove Fiber IDs from error message (as those will be unique).
|
||||
message = message.replace(/"[0-9]+"/, '');
|
||||
|
||||
const filters = [
|
||||
// Unfortunately "repo" and "org" filters don't work
|
||||
// Hopefully the label filter will be sufficient.
|
||||
'in:title',
|
||||
'is:issue',
|
||||
'is:open',
|
||||
'is:public',
|
||||
'label:"Component: Developer Tools"',
|
||||
];
|
||||
|
||||
const octokit = new Octokit();
|
||||
const {data} = await octokit.search.issuesAndPullRequests({
|
||||
q: message + ' ' + filters.join(' '),
|
||||
});
|
||||
|
||||
const maybeItem = data.items.find(item =>
|
||||
item.html_url.startsWith('https://github.com/facebook/react/'),
|
||||
);
|
||||
|
||||
if (maybeItem) {
|
||||
return {
|
||||
title: maybeItem.title,
|
||||
url: maybeItem.html_url,
|
||||
};
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
/**
|
||||
* Copyright (c) Facebook, Inc. and its affiliates.
|
||||
*
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @flow
|
||||
*/
|
||||
|
||||
import ErrorBoundary from './ErrorBoundary';
|
||||
|
||||
export default ErrorBoundary;
|
|
@ -0,0 +1,83 @@
|
|||
.GitHubLinkRow {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--color-console-warning-background);
|
||||
color: var(--color-text);
|
||||
border-bottom: 1px solid var(--color-console-warning-border);
|
||||
border-top: 1px solid var(--color-console-warning-border);
|
||||
}
|
||||
|
||||
.WorkplaceGroupRow {
|
||||
flex: 0 0 auto;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
overflow: auto;
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: var(--color-background-hover);
|
||||
border-bottom: 1px solid var(--color-border);
|
||||
}
|
||||
|
||||
.ErrorBoundary {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
background-color: var(--color-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.ErrorInfo {
|
||||
padding: 0.5rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.Header {
|
||||
font-size: var(--font-size-sans-large);
|
||||
font-weight: bold;
|
||||
color: var(--color-error-text);
|
||||
}
|
||||
|
||||
.Stack {
|
||||
margin-top: 0.5rem;
|
||||
white-space: pre-wrap;
|
||||
font-family: var(--font-family-monospace);
|
||||
font-size: var(--font-size-monospace-normal);
|
||||
-webkit-font-smoothing: initial;
|
||||
background-color: var(--color-error-background);
|
||||
border: 1px solid var(--color-error-border);
|
||||
color: var(--color-error-text);
|
||||
border-radius: 0.25rem;
|
||||
padding: 0.5rem;
|
||||
}
|
||||
|
||||
.LoadingIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.ReportIcon {
|
||||
margin-right: 0.25rem;
|
||||
}
|
||||
|
||||
.ReportLink {
|
||||
color: var(--color-link);
|
||||
}
|
||||
|
||||
.FacebookOnly {
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
.ReproSteps {
|
||||
margin-left: 0.25rem;
|
||||
color: var(--color-console-warning-text);
|
||||
}
|
||||
|
||||
.UpdateExistingIssuePrompt {
|
||||
margin-right: 0.25rem;
|
||||
color: var(--color-console-warning-text);
|
||||
}
|
|
@ -17,6 +17,7 @@ export type IconType =
|
|||
| 'components'
|
||||
| 'copy'
|
||||
| 'error'
|
||||
| 'facebook'
|
||||
| 'flame-chart'
|
||||
| 'interactions'
|
||||
| 'profiler'
|
||||
|
@ -52,6 +53,9 @@ export default function Icon({className = '', type}: Props) {
|
|||
case 'error':
|
||||
pathData = PATH_ERROR;
|
||||
break;
|
||||
case 'facebook':
|
||||
pathData = PATH_FACEBOOK;
|
||||
break;
|
||||
case 'flame-chart':
|
||||
pathData = PATH_FLAME_CHART;
|
||||
break;
|
||||
|
@ -117,6 +121,10 @@ const PATH_COPY = `
|
|||
|
||||
const PATH_ERROR = `M16.971 0h-9.942l-7.029 7.029v9.941l7.029 7.03h9.941l7.03-7.029v-9.942l-7.029-7.029zm-1.402 16.945l-3.554-3.521-3.518 3.568-1.418-1.418 3.507-3.566-3.586-3.472 1.418-1.417 3.581 3.458 3.539-3.583 1.431 1.431-3.535 3.568 3.566 3.522-1.431 1.43z`;
|
||||
|
||||
const PATH_FACEBOOK = `
|
||||
M22,12c0-5.52-4.48-10-10-10S2,6.48,2,12c0,4.84,3.44,8.87,8,9.8V15H8v-3h2V9.5C10,7.57,11.57,6,13.5,6H16v3h-2 c-0.55,0-1,0.45-1,1v2h3v3h-3v6.95C18.05,21.45,22,17.19,22,12z
|
||||
`;
|
||||
|
||||
const PATH_FLAME_CHART = `
|
||||
M10.0650893,21.5040462 C7.14020814,20.6850349 5,18.0558698 5,14.9390244 C5,14.017627
|
||||
5,9.81707317 7.83333333,7.37804878 C7.83333333,7.37804878 7.58333333,11.199187 10,
|
||||
|
|
123
yarn.lock
123
yarn.lock
|
@ -1814,6 +1814,26 @@
|
|||
read-package-json-fast "^1.1.1"
|
||||
readdir-scoped-modules "^1.1.0"
|
||||
|
||||
"@octokit/auth-token@^2.4.4":
|
||||
version "2.4.5"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
|
||||
integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
|
||||
dependencies:
|
||||
"@octokit/types" "^6.0.3"
|
||||
|
||||
"@octokit/core@^3.2.3":
|
||||
version "3.4.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.4.0.tgz#b48aa27d755b339fe7550548b340dcc2b513b742"
|
||||
integrity sha512-6/vlKPP8NF17cgYXqucdshWqmMZGXkuvtcrWCgU5NOI0Pl2GjlmZyWgBMrU8zJ3v2MJlM6++CiB45VKYmhiWWg==
|
||||
dependencies:
|
||||
"@octokit/auth-token" "^2.4.4"
|
||||
"@octokit/graphql" "^4.5.8"
|
||||
"@octokit/request" "^5.4.12"
|
||||
"@octokit/request-error" "^2.0.5"
|
||||
"@octokit/types" "^6.0.3"
|
||||
before-after-hook "^2.2.0"
|
||||
universal-user-agent "^6.0.0"
|
||||
|
||||
"@octokit/endpoint@^5.1.0":
|
||||
version "5.3.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-5.3.2.tgz#2deda2d869cac9ba7f370287d55667be2a808d4b"
|
||||
|
@ -1824,6 +1844,49 @@
|
|||
universal-user-agent "^3.0.0"
|
||||
url-template "^2.0.8"
|
||||
|
||||
"@octokit/endpoint@^6.0.1":
|
||||
version "6.0.11"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1"
|
||||
integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==
|
||||
dependencies:
|
||||
"@octokit/types" "^6.0.3"
|
||||
is-plain-object "^5.0.0"
|
||||
universal-user-agent "^6.0.0"
|
||||
|
||||
"@octokit/graphql@^4.5.8":
|
||||
version "4.6.1"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.1.tgz#f975486a46c94b7dbe58a0ca751935edc7e32cc9"
|
||||
integrity sha512-2lYlvf4YTDgZCTXTW4+OX+9WTLFtEUc6hGm4qM1nlZjzxj+arizM4aHWzBVBCxY9glh7GIs0WEuiSgbVzv8cmA==
|
||||
dependencies:
|
||||
"@octokit/request" "^5.3.0"
|
||||
"@octokit/types" "^6.0.3"
|
||||
universal-user-agent "^6.0.0"
|
||||
|
||||
"@octokit/openapi-types@^6.0.0":
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-6.0.0.tgz#7da8d7d5a72d3282c1a3ff9f951c8133a707480d"
|
||||
integrity sha512-CnDdK7ivHkBtJYzWzZm7gEkanA7gKH6a09Eguz7flHw//GacPJLmkHA3f3N++MJmlxD1Fl+mB7B32EEpSCwztQ==
|
||||
|
||||
"@octokit/plugin-paginate-rest@^2.6.2":
|
||||
version "2.13.3"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.13.3.tgz#f0f1792230805108762d87906fb02d573b9e070a"
|
||||
integrity sha512-46lptzM9lTeSmIBt/sVP/FLSTPGx6DCzAdSX3PfeJ3mTf4h9sGC26WpaQzMEq/Z44cOcmx8VsOhO+uEgE3cjYg==
|
||||
dependencies:
|
||||
"@octokit/types" "^6.11.0"
|
||||
|
||||
"@octokit/plugin-request-log@^1.0.2":
|
||||
version "1.0.3"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d"
|
||||
integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==
|
||||
|
||||
"@octokit/plugin-rest-endpoint-methods@5.0.0":
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-5.0.0.tgz#cf2cdeb24ea829c31688216a5b165010b61f9a98"
|
||||
integrity sha512-Jc7CLNUueIshXT+HWt6T+M0sySPjF32mSFQAK7UfAg8qGeRI6OM1GSBxDLwbXjkqy2NVdnqCedJcP1nC785JYg==
|
||||
dependencies:
|
||||
"@octokit/types" "^6.13.0"
|
||||
deprecation "^2.3.1"
|
||||
|
||||
"@octokit/request-error@^1.0.1", "@octokit/request-error@^1.0.2":
|
||||
version "1.0.4"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-1.0.4.tgz#15e1dc22123ba4a9a4391914d80ec1e5303a23be"
|
||||
|
@ -1832,6 +1895,15 @@
|
|||
deprecation "^2.0.0"
|
||||
once "^1.4.0"
|
||||
|
||||
"@octokit/request-error@^2.0.0", "@octokit/request-error@^2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143"
|
||||
integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==
|
||||
dependencies:
|
||||
"@octokit/types" "^6.0.3"
|
||||
deprecation "^2.0.0"
|
||||
once "^1.4.0"
|
||||
|
||||
"@octokit/request@^5.0.0":
|
||||
version "5.0.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.0.2.tgz#59a920451f24811c016ddc507adcc41aafb2dca5"
|
||||
|
@ -1845,6 +1917,18 @@
|
|||
once "^1.4.0"
|
||||
universal-user-agent "^3.0.0"
|
||||
|
||||
"@octokit/request@^5.3.0", "@octokit/request@^5.4.12":
|
||||
version "5.4.15"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.15.tgz#829da413dc7dd3aa5e2cdbb1c7d0ebe1f146a128"
|
||||
integrity sha512-6UnZfZzLwNhdLRreOtTkT9n57ZwulCve8q3IT/Z477vThu6snfdkBuhxnChpOKNGxcQ71ow561Qoa6uqLdPtag==
|
||||
dependencies:
|
||||
"@octokit/endpoint" "^6.0.1"
|
||||
"@octokit/request-error" "^2.0.0"
|
||||
"@octokit/types" "^6.7.1"
|
||||
is-plain-object "^5.0.0"
|
||||
node-fetch "^2.6.1"
|
||||
universal-user-agent "^6.0.0"
|
||||
|
||||
"@octokit/rest@^16.14.1":
|
||||
version "16.28.7"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-16.28.7.tgz#a2c2db5b318da84144beba82d19c1a9dbdb1a1fa"
|
||||
|
@ -1864,6 +1948,23 @@
|
|||
universal-user-agent "^3.0.0"
|
||||
url-template "^2.0.8"
|
||||
|
||||
"@octokit/rest@^18.5.2":
|
||||
version "18.5.2"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.5.2.tgz#0369e554b7076e3749005147be94c661c7a5a74b"
|
||||
integrity sha512-Kz03XYfKS0yYdi61BkL9/aJ0pP2A/WK5vF/syhu9/kY30J8He3P68hv9GRpn8bULFx2K0A9MEErn4v3QEdbZcw==
|
||||
dependencies:
|
||||
"@octokit/core" "^3.2.3"
|
||||
"@octokit/plugin-paginate-rest" "^2.6.2"
|
||||
"@octokit/plugin-request-log" "^1.0.2"
|
||||
"@octokit/plugin-rest-endpoint-methods" "5.0.0"
|
||||
|
||||
"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.13.0", "@octokit/types@^6.7.1":
|
||||
version "6.13.0"
|
||||
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.13.0.tgz#779e5b7566c8dde68f2f6273861dd2f0409480d0"
|
||||
integrity sha512-W2J9qlVIU11jMwKHUp5/rbVUeErqelCsO5vW5PKNb7wAXQVUz87Rc+imjlEvpvbH8yUb+KHmv8NEjVZdsdpyxA==
|
||||
dependencies:
|
||||
"@octokit/openapi-types" "^6.0.0"
|
||||
|
||||
"@pmmmwh/react-refresh-webpack-plugin@^0.4.1":
|
||||
version "0.4.1"
|
||||
resolved "https://registry.yarnpkg.com/@pmmmwh/react-refresh-webpack-plugin/-/react-refresh-webpack-plugin-0.4.1.tgz#a4db0ed8e58c2f8566161c9a8cdf1d095c9a891b"
|
||||
|
@ -3478,6 +3579,11 @@ before-after-hook@^2.0.0:
|
|||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.0.tgz#b6c03487f44e24200dd30ca5e6a1979c5d2fb635"
|
||||
integrity sha512-IWIbu7pMqyw3EAJHzzHbWa85b6oud/yfKYg5rqB5hNE8CeMi3nX+2C2sj0HswfblST86hpVEOAb9x34NZd6P7A==
|
||||
|
||||
before-after-hook@^2.2.0:
|
||||
version "2.2.1"
|
||||
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.2.1.tgz#73540563558687586b52ed217dad6a802ab1549c"
|
||||
integrity sha512-/6FKxSTWoJdbsLDF8tdIjaRiFXiE6UHsEHE3OPI/cwPURCVi1ukP0gmLn7XWEiFk5TcwQjjY5PWsU+j+tgXgmw==
|
||||
|
||||
big.js@^5.2.2:
|
||||
version "5.2.2"
|
||||
resolved "https://registry.yarnpkg.com/big.js/-/big.js-5.2.2.tgz#65f0af382f578bcdc742bd9c281e9cb2d7768328"
|
||||
|
@ -5140,7 +5246,7 @@ depd@^1.1.2, depd@~1.1.2:
|
|||
resolved "https://registry.yarnpkg.com/depd/-/depd-1.1.2.tgz#9bcd52e14c097763e749b274c4346ed2e560b5a9"
|
||||
integrity sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=
|
||||
|
||||
deprecation@^2.0.0:
|
||||
deprecation@^2.0.0, deprecation@^2.3.1:
|
||||
version "2.3.1"
|
||||
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
|
||||
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
|
||||
|
@ -8128,6 +8234,11 @@ is-plain-object@^3.0.0:
|
|||
dependencies:
|
||||
isobject "^4.0.0"
|
||||
|
||||
is-plain-object@^5.0.0:
|
||||
version "5.0.0"
|
||||
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
|
||||
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
|
||||
|
||||
is-posix-bracket@^0.1.0:
|
||||
version "0.1.1"
|
||||
resolved "https://registry.yarnpkg.com/is-posix-bracket/-/is-posix-bracket-0.1.1.tgz#3334dc79774368e92f016e6fbc0a88f5cd6e6bc4"
|
||||
|
@ -10042,6 +10153,11 @@ node-fetch@^2.3.0, node-fetch@^2.6.0:
|
|||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.0.tgz#e633456386d4aa55863f676a7ab0daa8fdecb0fd"
|
||||
integrity sha512-8dG4H5ujfvFiqDmVu9fQ5bOHUC15JMjMY/Zumv26oOvvVJjM67KF8koCWIabKQ1GJIa9r2mMZscBq/TbdOcmNA==
|
||||
|
||||
node-fetch@^2.6.1:
|
||||
version "2.6.1"
|
||||
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
|
||||
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
|
||||
|
||||
node-forge@0.9.0:
|
||||
version "0.9.0"
|
||||
resolved "https://registry.yarnpkg.com/node-forge/-/node-forge-0.9.0.tgz#d624050edbb44874adca12bb9a52ec63cb782579"
|
||||
|
@ -13801,6 +13917,11 @@ universal-user-agent@^3.0.0:
|
|||
dependencies:
|
||||
os-name "^3.0.0"
|
||||
|
||||
universal-user-agent@^6.0.0:
|
||||
version "6.0.0"
|
||||
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
|
||||
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
|
||||
|
||||
universalify@^0.1.0:
|
||||
version "0.1.2"
|
||||
resolved "https://registry.yarnpkg.com/universalify/-/universalify-0.1.2.tgz#b646f69be3942dabcecc9d6639c80dc105efaa66"
|
||||
|
|
Loading…
Reference in New Issue