chore: fixing dependencies and code improvements
Signed-off-by: benjdlambert <ben@blam.sh>
This commit is contained in:
@@ -47,9 +47,7 @@
|
||||
"test": "backstage-cli package test"
|
||||
},
|
||||
"dependencies": {
|
||||
"@backstage/core-compat-api": "workspace:^",
|
||||
"@backstage/core-components": "workspace:^",
|
||||
"@backstage/core-plugin-api": "workspace:^",
|
||||
"@backstage/errors": "workspace:^",
|
||||
"@backstage/frontend-plugin-api": "workspace:^",
|
||||
"@backstage/theme": "workspace:^",
|
||||
@@ -60,12 +58,11 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@backstage/cli": "workspace:^",
|
||||
"@backstage/core-app-api": "workspace:^",
|
||||
"@backstage/dev-utils": "workspace:^",
|
||||
"@backstage/frontend-defaults": "workspace:^",
|
||||
"@backstage/test-utils": "workspace:^",
|
||||
"@testing-library/jest-dom": "^6.0.0",
|
||||
"@testing-library/react": "^14.0.0",
|
||||
"@testing-library/react": "^16.0.0",
|
||||
"@testing-library/user-event": "^14.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"msw": "^1.0.0",
|
||||
|
||||
@@ -13,8 +13,8 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { useCallback, useEffect, useState } from 'react';
|
||||
import { useParams } from 'react-router-dom';
|
||||
|
||||
import {
|
||||
Box,
|
||||
Button,
|
||||
@@ -38,13 +38,7 @@ import {
|
||||
EmptyState,
|
||||
ResponseErrorPanel,
|
||||
} from '@backstage/core-components';
|
||||
import {
|
||||
alertApiRef,
|
||||
useApi,
|
||||
fetchApiRef,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/core-plugin-api';
|
||||
import { isError } from '@backstage/errors';
|
||||
import { useConsentSession } from './useConsentSession';
|
||||
|
||||
const useStyles = makeStyles(theme => ({
|
||||
authCard: {
|
||||
@@ -89,260 +83,164 @@ const useStyles = makeStyles(theme => ({
|
||||
},
|
||||
}));
|
||||
|
||||
interface Session {
|
||||
id: string;
|
||||
clientName?: string;
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
scopes?: string[];
|
||||
responseType?: string;
|
||||
state?: string;
|
||||
nonce?: string;
|
||||
codeChallenge?: string;
|
||||
codeChallengeMethod?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
const ConsentPageLayout = ({
|
||||
title,
|
||||
children,
|
||||
}: {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
}) => (
|
||||
<Page themeId="tool">
|
||||
<Header title={title} />
|
||||
<Content>{children}</Content>
|
||||
</Page>
|
||||
);
|
||||
|
||||
export const ConsentPage = () => {
|
||||
const classes = useStyles();
|
||||
const { sessionId } = useParams<{ sessionId: string }>();
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
const [session, setSession] = useState<Session | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [submitting, setSubmitting] = useState(false);
|
||||
const [error, setError] = useState<string | null>(null);
|
||||
const [completed, setCompleted] = useState<
|
||||
| {
|
||||
action: 'approve' | 'reject';
|
||||
}
|
||||
| undefined
|
||||
>(undefined);
|
||||
|
||||
useEffect(() => {
|
||||
const fetchSession = async () => {
|
||||
if (!sessionId) return;
|
||||
|
||||
try {
|
||||
const baseUrl = await discoveryApi.getBaseUrl('auth');
|
||||
const response = await fetchApi.fetch(
|
||||
`${baseUrl}/v1/sessions/${sessionId}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
setSession(data);
|
||||
} catch (err) {
|
||||
setError(isError(err) ? err.message : 'Failed to load consent request');
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchSession();
|
||||
}, [sessionId, discoveryApi, fetchApi]);
|
||||
|
||||
const handleAction = useCallback(
|
||||
async (action: 'approve' | 'reject') => {
|
||||
if (!session) return;
|
||||
|
||||
setSubmitting(true);
|
||||
try {
|
||||
const baseUrl = await discoveryApi.getBaseUrl('auth');
|
||||
const response = await fetchApi.fetch(
|
||||
`${baseUrl}/v1/sessions/${session.id}/${action}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
setCompleted({
|
||||
action,
|
||||
});
|
||||
|
||||
if (result.redirectUrl) {
|
||||
window.location.href = result.redirectUrl;
|
||||
}
|
||||
} catch (err) {
|
||||
alertApi.post({
|
||||
message: isError(err) ? err.message : `Failed to ${action} consent`,
|
||||
severity: 'error',
|
||||
});
|
||||
} finally {
|
||||
setSubmitting(false);
|
||||
}
|
||||
},
|
||||
[session, discoveryApi, fetchApi, alertApi],
|
||||
);
|
||||
const { state, handleAction } = useConsentSession({ sessionId });
|
||||
|
||||
if (!sessionId) {
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title="Authorization Error" />
|
||||
<Content>
|
||||
<EmptyState
|
||||
missing="data"
|
||||
title="Invalid Request"
|
||||
description="The consent request ID is missing or invalid."
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
<ConsentPageLayout title="Authorization Error">
|
||||
<EmptyState
|
||||
missing="data"
|
||||
title="Invalid Request"
|
||||
description="The consent request ID is missing or invalid."
|
||||
/>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (loading) {
|
||||
if (state.status === 'loading') {
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title="Authorization Request" />
|
||||
<Content>
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight={300}
|
||||
>
|
||||
<Progress />
|
||||
</Box>
|
||||
</Content>
|
||||
</Page>
|
||||
<ConsentPageLayout title="Authorization Request">
|
||||
<Box
|
||||
display="flex"
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
minHeight={300}
|
||||
>
|
||||
<Progress />
|
||||
</Box>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (error ?? !session) {
|
||||
if (state.status === 'error') {
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title="Authorization Error" />
|
||||
<Content>
|
||||
<ResponseErrorPanel
|
||||
error={new Error(error || 'Authorization request not found')}
|
||||
/>
|
||||
</Content>
|
||||
</Page>
|
||||
<ConsentPageLayout title="Authorization Error">
|
||||
<ResponseErrorPanel error={new Error(state.error)} />
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
if (completed) {
|
||||
if (state.status === 'completed') {
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title="Authorization Complete" />
|
||||
<Content>
|
||||
<Card className={classes.authCard}>
|
||||
<CardContent>
|
||||
<Box textAlign="center">
|
||||
{completed.action === 'approve' ? (
|
||||
<CheckCircleIcon
|
||||
style={{ fontSize: 64, color: 'green', marginBottom: 16 }}
|
||||
/>
|
||||
) : (
|
||||
<CancelIcon
|
||||
style={{ fontSize: 64, color: 'red', marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{completed.action === 'approve'
|
||||
? 'Authorization Approved'
|
||||
: 'Authorization Denied'}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" gutterBottom>
|
||||
{completed.action === 'approve'
|
||||
? 'You have successfully authorized the application to access your Backstage account.'
|
||||
: 'You have denied the application access to your Backstage account.'}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Redirecting to the application...
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Card>
|
||||
</Content>
|
||||
</Page>
|
||||
);
|
||||
}
|
||||
|
||||
const appName = session.clientName ?? session.clientId;
|
||||
|
||||
return (
|
||||
<Page themeId="tool">
|
||||
<Header title="Authorization Request" />
|
||||
<Content>
|
||||
<ConsentPageLayout title="Authorization Complete">
|
||||
<Card className={classes.authCard}>
|
||||
<CardContent>
|
||||
<Box className={classes.appHeader}>
|
||||
<AppsIcon className={classes.appIcon} />
|
||||
<Box>
|
||||
<Typography className={classes.appName}>{appName}</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
wants to access your Backstage account
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Alert
|
||||
severity="warning"
|
||||
icon={<WarningIcon />}
|
||||
className={classes.securityWarning}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
<strong>Security Notice:</strong> By authorizing this
|
||||
application, you are granting it access to your Backstage
|
||||
account. The application will receive an access token that
|
||||
allows it to act on your behalf.
|
||||
<Box textAlign="center">
|
||||
{state.action === 'approve' ? (
|
||||
<CheckCircleIcon
|
||||
style={{ fontSize: 64, color: 'green', marginBottom: 16 }}
|
||||
/>
|
||||
) : (
|
||||
<CancelIcon
|
||||
style={{ fontSize: 64, color: 'red', marginBottom: 16 }}
|
||||
/>
|
||||
)}
|
||||
<Typography variant="h5" gutterBottom>
|
||||
{state.action === 'approve'
|
||||
? 'Authorization Approved'
|
||||
: 'Authorization Denied'}
|
||||
</Typography>
|
||||
<Typography variant="body1" color="textSecondary" gutterBottom>
|
||||
{state.action === 'approve'
|
||||
? 'You have successfully authorized the application to access your Backstage account.'
|
||||
: 'You have denied the application access to your Backstage account.'}
|
||||
</Typography>
|
||||
<Box mt={1}>
|
||||
<Typography variant="body2">
|
||||
<strong>Callback URL:</strong>
|
||||
</Typography>
|
||||
<Box className={classes.callbackUrl}>{session.redirectUri}</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Make sure you trust this application and recognize the callback
|
||||
URL above. Only authorize applications you trust.
|
||||
Redirecting to the application...
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
|
||||
<CardActions className={classes.buttonContainer}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
disabled={submitting}
|
||||
onClick={() => handleAction('reject')}
|
||||
startIcon={<CancelIcon />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={submitting}
|
||||
onClick={() => handleAction('approve')}
|
||||
startIcon={<CheckCircleIcon />}
|
||||
>
|
||||
{submitting ? 'Authorizing...' : 'Authorize'}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</Content>
|
||||
</Page>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
}
|
||||
|
||||
const session = state.session;
|
||||
const isSubmitting = state.status === 'submitting';
|
||||
const appName = session.clientName ?? session.clientId;
|
||||
|
||||
return (
|
||||
<ConsentPageLayout title="Authorization Request">
|
||||
<Card className={classes.authCard}>
|
||||
<CardContent>
|
||||
<Box className={classes.appHeader}>
|
||||
<AppsIcon className={classes.appIcon} />
|
||||
<Box>
|
||||
<Typography className={classes.appName}>{appName}</Typography>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
wants to access your Backstage account
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
<Divider />
|
||||
|
||||
<Alert
|
||||
severity="warning"
|
||||
icon={<WarningIcon />}
|
||||
className={classes.securityWarning}
|
||||
>
|
||||
<Typography variant="body2">
|
||||
<strong>Security Notice:</strong> By authorizing this application,
|
||||
you are granting it access to your Backstage account. The
|
||||
application will receive an access token that allows it to act on
|
||||
your behalf.
|
||||
</Typography>
|
||||
<Box mt={1}>
|
||||
<Typography variant="body2">
|
||||
<strong>Callback URL:</strong>
|
||||
</Typography>
|
||||
<Box className={classes.callbackUrl}>{session.redirectUri}</Box>
|
||||
</Box>
|
||||
</Alert>
|
||||
|
||||
<Box mt={2}>
|
||||
<Typography variant="body2" color="textSecondary">
|
||||
Make sure you trust this application and recognize the callback
|
||||
URL above. Only authorize applications you trust.
|
||||
</Typography>
|
||||
</Box>
|
||||
</CardContent>
|
||||
|
||||
<CardActions className={classes.buttonContainer}>
|
||||
<Button
|
||||
variant="outlined"
|
||||
color="secondary"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => handleAction('reject')}
|
||||
startIcon={<CancelIcon />}
|
||||
>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="primary"
|
||||
size="large"
|
||||
disabled={isSubmitting}
|
||||
onClick={() => handleAction('approve')}
|
||||
startIcon={<CheckCircleIcon />}
|
||||
>
|
||||
{isSubmitting ? 'Authorizing...' : 'Authorize'}
|
||||
</Button>
|
||||
</CardActions>
|
||||
</Card>
|
||||
</ConsentPageLayout>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
/*
|
||||
* Copyright 2025 The Backstage Authors
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import {
|
||||
useApi,
|
||||
alertApiRef,
|
||||
fetchApiRef,
|
||||
discoveryApiRef,
|
||||
} from '@backstage/frontend-plugin-api';
|
||||
import { useCallback } from 'react';
|
||||
import useAsync from 'react-use/esm/useAsync';
|
||||
import useAsyncFn from 'react-use/esm/useAsyncFn';
|
||||
import { isError } from '@backstage/errors';
|
||||
|
||||
interface Session {
|
||||
id: string;
|
||||
clientName?: string;
|
||||
clientId: string;
|
||||
redirectUri: string;
|
||||
scopes?: string[];
|
||||
responseType?: string;
|
||||
state?: string;
|
||||
nonce?: string;
|
||||
codeChallenge?: string;
|
||||
codeChallengeMethod?: string;
|
||||
expiresAt?: string;
|
||||
}
|
||||
|
||||
type ConsentState =
|
||||
| { status: 'loading' }
|
||||
| { status: 'error'; error: string }
|
||||
| { status: 'loaded'; session: Session }
|
||||
| { status: 'submitting'; session: Session; action: 'approve' | 'reject' }
|
||||
| { status: 'completed'; action: 'approve' | 'reject' };
|
||||
|
||||
export const useConsentSession = (opts: { sessionId?: string }) => {
|
||||
const alertApi = useApi(alertApiRef);
|
||||
const fetchApi = useApi(fetchApiRef);
|
||||
const discoveryApi = useApi(discoveryApiRef);
|
||||
const { sessionId } = opts;
|
||||
|
||||
const sessionState = useAsync(async () => {
|
||||
if (!sessionId) {
|
||||
throw new Error('Session ID is missing');
|
||||
}
|
||||
|
||||
const baseUrl = await discoveryApi.getBaseUrl('auth');
|
||||
const response = await fetchApi.fetch(
|
||||
`${baseUrl}/v1/sessions/${sessionId}`,
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
return (await response.json()) as Session;
|
||||
}, [sessionId]);
|
||||
|
||||
const [actionState, handleActionInternal] = useAsyncFn(
|
||||
async (action: 'approve' | 'reject', session: Session) => {
|
||||
const baseUrl = await discoveryApi.getBaseUrl('auth');
|
||||
const response = await fetchApi.fetch(
|
||||
`${baseUrl}/v1/sessions/${session.id}/${action}`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
},
|
||||
);
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.redirectUrl) {
|
||||
window.location.href = result.redirectUrl;
|
||||
}
|
||||
|
||||
return { action, redirectUrl: result.redirectUrl };
|
||||
},
|
||||
[discoveryApi, fetchApi],
|
||||
);
|
||||
|
||||
const getConsentState = (): ConsentState => {
|
||||
if (actionState.value) {
|
||||
return { status: 'completed', action: actionState.value.action };
|
||||
}
|
||||
if (actionState.loading && sessionState.value) {
|
||||
return {
|
||||
status: 'submitting',
|
||||
session: sessionState.value,
|
||||
action: 'approve', // This will be set properly when called
|
||||
};
|
||||
}
|
||||
if (sessionState.error) {
|
||||
return {
|
||||
status: 'error',
|
||||
error: isError(sessionState.error)
|
||||
? sessionState.error.message
|
||||
: 'Failed to load consent request',
|
||||
};
|
||||
}
|
||||
if (sessionState.value) {
|
||||
return { status: 'loaded', session: sessionState.value };
|
||||
}
|
||||
return { status: 'loading' };
|
||||
};
|
||||
|
||||
const state = getConsentState();
|
||||
return {
|
||||
state,
|
||||
handleAction: useCallback(
|
||||
async (action: 'approve' | 'reject') => {
|
||||
if (state.status !== 'loaded') return;
|
||||
|
||||
try {
|
||||
await handleActionInternal(action, state.session);
|
||||
} catch (err) {
|
||||
alertApi.post({
|
||||
message: isError(err) ? err.message : `Failed to ${action} consent`,
|
||||
severity: 'error',
|
||||
});
|
||||
}
|
||||
},
|
||||
[state, handleActionInternal, alertApi],
|
||||
),
|
||||
};
|
||||
};
|
||||
@@ -13,7 +13,6 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
import { compatWrapper } from '@backstage/core-compat-api';
|
||||
import {
|
||||
createFrontendPlugin,
|
||||
PageBlueprint,
|
||||
@@ -24,8 +23,7 @@ export const AuthPage = PageBlueprint.make({
|
||||
params: {
|
||||
path: '/oauth2',
|
||||
routeRef: rootRouteRef,
|
||||
loader: () =>
|
||||
import('./components/Router').then(m => compatWrapper(<m.Router />)),
|
||||
loader: () => import('./components/Router').then(m => <m.Router />),
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -4269,10 +4269,7 @@ __metadata:
|
||||
resolution: "@backstage/plugin-auth@workspace:plugins/auth"
|
||||
dependencies:
|
||||
"@backstage/cli": "workspace:^"
|
||||
"@backstage/core-app-api": "workspace:^"
|
||||
"@backstage/core-compat-api": "workspace:^"
|
||||
"@backstage/core-components": "workspace:^"
|
||||
"@backstage/core-plugin-api": "workspace:^"
|
||||
"@backstage/dev-utils": "workspace:^"
|
||||
"@backstage/errors": "workspace:^"
|
||||
"@backstage/frontend-defaults": "workspace:^"
|
||||
@@ -4283,18 +4280,22 @@ __metadata:
|
||||
"@material-ui/icons": "npm:^4.9.1"
|
||||
"@material-ui/lab": "npm:4.0.0-alpha.61"
|
||||
"@testing-library/jest-dom": "npm:^6.0.0"
|
||||
"@testing-library/react": "npm:^14.0.0"
|
||||
"@testing-library/react": "npm:^16.0.0"
|
||||
"@testing-library/user-event": "npm:^14.0.0"
|
||||
"@types/react": "npm:^18.0.0"
|
||||
msw: "npm:^1.0.0"
|
||||
react: "npm:^18.0.2"
|
||||
react-dom: "npm:^18.0.2"
|
||||
react-router-dom: "npm:^6.3.0"
|
||||
react-use: "npm:^17.2.4"
|
||||
peerDependencies:
|
||||
"@types/react": ^17.0.0 || ^18.0.0
|
||||
react: ^17.0.0 || ^18.0.0
|
||||
react-dom: ^17.0.0 || ^18.0.0
|
||||
react-router-dom: ^6.3.0
|
||||
peerDependenciesMeta:
|
||||
"@types/react":
|
||||
optional: true
|
||||
languageName: unknown
|
||||
linkType: soft
|
||||
|
||||
@@ -19521,22 +19522,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/dom@npm:^9.0.0":
|
||||
version: 9.3.4
|
||||
resolution: "@testing-library/dom@npm:9.3.4"
|
||||
dependencies:
|
||||
"@babel/code-frame": "npm:^7.10.4"
|
||||
"@babel/runtime": "npm:^7.12.5"
|
||||
"@types/aria-query": "npm:^5.0.1"
|
||||
aria-query: "npm:5.1.3"
|
||||
chalk: "npm:^4.1.0"
|
||||
dom-accessibility-api: "npm:^0.5.9"
|
||||
lz-string: "npm:^1.5.0"
|
||||
pretty-format: "npm:^27.0.2"
|
||||
checksum: 10/510da752ea76f4a10a0a4e3a77917b0302cf03effe576cd3534cab7e796533ee2b0e9fb6fb11b911a1ebd7c70a0bb6f235bf4f816c9b82b95b8fe0cddfd10975
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/jest-dom@npm:6.5.0":
|
||||
version: 6.5.0
|
||||
resolution: "@testing-library/jest-dom@npm:6.5.0"
|
||||
@@ -19589,20 +19574,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react@npm:^14.0.0":
|
||||
version: 14.3.1
|
||||
resolution: "@testing-library/react@npm:14.3.1"
|
||||
dependencies:
|
||||
"@babel/runtime": "npm:^7.12.5"
|
||||
"@testing-library/dom": "npm:^9.0.0"
|
||||
"@types/react-dom": "npm:^18.0.0"
|
||||
peerDependencies:
|
||||
react: ^18.0.0
|
||||
react-dom: ^18.0.0
|
||||
checksum: 10/83359dcdf9eaf067839f34604e1a181cbc14fc09f3a07672403700fcc6a900c4b8054ad1114fc24b4b9f89d84e2a09e1b7c9afce2306b1d4b4c9e30eb1cb12de
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"@testing-library/react@npm:^16.0.0":
|
||||
version: 16.3.0
|
||||
resolution: "@testing-library/react@npm:16.3.0"
|
||||
@@ -24005,15 +23976,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aria-query@npm:5.1.3":
|
||||
version: 5.1.3
|
||||
resolution: "aria-query@npm:5.1.3"
|
||||
dependencies:
|
||||
deep-equal: "npm:^2.0.5"
|
||||
checksum: 10/e5da608a7c4954bfece2d879342b6c218b6b207e2d9e5af270b5e38ef8418f02d122afdc948b68e32649b849a38377785252059090d66fa8081da95d1609c0d2
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"aria-query@npm:5.3.0":
|
||||
version: 5.3.0
|
||||
resolution: "aria-query@npm:5.3.0"
|
||||
@@ -24030,7 +23992,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"array-buffer-byte-length@npm:^1.0.0, array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
|
||||
"array-buffer-byte-length@npm:^1.0.1, array-buffer-byte-length@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "array-buffer-byte-length@npm:1.0.2"
|
||||
dependencies:
|
||||
@@ -27790,32 +27752,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-equal@npm:^2.0.5":
|
||||
version: 2.2.3
|
||||
resolution: "deep-equal@npm:2.2.3"
|
||||
dependencies:
|
||||
array-buffer-byte-length: "npm:^1.0.0"
|
||||
call-bind: "npm:^1.0.5"
|
||||
es-get-iterator: "npm:^1.1.3"
|
||||
get-intrinsic: "npm:^1.2.2"
|
||||
is-arguments: "npm:^1.1.1"
|
||||
is-array-buffer: "npm:^3.0.2"
|
||||
is-date-object: "npm:^1.0.5"
|
||||
is-regex: "npm:^1.1.4"
|
||||
is-shared-array-buffer: "npm:^1.0.2"
|
||||
isarray: "npm:^2.0.5"
|
||||
object-is: "npm:^1.1.5"
|
||||
object-keys: "npm:^1.1.1"
|
||||
object.assign: "npm:^4.1.4"
|
||||
regexp.prototype.flags: "npm:^1.5.1"
|
||||
side-channel: "npm:^1.0.4"
|
||||
which-boxed-primitive: "npm:^1.0.2"
|
||||
which-collection: "npm:^1.0.1"
|
||||
which-typed-array: "npm:^1.1.13"
|
||||
checksum: 10/1ce49d0b71d0f14d8ef991a742665eccd488dfc9b3cada069d4d7a86291e591c92d2589c832811dea182b4015736b210acaaebce6184be356c1060d176f5a05f
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"deep-equal@npm:~1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "deep-equal@npm:1.0.1"
|
||||
@@ -29024,7 +28960,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"es-get-iterator@npm:^1.0.2, es-get-iterator@npm:^1.1.3":
|
||||
"es-get-iterator@npm:^1.0.2":
|
||||
version: 1.1.3
|
||||
resolution: "es-get-iterator@npm:1.1.3"
|
||||
dependencies:
|
||||
@@ -31621,7 +31557,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.2, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
|
||||
"get-intrinsic@npm:^1.1.3, get-intrinsic@npm:^1.2.1, get-intrinsic@npm:^1.2.4, get-intrinsic@npm:^1.2.5, get-intrinsic@npm:^1.2.6, get-intrinsic@npm:^1.3.0":
|
||||
version: 1.3.0
|
||||
resolution: "get-intrinsic@npm:1.3.0"
|
||||
dependencies:
|
||||
@@ -33554,7 +33490,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-array-buffer@npm:^3.0.2, is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
|
||||
"is-array-buffer@npm:^3.0.4, is-array-buffer@npm:^3.0.5":
|
||||
version: 3.0.5
|
||||
resolution: "is-array-buffer@npm:3.0.5"
|
||||
dependencies:
|
||||
@@ -34027,7 +33963,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-regex@npm:^1.1.4, is-regex@npm:^1.2.1":
|
||||
"is-regex@npm:^1.2.1":
|
||||
version: 1.2.1
|
||||
resolution: "is-regex@npm:1.2.1"
|
||||
dependencies:
|
||||
@@ -34076,7 +34012,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-shared-array-buffer@npm:^1.0.2, is-shared-array-buffer@npm:^1.0.4":
|
||||
"is-shared-array-buffer@npm:^1.0.4":
|
||||
version: 1.0.4
|
||||
resolution: "is-shared-array-buffer@npm:1.0.4"
|
||||
dependencies:
|
||||
@@ -43815,7 +43751,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"regexp.prototype.flags@npm:^1.5.1, regexp.prototype.flags@npm:^1.5.3":
|
||||
"regexp.prototype.flags@npm:^1.5.3":
|
||||
version: 1.5.4
|
||||
resolution: "regexp.prototype.flags@npm:1.5.4"
|
||||
dependencies:
|
||||
@@ -45319,7 +45255,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"side-channel@npm:^1.0.4, side-channel@npm:^1.0.6, side-channel@npm:^1.1.0":
|
||||
"side-channel@npm:^1.0.6, side-channel@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "side-channel@npm:1.1.0"
|
||||
dependencies:
|
||||
@@ -49486,7 +49422,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-boxed-primitive@npm:^1.0.2, which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
|
||||
"which-boxed-primitive@npm:^1.1.0, which-boxed-primitive@npm:^1.1.1":
|
||||
version: 1.1.1
|
||||
resolution: "which-boxed-primitive@npm:1.1.1"
|
||||
dependencies:
|
||||
@@ -49520,7 +49456,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-collection@npm:^1.0.1, which-collection@npm:^1.0.2":
|
||||
"which-collection@npm:^1.0.2":
|
||||
version: 1.0.2
|
||||
resolution: "which-collection@npm:1.0.2"
|
||||
dependencies:
|
||||
@@ -49542,7 +49478,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"which-typed-array@npm:^1.1.13, which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2":
|
||||
"which-typed-array@npm:^1.1.16, which-typed-array@npm:^1.1.18, which-typed-array@npm:^1.1.2":
|
||||
version: 1.1.19
|
||||
resolution: "which-typed-array@npm:1.1.19"
|
||||
dependencies:
|
||||
|
||||
Reference in New Issue
Block a user