Merge branch 'backstage:master' into stack-overflow-search-highlight-fix

This commit is contained in:
Tommy Le
2024-01-27 17:37:18 +01:00
committed by GitHub
45 changed files with 698 additions and 481 deletions
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-scaffolder-backend-module-github': minor
---
Allow to force the creation of a pull request from a forked repository
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Upgrade jest
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-github': patch
---
Decreased number of teams fetched by GraphQL Query responsible for fetching Teams and Members in organization, due to timeouts when running against big organizations
+5
View File
@@ -0,0 +1,5 @@
---
'@techdocs/cli': patch
---
fix: mkdocs parameter casing
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-app-api': patch
---
The default `ComponentsApi` implementation now uses the `ComponentRef` ID as the component key, rather than the reference instance. This fixes a bug where duplicate installations of `@backstage/frontend-plugin-api` would break the app.
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/frontend-plugin-api': patch
---
App component extensions are no longer wrapped in an `ExtensionBoundary`, allowing them to inherit the outer context instead.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/integration': minor
'@backstage/backend-common': patch
---
Fix rate limit detection by looking for HTTP status code 429 and updating the header `x-ratelimit-remaining` to look for in case of a 403 code is returned
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/plugin-catalog-backend-module-github': minor
---
Prevent Entity Providers from eliminating Users and Groups from the DB when the synchronisation fails
+5
View File
@@ -0,0 +1,5 @@
---
'@backstage/cli': patch
---
Fixed an issue that would cause an invalid `__backstage-autodetected-plugins__.js` to be written when using experimental module discovery.
+6
View File
@@ -0,0 +1,6 @@
---
'@backstage/plugin-scaffolder-backend': patch
'@backstage/plugin-scaffolder-node': patch
---
Add option to configure nunjucks with the `trimBlocks` and `lstripBlocks` options in the fetch:template action
+2 -2
View File
@@ -21,9 +21,9 @@ To switch a project to React 18, there are generally three changes that need to
// highlight-remove-next-line
"@types/react": "^17",
// highlight-remove-next-line
"@types/react": "^17",
"@types/react-dom": "^17",
// highlight-add-next-line
"@types/react-dom": "^18",
"@types/react": "^18",
// highlight-add-next-line
"@types/react-dom": "^18",
},
+1 -1
View File
@@ -277,7 +277,7 @@ module.exports = {
to: 'https://developer.spotify.com/',
},
{
label: 'Github',
label: 'GitHub',
to: 'https://github.com/backstage/',
},
],
+1 -64
View File
@@ -36,11 +36,6 @@ const Community = () => {
label: 'Community newsletter',
link: 'https://info.backstage.spotify.com/newsletter_subscribe',
},
{
content: 'Join the',
label: 'Twitter community',
link: 'https://twitter.com/i/communities/1494019781716062215',
},
];
const officialInitiatives: ICollectionItem[] = [
@@ -58,45 +53,11 @@ const Community = () => {
link: 'https://info.backstage.spotify.com/newsletter_subscribe',
label: 'Subscribe',
},
{
title: 'Contributor Spotlight',
content:
"A recognition for valuable community work. Nominate contributing members for their efforts! We'll put them in the spotlight ❤️",
link: '/nominate',
label: 'Nominate now',
},
];
const communityInitiatives: ICollectionItem[] = [
{
title: 'Open Mic Meetup',
content: (
<>
A casual get together of Backstage users sharing their experiences and
helping each other. Hosted by{' '}
<Link to="https://roadie.io/">Roadie.io</Link> and{' '}
<Link to="https://frontside.com/">Frontside Software</Link>.
</>
),
link: 'https://backstage-openmic.com/',
label: 'Learn more',
},
{
title: 'Backstage Weekly Newsletter',
content: (
<>
A weekly newsletter with news, updates and things community from your
friends at <Link to="https://roadie.io/">Roadie.io</Link>.
</>
),
link: 'https://roadie.io/backstage-weekly/',
label: 'Learn more',
},
];
const trainingNCertifications: ICollectionItem[] = [
{
title: 'Open Mic Meetup',
title: 'Introduction to Backstage: Developer Portals Made Easy (LFS142x)',
content:
'This is a course produced and curated by the Linux Foundation. This course introduces you to Backstage and how to get started with the project.',
link: 'https://training.linuxfoundation.org/training/introduction-to-backstage-developer-portals-made-easy-lfs142x/',
@@ -204,30 +165,6 @@ const Community = () => {
</BannerSectionGrid>
</BannerSection>
<BannerSection diagonalBorder greyBackground>
<BannerSectionGrid
header={<h2 className="text--primary">Community initiatives</h2>}
>
{communityInitiatives.map(
({ title, content, link, label }, index) => (
<ContentBlock
key={index}
title={title}
hasBulletLine
actionButtons={[
{
link,
label,
},
]}
>
<p>{content}</p>
</ContentBlock>
),
)}
</BannerSectionGrid>
</BannerSection>
<BannerSection diagonalBorder>
<BannerSectionGrid
header={
-27
View File
@@ -1,27 +0,0 @@
import React from 'react';
import nominateStyles from './nominate.module.scss';
import Layout from '@theme/Layout';
import clsx from 'clsx';
import { BannerSection } from '@site/src/components/bannerSection/bannerSection';
import { BannerSectionGrid } from '../../components/bannerSection/bannerSectionGrid';
import { ContentBlock } from '@site/src/components/contentBlock/contentBlock';
const Nominate = () => {
return (
<Layout>
<div className={clsx(nominateStyles.nominatePage)}>
<BannerSection greyBackground>
<ContentBlock title={<h1>Contributor Spotlight nomination</h1>}>
<iframe
src="https://docs.google.com/forms/d/e/1FAIpQLSdiZ28O7vwHo6NrwirEzGSbuVyBANSv7ItHqRlgVvSz3Z5xqQ/viewform?embedded=true"
allowFullScreen
></iframe>
</ContentBlock>
</BannerSection>
</div>
</Layout>
);
};
export default Nominate;
@@ -1,32 +0,0 @@
.nominatePage {
font-size: 1.25rem;
&,
& > section,
& > section > div {
flex: 1;
display: flex;
flex-direction: column;
}
& > section > div > div {
flex: 1;
grid-template-rows: auto 1fr;
}
h1 {
font-size: 54px;
line-height: 56px;
word-break: break-word;
}
p {
text-align: justify;
}
iframe {
width: 100%;
height: 100%;
min-height: 400px;
}
}
@@ -333,10 +333,7 @@ export class GithubUrlReader implements UrlReader {
// GitHub returns a 403 response with a couple of headers indicating rate
// limit status. See more in the GitHub docs:
// https://docs.github.com/en/rest/overview/resources-in-the-rest-api#rate-limiting
if (
response.status === 403 &&
response.headers.get('X-RateLimit-Remaining') === '0'
) {
if (this.integration.parseRateLimitInfo(response).isRateLimited) {
message += ' (rate limit exceeded)';
}
+3 -2
View File
@@ -62,7 +62,7 @@
"@swc/core": "^1.3.46",
"@swc/helpers": "^0.5.0",
"@swc/jest": "^0.2.22",
"@types/jest": "^29.0.0",
"@types/jest": "^29.5.11",
"@types/webpack-env": "^1.15.2",
"@typescript-eslint/eslint-plugin": "^6.12.0",
"@typescript-eslint/parser": "^6.7.2",
@@ -100,7 +100,7 @@
"handlebars": "^4.7.3",
"html-webpack-plugin": "^5.3.1",
"inquirer": "^8.2.0",
"jest": "^29.0.2",
"jest": "^29.7.0",
"jest-css-modules": "^2.1.0",
"jest-environment-jsdom": "^29.0.2",
"jest-runtime": "^29.0.2",
@@ -112,6 +112,7 @@
"node-libs-browser": "^2.2.1",
"npm-packlist": "^5.0.0",
"ora": "^5.3.0",
"p-queue": "^6.6.2",
"postcss": "^8.1.0",
"process": "^0.11.10",
"react-dev-utils": "^12.0.0-next.60",
@@ -18,6 +18,7 @@ import { BackstagePackageJson } from '@backstage/cli-node';
import { Config, ConfigReader } from '@backstage/config';
import chokidar from 'chokidar';
import fs from 'fs-extra';
import PQueue from 'p-queue';
import { join as joinPath, resolve as resolvePath } from 'path';
import { paths as cliPaths } from '../paths';
@@ -104,6 +105,9 @@ async function detectPackages(
});
}
// Make sure we're not issuing multiple writes at the same time, which can cause partial overwrites
const writeQueue = new PQueue({ concurrency: 1 });
async function writeDetectedPackagesModule(
pkgs: { name: string; export?: string; import: string }[],
) {
@@ -116,13 +120,15 @@ async function writeDetectedPackagesModule(
)
.join(',');
await fs.writeFile(
joinPath(
cliPaths.targetRoot,
'node_modules',
`${DETECTED_MODULES_MODULE_NAME}.js`,
await writeQueue.add(() =>
fs.writeFile(
joinPath(
cliPaths.targetRoot,
'node_modules',
`${DETECTED_MODULES_MODULE_NAME}.js`,
),
`window['__@backstage/discovered__'] = { modules: [${requirePackageScript}] };`,
),
`window['__@backstage/discovered__'] = { modules: [${requirePackageScript}] };`,
);
}
@@ -25,6 +25,11 @@ const ERR = (name: string, path: string) => ({
message: `${name} does not export ${path}`,
});
// cwd must be restored
const origDir = process.cwd();
afterAll(() => {
process.chdir(origDir);
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
@@ -28,6 +28,11 @@ const ERR_FORBIDDEN = (newImp: string) => ({
message: `Relative imports of monorepo packages are forbidden, use '${newImp}' instead`,
});
// cwd must be restored
const origDir = process.cwd();
afterAll(() => {
process.chdir(origDir);
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
@@ -53,6 +53,11 @@ const ERR_SWITCH_BACK = () => ({
message: 'Switch back to import declaration',
});
// cwd must be restored
const origDir = process.cwd();
afterAll(() => {
process.chdir(origDir);
});
process.chdir(FIXTURE);
const ruleTester = new RuleTester({
@@ -0,0 +1,111 @@
/*
* Copyright 2023 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 React from 'react';
import {
coreExtensionData,
createComponentExtension,
createComponentRef,
createExtension,
createExtensionOverrides,
} from '@backstage/frontend-plugin-api';
import { resolveAppNodeSpecs } from '../../../tree/resolveAppNodeSpecs';
import { resolveAppTree } from '../../../tree/resolveAppTree';
import { App } from '../../../extensions/App';
import { DefaultComponentsApi } from './DefaultComponentsApi';
import { render, screen } from '@testing-library/react';
import { instantiateAppNodeTree } from '../../../tree/instantiateAppNodeTree';
const testRefA = createComponentRef({ id: 'test.a' });
const testRefB1 = createComponentRef({ id: 'test.b' });
const testRefB2 = createComponentRef({ id: 'test.b' });
const baseOverrides = createExtensionOverrides({
extensions: [
App,
createExtension({
namespace: 'app',
name: 'root',
attachTo: { id: 'app', input: 'root' },
output: {
element: coreExtensionData.reactElement,
},
factory() {
return {
element: <div>root</div>,
};
},
}),
],
});
describe('DefaultComponentsApi', () => {
it('should provide components', () => {
const tree = resolveAppTree(
'app',
resolveAppNodeSpecs({
features: [
baseOverrides,
createExtensionOverrides({
extensions: [
createComponentExtension({
ref: testRefA,
loader: { sync: () => () => <div>test.a</div> },
}),
],
}),
],
}),
);
instantiateAppNodeTree(tree.root);
const api = DefaultComponentsApi.fromTree(tree);
const ComponentA = api.getComponent(testRefA);
render(<ComponentA />);
expect(screen.getByText('test.a')).toBeInTheDocument();
});
it('should key extension refs by ID', () => {
const tree = resolveAppTree(
'app',
resolveAppNodeSpecs({
features: [
baseOverrides,
createExtensionOverrides({
extensions: [
createComponentExtension({
ref: testRefB1,
loader: { sync: () => () => <div>test.b</div> },
}),
],
}),
],
}),
);
instantiateAppNodeTree(tree.root);
const api = DefaultComponentsApi.fromTree(tree);
const ComponentB1 = api.getComponent(testRefB1);
const ComponentB2 = api.getComponent(testRefB2);
expect(ComponentB1).toBe(ComponentB2);
render(<ComponentB2 />);
expect(screen.getByText('test.b')).toBeInTheDocument();
});
});
@@ -15,7 +15,12 @@
*/
import { ComponentType } from 'react';
import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api';
import {
AppTree,
ComponentRef,
ComponentsApi,
createComponentExtension,
} from '@backstage/frontend-plugin-api';
/**
* Implementation for the {@linkComponentApi}
@@ -23,14 +28,29 @@ import { ComponentRef, ComponentsApi } from '@backstage/frontend-plugin-api';
* @internal
*/
export class DefaultComponentsApi implements ComponentsApi {
#components: Map<ComponentRef<any>, ComponentType<any>>;
#components: Map<string, ComponentType<any>>;
constructor(components: Map<ComponentRef<any>, any>) {
static fromTree(tree: AppTree) {
const componentEntries = tree.root.edges.attachments
.get('components')
?.reduce((map, e) => {
const data = e.instance?.getData(
createComponentExtension.componentDataRef,
);
if (data) {
map.set(data.ref.id, data.impl);
}
return map;
}, new Map<string, ComponentType>());
return new DefaultComponentsApi(componentEntries ?? new Map());
}
constructor(components: Map<string, any>) {
this.#components = components;
}
getComponent<T extends {}>(ref: ComponentRef<T>): ComponentType<T> {
const impl = this.#components.get(ref);
const impl = this.#components.get(ref.id);
if (!impl) {
throw new Error(`No implementation found for component ref ${ref}`);
}
@@ -14,4 +14,4 @@
* limitations under the License.
*/
export { DefaultComponentsApi } from './ComponentsApi';
export { DefaultComponentsApi } from './DefaultComponentsApi';
@@ -31,17 +31,22 @@ import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/res
/** @internal */
export function resolveAppNodeSpecs(options: {
features: FrontendFeature[];
builtinExtensions: Extension<unknown>[];
parameters: Array<ExtensionParameters>;
features?: FrontendFeature[];
builtinExtensions?: Extension<unknown>[];
parameters?: Array<ExtensionParameters>;
forbidden?: Set<string>;
}): AppNodeSpec[] {
const { builtinExtensions, parameters, forbidden = new Set() } = options;
const {
builtinExtensions = [],
parameters = [],
forbidden = new Set(),
features = [],
} = options;
const plugins = options.features.filter(
const plugins = features.filter(
(f): f is BackstagePlugin => f.$$type === '@backstage/BackstagePlugin',
);
const overrides = options.features.filter(
const overrides = features.filter(
(f): f is ExtensionOverrides =>
f.$$type === '@backstage/ExtensionOverrides',
);
@@ -19,11 +19,9 @@ import { ConfigReader } from '@backstage/config';
import {
AppTree,
appTreeApiRef,
ComponentRef,
componentsApiRef,
coreExtensionData,
createApiExtension,
createComponentExtension,
createThemeExtension,
createTranslationExtension,
FrontendFeature,
@@ -373,22 +371,10 @@ function createApiHolder(
factory: () => routeResolutionApi,
});
const componentsExtensions =
tree.root.edges.attachments
.get('components')
?.map(e => e.instance?.getData(createComponentExtension.componentDataRef))
.filter(x => !!x) ?? [];
const componentsMap = componentsExtensions.reduce(
(components, component) =>
component ? components.set(component.ref, component?.impl) : components,
new Map<ComponentRef<any>, any>(),
);
factoryRegistry.register('static', {
api: componentsApiRef,
deps: {},
factory: () => new DefaultComponentsApi(componentsMap),
factory: () => DefaultComponentsApi.fromTree(tree),
});
factoryRegistry.register('static', {
@@ -14,7 +14,7 @@
* limitations under the License.
*/
import React, { lazy, ComponentType } from 'react';
import { lazy, ComponentType } from 'react';
import {
AnyExtensionInputMap,
ResolvedExtensionInputs,
@@ -23,7 +23,7 @@ import {
} from '../wiring';
import { Expand } from '../types';
import { PortableSchema } from '../schema';
import { ExtensionBoundary, ComponentRef } from '../components';
import { ComponentRef } from '../components';
/** @public */
export function createComponentExtension<
@@ -61,28 +61,26 @@ export function createComponentExtension<
output: {
component: createComponentExtension.componentDataRef,
},
factory({ config, inputs, node }) {
let ExtensionComponent: ComponentType<TProps>;
factory({ config, inputs }) {
if ('sync' in options.loader) {
ExtensionComponent = options.loader.sync({ config, inputs });
} else {
const lazyLoader = options.loader.lazy;
ExtensionComponent = lazy(() =>
lazyLoader({ config, inputs }).then(Component => ({
default: Component,
})),
) as unknown as ComponentType<TProps>;
return {
component: {
ref: options.ref,
impl: options.loader.sync({ config, inputs }) as ComponentType,
},
};
}
const lazyLoader = options.loader.lazy;
const ExtensionComponent = lazy(() =>
lazyLoader({ config, inputs }).then(Component => ({
default: Component,
})),
) as unknown as ComponentType;
return {
component: {
ref: options.ref,
impl: props => (
<ExtensionBoundary node={node}>
<ExtensionComponent {...(props as TProps)} />
</ExtensionBoundary>
),
impl: ExtensionComponent,
},
};
},
+9
View File
@@ -4,6 +4,7 @@
```ts
import { Config } from '@backstage/config';
import { ConsumedResponse } from '@backstage/errors';
import { RestEndpointMethodTypes } from '@octokit/rest';
// @public
@@ -567,6 +568,8 @@ export class GithubIntegration implements ScmIntegration {
// (undocumented)
static factory: ScmIntegrationsFactory<GithubIntegration>;
// (undocumented)
parseRateLimitInfo(response: ConsumedResponse): RateLimitInfo;
// (undocumented)
resolveEditUrl(url: string): string;
// (undocumented)
resolveUrl(options: {
@@ -694,6 +697,12 @@ export type PersonalAccessTokenCredential = AzureCredentialBase & {
personalAccessToken: string;
};
// @public
export interface RateLimitInfo {
// (undocumented)
isRateLimited: boolean;
}
// @public
export function readAwsS3IntegrationConfig(
config: Config,
+1
View File
@@ -35,6 +35,7 @@
"dependencies": {
"@azure/identity": "^4.0.0",
"@backstage/config": "workspace:^",
"@backstage/errors": "workspace:^",
"@octokit/auth-app": "^4.0.0",
"@octokit/rest": "^19.0.3",
"cross-fetch": "^4.0.0",
@@ -78,6 +78,33 @@ describe('GithubIntegration', () => {
),
).toBe('https://github.com/backstage/backstage/edit/master/README.md');
});
describe('isRateLimited', () => {
const integration = new GithubIntegration({ host: 'h.com' });
it.each`
status | ratelimitRemaining | expected
${404} | ${100} | ${false}
${429} | ${undefined} | ${true}
${429} | ${100} | ${true}
${403} | ${100} | ${false}
${403} | ${0} | ${true}
`(
'(statusCode: $status, header: $ratelimitRemaining) === $expected',
({ status, ratelimitRemaining, expected }) => {
const headers = new Headers({
'x-ratelimit-remaining': ratelimitRemaining,
});
const result = integration.parseRateLimitInfo({
status,
headers,
} as Response);
expect(result).toMatchObject({
isRateLimited: expected,
});
},
);
});
});
describe('replaceGithubUrlType', () => {
@@ -15,11 +15,16 @@
*/
import { basicIntegrations, defaultScmResolveUrl } from '../helpers';
import { ScmIntegration, ScmIntegrationsFactory } from '../types';
import {
RateLimitInfo,
ScmIntegration,
ScmIntegrationsFactory,
} from '../types';
import {
GithubIntegrationConfig,
readGithubIntegrationConfigs,
} from './config';
import { ConsumedResponse } from '@backstage/errors';
/**
* A GitHub based integration.
@@ -65,6 +70,15 @@ export class GithubIntegration implements ScmIntegration {
resolveEditUrl(url: string): string {
return replaceGithubUrlType(url, 'edit');
}
parseRateLimitInfo(response: ConsumedResponse): RateLimitInfo {
return {
isRateLimited:
response.status === 429 ||
(response.status === 403 &&
response.headers.get('x-ratelimit-remaining') === '0'),
};
}
}
/**
+1
View File
@@ -37,5 +37,6 @@ export type {
ScmIntegration,
ScmIntegrationsFactory,
ScmIntegrationsGroup,
RateLimitInfo,
} from './types';
export type { ScmIntegrationRegistry } from './registry';
+9
View File
@@ -108,3 +108,12 @@ export interface ScmIntegrationsGroup<T extends ScmIntegration> {
export type ScmIntegrationsFactory<T extends ScmIntegration> = (options: {
config: Config;
}) => ScmIntegrationsGroup<T>;
/**
* Encapsulates information about the RateLimit state
*
* @public
*/
export interface RateLimitInfo {
isRateLimited: boolean;
}
@@ -117,7 +117,7 @@ export default async function serve(opts: OptionValues) {
stderrLogFunc: mkdocsLogFunc,
mkdocsConfigFileName: mkdocsYmlPath,
mkdocsParameterClean: opts.mkdocsParameterClean,
mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyReload,
mkdocsParameterDirtyReload: opts.mkdocsParameterDirtyreload,
mkdocsParameterStrict: opts.mkdocsParameterStrict,
});
@@ -193,7 +193,7 @@ export async function getOrganizationTeams(
const query = `
query teams($org: String!, $cursor: String) {
organization(login: $org) {
teams(first: 100, after: $cursor) {
teams(first: 50, after: $cursor) {
pageInfo { hasNextPage, endCursor }
nodes {
slug
@@ -17,10 +17,7 @@
import { getVoidLogger } from '@backstage/backend-common';
import { GroupEntity, UserEntity } from '@backstage/catalog-model';
import { ConfigReader } from '@backstage/config';
import {
GithubCredentialsProvider,
GithubIntegrationConfig,
} from '@backstage/integration';
import { GithubCredentialsProvider } from '@backstage/integration';
import { EntityProviderConnection } from '@backstage/plugin-catalog-node';
import { EventSubscriber } from '@backstage/plugin-events-node';
import { graphql } from '@octokit/graphql';
@@ -28,6 +25,7 @@ import {
GithubMultiOrgEntityProvider,
withLocations,
} from './GithubMultiOrgEntityProvider';
import { Logger } from 'winston';
jest.mock('@octokit/graphql');
@@ -43,11 +41,50 @@ jest.mock('@backstage/integration', () => ({
describe('GithubMultiOrgEntityProvider', () => {
describe('read', () => {
let mockClient: jest.Mock<any, any, any>;
let entityProviderConnection: EntityProviderConnection;
let logger: Logger;
let gitHubConfig: { host: string };
let mockGetCredentials: jest.Mock<any, any, any>;
let entityProvider: GithubMultiOrgEntityProvider;
beforeEach(() => {
mockClient = jest.fn();
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
entityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
logger = getVoidLogger();
gitHubConfig = { host: 'github.com' };
mockGetCredentials = jest.fn().mockReturnValue({
headers: { token: 'blah' },
type: 'app',
});
const githubCredentialsProvider: GithubCredentialsProvider = {
getCredentials: mockGetCredentials,
};
entityProvider = new GithubMultiOrgEntityProvider({
id: 'my-id',
gitHubConfig,
githubCredentialsProvider,
githubUrl: 'https://github.com',
logger,
orgs: ['orgA', 'orgB'], // only include for tests that require it
});
entityProvider.connect(entityProviderConnection);
});
afterEach(() => jest.resetAllMocks());
it('should read specified orgs', async () => {
const mockClient = jest.fn();
mockClient
.mockResolvedValueOnce({
organization: {
@@ -155,36 +192,6 @@ describe('GithubMultiOrgEntityProvider', () => {
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const logger = getVoidLogger();
const gitHubConfig: GithubIntegrationConfig = {
host: 'github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
headers: { token: 'blah' },
type: 'app',
});
const githubCredentialsProvider: GithubCredentialsProvider = {
getCredentials: mockGetCredentials,
};
const entityProvider = new GithubMultiOrgEntityProvider({
id: 'my-id',
gitHubConfig,
githubCredentialsProvider,
githubUrl: 'https://github.com',
logger,
orgs: ['orgA', 'orgB'],
});
entityProvider.connect(entityProviderConnection);
await entityProvider.read();
expect(mockGetCredentials).toHaveBeenCalledWith({
@@ -353,8 +360,6 @@ describe('GithubMultiOrgEntityProvider', () => {
},
]);
const mockClient = jest.fn();
mockClient
.mockResolvedValueOnce({
organization: {
@@ -462,26 +467,11 @@ describe('GithubMultiOrgEntityProvider', () => {
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const logger = getVoidLogger();
const gitHubConfig: GithubIntegrationConfig = {
host: 'github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
headers: { token: 'blah' },
type: 'app',
});
const githubCredentialsProvider: GithubCredentialsProvider = {
getCredentials: mockGetCredentials,
};
const entityProvider = new GithubMultiOrgEntityProvider({
entityProvider = new GithubMultiOrgEntityProvider({
id: 'my-id',
gitHubConfig,
githubCredentialsProvider,
@@ -642,6 +632,16 @@ describe('GithubMultiOrgEntityProvider', () => {
type: 'full',
});
});
it('should not call applyMutation if an error is thrown', async () => {
mockClient.mockImplementationOnce(() => {
throw new Error('Network Error');
});
await expect(entityProvider.read()).rejects.toThrow('Network Error');
expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled();
});
});
describe('withLocations', () => {
@@ -248,53 +248,49 @@ export class GithubMultiOrgEntityProvider implements EntityProvider {
: await this.getAllOrgs(this.options.gitHubConfig);
for (const org of orgsToProcess) {
try {
const { headers, type: tokenType } =
await this.options.githubCredentialsProvider.getCredentials({
url: `${this.options.githubUrl}/${org}`,
});
const client = graphql.defaults({
baseUrl: this.options.gitHubConfig.apiBaseUrl,
headers,
const { headers, type: tokenType } =
await this.options.githubCredentialsProvider.getCredentials({
url: `${this.options.githubUrl}/${org}`,
});
const client = graphql.defaults({
baseUrl: this.options.gitHubConfig.apiBaseUrl,
headers,
});
logger.info(`Reading GitHub users and teams for org: ${org}`);
logger.info(`Reading GitHub users and teams for org: ${org}`);
const { users } = await getOrganizationUsers(
client,
org,
tokenType,
this.options.userTransformer,
);
const { users } = await getOrganizationUsers(
client,
org,
tokenType,
this.options.userTransformer,
);
const { teams } = await getOrganizationTeams(
client,
org,
this.defaultMultiOrgTeamTransformer.bind(this),
);
const { teams } = await getOrganizationTeams(
client,
org,
this.defaultMultiOrgTeamTransformer.bind(this),
);
// Grab current users from `allUsersMap` if they already exist in our
// pending users so we can append to their group membership relations
const pendingUsers = users.map(u => {
const userRef = stringifyEntityRef(u);
if (!allUsersMap.has(userRef)) {
allUsersMap.set(userRef, u);
}
return allUsersMap.get(userRef);
});
if (areGroupEntities(teams)) {
buildOrgHierarchy(teams);
if (areUserEntities(pendingUsers)) {
assignGroupsToUsers(pendingUsers, teams);
}
// Grab current users from `allUsersMap` if they already exist in our
// pending users so we can append to their group membership relations
const pendingUsers = users.map(u => {
const userRef = stringifyEntityRef(u);
if (!allUsersMap.has(userRef)) {
allUsersMap.set(userRef, u);
}
allTeams.push(...teams);
} catch (e) {
logger.error(`Failed to read GitHub org data for ${org}: ${e}`);
return allUsersMap.get(userRef);
});
if (areGroupEntities(teams)) {
buildOrgHierarchy(teams);
if (areUserEntities(pendingUsers)) {
assignGroupsToUsers(pendingUsers, teams);
}
}
allTeams.push(...teams);
}
const allUsers = Array.from(allUsersMap.values());
@@ -30,13 +30,51 @@ jest.mock('@octokit/graphql');
describe('GithubOrgEntityProvider', () => {
describe('read', () => {
let mockClient;
let entityProviderConnection: EntityProviderConnection;
let entityProvider: GithubOrgEntityProvider;
const setupMocks = (response: ((...args: any) => any) | undefined) => {
mockClient = jest.fn().mockImplementation(response);
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
};
beforeEach(() => {
entityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const logger = getVoidLogger();
const gitHubConfig = {
host: 'https://github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
headers: { token: 'blah' },
type: 'app',
});
const githubCredentialsProvider = {
getCredentials: mockGetCredentials,
};
entityProvider = new GithubOrgEntityProvider({
id: 'my-id',
githubCredentialsProvider,
orgUrl: 'https://github.com/backstage',
gitHubConfig,
logger,
});
entityProvider.connect(entityProviderConnection);
});
afterEach(() => jest.resetAllMocks());
it('should read org data and apply mutation', async () => {
const mockClient = jest.fn();
mockClient
.mockResolvedValueOnce({
setupMocks(() =>
Promise.resolve({
organization: {
membersWithRole: {
pageInfo: { hasNextPage: false },
@@ -50,10 +88,6 @@ describe('GithubOrgEntityProvider', () => {
},
],
},
},
})
.mockResolvedValueOnce({
organization: {
teams: {
pageInfo: { hasNextPage: false },
nodes: [
@@ -76,38 +110,8 @@ describe('GithubOrgEntityProvider', () => {
],
},
},
});
(graphql.defaults as jest.Mock).mockReturnValue(mockClient);
const entityProviderConnection: EntityProviderConnection = {
applyMutation: jest.fn(),
refresh: jest.fn(),
};
const logger = getVoidLogger();
const gitHubConfig: GithubIntegrationConfig = {
host: 'https://github.com',
};
const mockGetCredentials = jest.fn().mockReturnValue({
headers: { token: 'blah' },
type: 'app',
});
const githubCredentialsProvider: GithubCredentialsProvider = {
getCredentials: mockGetCredentials,
};
const entityProvider = new GithubOrgEntityProvider({
id: 'my-id',
githubCredentialsProvider,
orgUrl: 'https://github.com/backstage',
gitHubConfig,
logger,
});
entityProvider.connect(entityProviderConnection);
}),
);
await entityProvider.read();
@@ -171,6 +175,14 @@ describe('GithubOrgEntityProvider', () => {
type: 'full',
});
});
it('should not apply mutation if a request fails', async () => {
setupMocks(() => Promise.reject(new Error('Network error')));
await expect(entityProvider.read()).rejects.toThrow('Network error');
expect(entityProviderConnection.applyMutation).not.toHaveBeenCalled();
});
});
describe('withLocations', () => {
@@ -375,6 +375,7 @@ export const createPublishGithubPullRequestAction: (
teamReviewers?: string[] | undefined;
commitMessage?: string | undefined;
update?: boolean | undefined;
forceFork?: boolean | undefined;
},
JsonObject
>;
@@ -687,4 +687,57 @@ describe('createPublishGithubPullRequestAction', () => {
});
});
});
describe('with force fork', () => {
let input: GithubPullRequestActionInput;
let ctx: ActionContext<GithubPullRequestActionInput>;
beforeEach(() => {
input = {
repoUrl: 'github.com?owner=myorg&repo=myrepo',
title: 'Create my new app',
branchName: 'new-app',
description: 'This PR is really good',
forceFork: true,
};
mockDir.setContent({
[workspacePath]: { 'file.txt': 'Hello there!' },
});
ctx = {
createTemporaryDirectory: jest.fn(),
output: jest.fn(),
logger: getRootLogger(),
logStream: new Writable(),
input,
workspacePath,
};
});
it('creates a pull request', async () => {
await instance.handler(ctx);
expect(fakeClient.createPullRequest).toHaveBeenCalledWith({
owner: 'myorg',
repo: 'myrepo',
title: 'Create my new app',
head: 'new-app',
body: 'This PR is really good',
changes: [
{
commit: 'Create my new app',
files: {
'file.txt': {
content: Buffer.from('Hello there!').toString('base64'),
encoding: 'base64',
mode: '100644',
},
},
},
],
forceFork: true,
});
});
});
});
@@ -135,6 +135,7 @@ export const createPublishGithubPullRequestAction = (
teamReviewers?: string[];
commitMessage?: string;
update?: boolean;
forceFork?: boolean;
}>({
id: 'publish:github:pull-request',
examples,
@@ -217,6 +218,11 @@ export const createPublishGithubPullRequestAction = (
title: 'Update',
description: 'Update pull request if already exists',
},
forceFork: {
type: 'boolean',
title: 'Force Fork',
description: 'Create pull request from a fork',
},
},
},
output: {
@@ -255,6 +261,7 @@ export const createPublishGithubPullRequestAction = (
teamReviewers,
commitMessage,
update,
forceFork,
} = ctx.input;
const { owner, repo, host } = parseRepoUrl(repoUrl, integrations);
@@ -327,6 +334,7 @@ export const createPublishGithubPullRequestAction = (
head: branchName,
draft,
update,
forceFork,
};
if (targetBranchName) {
createOptions.base = targetBranchName;
+3
View File
@@ -177,6 +177,8 @@ export function createFetchTemplateAction(options: {
copyWithoutTemplating?: string[] | undefined;
cookiecutterCompat?: boolean | undefined;
replace?: boolean | undefined;
trimBlocks?: boolean | undefined;
lstripBlocks?: boolean | undefined;
},
JsonObject
>;
@@ -268,6 +270,7 @@ export const createPublishGithubPullRequestAction: (
teamReviewers?: string[] | undefined;
commitMessage?: string | undefined;
update?: boolean | undefined;
forceFork?: boolean | undefined;
},
JsonObject
>;
@@ -34,6 +34,7 @@ const { render, renderCompat } = (() => {
const env = module.exports.configure({
autoescape: false,
...JSON.parse(nunjucksConfigs),
tags: {
variableStart: '\${{',
variableEnd: '}}',
@@ -42,6 +43,7 @@ const { render, renderCompat } = (() => {
const compatEnv = module.exports.configure({
autoescape: false,
...JSON.parse(nunjucksConfigs),
tags: {
variableStart: '{{',
variableEnd: '}}',
@@ -102,13 +104,14 @@ export type TemplateFilter = _TemplateFilter;
*/
export type TemplateGlobal = _TemplateGlobal;
export interface SecureTemplaterOptions {
interface SecureTemplaterOptions {
/* Enables jinja compatibility and the "jsonify" filter */
cookiecutterCompat?: boolean;
/* Extra user-provided nunjucks filters */
templateFilters?: Record<string, TemplateFilter>;
/* Extra user-provided nunjucks globals */
templateGlobals?: Record<string, TemplateGlobal>;
nunjucksConfigs?: { trimBlocks?: boolean; lstripBlocks?: boolean };
}
export type SecureTemplateRenderer = (
@@ -122,6 +125,7 @@ export class SecureTemplater {
cookiecutterCompat,
templateFilters = {},
templateGlobals = {},
nunjucksConfigs = {},
} = options;
const isolate = new Isolate({ memoryLimit: 128 });
@@ -140,6 +144,8 @@ export class SecureTemplater {
mkScript(nunjucksSource),
);
await contextGlobal.set('nunjucksConfigs', JSON.stringify(nunjucksConfigs));
const availableFilters = Object.keys(templateFilters);
await contextGlobal.set(
@@ -69,6 +69,8 @@ export function createFetchTemplateAction(options: {
copyWithoutTemplating?: string[];
cookiecutterCompat?: boolean;
replace?: boolean;
trimBlocks?: boolean;
lstripBlocks?: boolean;
}>({
id: 'fetch:template',
description:
@@ -237,6 +239,10 @@ export function createFetchTemplateAction(options: {
...additionalTemplateFilters,
},
templateGlobals: additionalTemplateGlobals,
nunjucksConfigs: {
trimBlocks: ctx.input.trimBlocks,
lstripBlocks: ctx.input.lstripBlocks,
},
});
for (const location of allEntriesInTemplate) {
+164 -162
View File
@@ -1547,15 +1547,15 @@ __metadata:
linkType: hard
"@azure/identity@npm:^4.0.0":
version: 4.0.0
resolution: "@azure/identity@npm:4.0.0"
version: 4.0.1
resolution: "@azure/identity@npm:4.0.1"
dependencies:
"@azure/abort-controller": ^1.0.0
"@azure/core-auth": ^1.5.0
"@azure/core-client": ^1.4.0
"@azure/core-rest-pipeline": ^1.1.0
"@azure/core-tracing": ^1.0.0
"@azure/core-util": ^1.0.0
"@azure/core-util": ^1.3.0
"@azure/logger": ^1.0.0
"@azure/msal-browser": ^3.5.0
"@azure/msal-node": ^2.5.1
@@ -1564,7 +1564,7 @@ __metadata:
open: ^8.0.0
stoppable: ^1.1.0
tslib: ^2.2.0
checksum: 534a62afe4715d18e221e021f8088873b1efad34344bd4c6c4685572c89517f15a646e246e9233e8db7942b0a5c73f1961ea63e3baf39c28edde1ba51da4423d
checksum: 2c975ca70b274dc185022c2b19f1774079fd5cfb08c78ec3500e4baf973911b2958031d5fa36369d3c9acdeb25db457cc497991e7393b383103c058e097703e5
languageName: node
linkType: hard
@@ -3586,7 +3586,7 @@ __metadata:
"@types/fs-extra": ^9.0.1
"@types/http-proxy": ^1.17.4
"@types/inquirer": ^8.1.3
"@types/jest": ^29.0.0
"@types/jest": ^29.5.11
"@types/minimatch": ^5.0.0
"@types/node": ^18.17.8
"@types/npm-packlist": ^3.0.0
@@ -3635,7 +3635,7 @@ __metadata:
handlebars: ^4.7.3
html-webpack-plugin: ^5.3.1
inquirer: ^8.2.0
jest: ^29.0.2
jest: ^29.7.0
jest-css-modules: ^2.1.0
jest-environment-jsdom: ^29.0.2
jest-runtime: ^29.0.2
@@ -3649,6 +3649,7 @@ __metadata:
nodemon: ^3.0.1
npm-packlist: ^5.0.0
ora: ^5.3.0
p-queue: ^6.6.2
postcss: ^8.1.0
process: ^0.11.10
react-dev-utils: ^12.0.0-next.60
@@ -4147,6 +4148,7 @@ __metadata:
"@backstage/cli": "workspace:^"
"@backstage/config": "workspace:^"
"@backstage/config-loader": "workspace:^"
"@backstage/errors": "workspace:^"
"@octokit/auth-app": ^4.0.0
"@octokit/rest": ^19.0.3
"@types/luxon": ^3.0.0
@@ -10178,13 +10180,13 @@ __metadata:
linkType: hard
"@codemirror/view@npm:^6.0.0, @codemirror/view@npm:^6.23.0":
version: 6.23.0
resolution: "@codemirror/view@npm:6.23.0"
version: 6.23.1
resolution: "@codemirror/view@npm:6.23.1"
dependencies:
"@codemirror/state": ^6.4.0
style-mod: ^4.1.0
w3c-keyname: ^2.2.4
checksum: 6e5f2314a3da2c724dc6a525654d949d3f2fcf7009f4d85f980d52ddc885c8969717e903ca1d9132afbe7c524af5d19bff8285fd394106282a965ae83aa47db4
checksum: 5ea3ba5761c574e1f6e1f1058cb452189c890982a77991606d0ae40da3c6fff77f7c7fc3c43fa78d62677ccdfa65dbc56175706b793e34ad4ec7a63b21e8c18e
languageName: node
linkType: hard
@@ -10469,9 +10471,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/aix-ppc64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/aix-ppc64@npm:0.19.11"
"@esbuild/aix-ppc64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/aix-ppc64@npm:0.19.12"
conditions: os=aix & cpu=ppc64
languageName: node
linkType: hard
@@ -10490,9 +10492,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/android-arm64@npm:0.19.11"
"@esbuild/android-arm64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/android-arm64@npm:0.19.12"
conditions: os=android & cpu=arm64
languageName: node
linkType: hard
@@ -10511,9 +10513,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-arm@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/android-arm@npm:0.19.11"
"@esbuild/android-arm@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/android-arm@npm:0.19.12"
conditions: os=android & cpu=arm
languageName: node
linkType: hard
@@ -10532,9 +10534,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/android-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/android-x64@npm:0.19.11"
"@esbuild/android-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/android-x64@npm:0.19.12"
conditions: os=android & cpu=x64
languageName: node
linkType: hard
@@ -10553,9 +10555,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-arm64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/darwin-arm64@npm:0.19.11"
"@esbuild/darwin-arm64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/darwin-arm64@npm:0.19.12"
conditions: os=darwin & cpu=arm64
languageName: node
linkType: hard
@@ -10574,9 +10576,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/darwin-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/darwin-x64@npm:0.19.11"
"@esbuild/darwin-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/darwin-x64@npm:0.19.12"
conditions: os=darwin & cpu=x64
languageName: node
linkType: hard
@@ -10595,9 +10597,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-arm64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/freebsd-arm64@npm:0.19.11"
"@esbuild/freebsd-arm64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/freebsd-arm64@npm:0.19.12"
conditions: os=freebsd & cpu=arm64
languageName: node
linkType: hard
@@ -10616,9 +10618,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/freebsd-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/freebsd-x64@npm:0.19.11"
"@esbuild/freebsd-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/freebsd-x64@npm:0.19.12"
conditions: os=freebsd & cpu=x64
languageName: node
linkType: hard
@@ -10637,9 +10639,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-arm64@npm:0.19.11"
"@esbuild/linux-arm64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-arm64@npm:0.19.12"
conditions: os=linux & cpu=arm64
languageName: node
linkType: hard
@@ -10658,9 +10660,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-arm@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-arm@npm:0.19.11"
"@esbuild/linux-arm@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-arm@npm:0.19.12"
conditions: os=linux & cpu=arm
languageName: node
linkType: hard
@@ -10679,9 +10681,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ia32@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-ia32@npm:0.19.11"
"@esbuild/linux-ia32@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-ia32@npm:0.19.12"
conditions: os=linux & cpu=ia32
languageName: node
linkType: hard
@@ -10700,9 +10702,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-loong64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-loong64@npm:0.19.11"
"@esbuild/linux-loong64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-loong64@npm:0.19.12"
conditions: os=linux & cpu=loong64
languageName: node
linkType: hard
@@ -10721,9 +10723,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-mips64el@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-mips64el@npm:0.19.11"
"@esbuild/linux-mips64el@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-mips64el@npm:0.19.12"
conditions: os=linux & cpu=mips64el
languageName: node
linkType: hard
@@ -10742,9 +10744,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-ppc64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-ppc64@npm:0.19.11"
"@esbuild/linux-ppc64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-ppc64@npm:0.19.12"
conditions: os=linux & cpu=ppc64
languageName: node
linkType: hard
@@ -10763,9 +10765,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-riscv64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-riscv64@npm:0.19.11"
"@esbuild/linux-riscv64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-riscv64@npm:0.19.12"
conditions: os=linux & cpu=riscv64
languageName: node
linkType: hard
@@ -10784,9 +10786,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-s390x@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-s390x@npm:0.19.11"
"@esbuild/linux-s390x@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-s390x@npm:0.19.12"
conditions: os=linux & cpu=s390x
languageName: node
linkType: hard
@@ -10805,9 +10807,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/linux-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/linux-x64@npm:0.19.11"
"@esbuild/linux-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/linux-x64@npm:0.19.12"
conditions: os=linux & cpu=x64
languageName: node
linkType: hard
@@ -10826,9 +10828,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/netbsd-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/netbsd-x64@npm:0.19.11"
"@esbuild/netbsd-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/netbsd-x64@npm:0.19.12"
conditions: os=netbsd & cpu=x64
languageName: node
linkType: hard
@@ -10847,9 +10849,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/openbsd-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/openbsd-x64@npm:0.19.11"
"@esbuild/openbsd-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/openbsd-x64@npm:0.19.12"
conditions: os=openbsd & cpu=x64
languageName: node
linkType: hard
@@ -10868,9 +10870,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/sunos-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/sunos-x64@npm:0.19.11"
"@esbuild/sunos-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/sunos-x64@npm:0.19.12"
conditions: os=sunos & cpu=x64
languageName: node
linkType: hard
@@ -10889,9 +10891,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-arm64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/win32-arm64@npm:0.19.11"
"@esbuild/win32-arm64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/win32-arm64@npm:0.19.12"
conditions: os=win32 & cpu=arm64
languageName: node
linkType: hard
@@ -10910,9 +10912,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-ia32@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/win32-ia32@npm:0.19.11"
"@esbuild/win32-ia32@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/win32-ia32@npm:0.19.12"
conditions: os=win32 & cpu=ia32
languageName: node
linkType: hard
@@ -10931,9 +10933,9 @@ __metadata:
languageName: node
linkType: hard
"@esbuild/win32-x64@npm:0.19.11":
version: 0.19.11
resolution: "@esbuild/win32-x64@npm:0.19.11"
"@esbuild/win32-x64@npm:0.19.12":
version: 0.19.12
resolution: "@esbuild/win32-x64@npm:0.19.12"
conditions: os=win32 & cpu=x64
languageName: node
linkType: hard
@@ -12836,9 +12838,9 @@ __metadata:
languageName: node
linkType: hard
"@mswjs/interceptors@npm:^0.25.14":
version: 0.25.14
resolution: "@mswjs/interceptors@npm:0.25.14"
"@mswjs/interceptors@npm:^0.25.15":
version: 0.25.15
resolution: "@mswjs/interceptors@npm:0.25.15"
dependencies:
"@open-draft/deferred-promise": ^2.2.0
"@open-draft/logger": ^0.3.0
@@ -12846,7 +12848,7 @@ __metadata:
is-node-process: ^1.2.0
outvariant: ^1.2.1
strict-event-emitter: ^0.5.1
checksum: caf9513cf6848ff0c3f1402abf881d3c1333f68fae54076cfd3fd919b7edb709769342bd3afd2a60ef4ae698ef47776b6203e0ea6a5d8e788e97eda7ed9dbbd4
checksum: dbe43f2df398bbe48ee5ea4ecf055144c9c5689218e5955b01378ea084044dab992d1b434d3533e75df044030a976402e53ec65d743c2619e024defd75ffc076
languageName: node
linkType: hard
@@ -18212,7 +18214,7 @@ __metadata:
languageName: node
linkType: hard
"@types/jest@npm:*, @types/jest@npm:^29.0.0":
"@types/jest@npm:*, @types/jest@npm:^29.0.0, @types/jest@npm:^29.5.11":
version: 29.5.11
resolution: "@types/jest@npm:29.5.11"
dependencies:
@@ -19353,14 +19355,14 @@ __metadata:
linkType: hard
"@typescript-eslint/eslint-plugin@npm:^6.12.0":
version: 6.19.0
resolution: "@typescript-eslint/eslint-plugin@npm:6.19.0"
version: 6.19.1
resolution: "@typescript-eslint/eslint-plugin@npm:6.19.1"
dependencies:
"@eslint-community/regexpp": ^4.5.1
"@typescript-eslint/scope-manager": 6.19.0
"@typescript-eslint/type-utils": 6.19.0
"@typescript-eslint/utils": 6.19.0
"@typescript-eslint/visitor-keys": 6.19.0
"@typescript-eslint/scope-manager": 6.19.1
"@typescript-eslint/type-utils": 6.19.1
"@typescript-eslint/utils": 6.19.1
"@typescript-eslint/visitor-keys": 6.19.1
debug: ^4.3.4
graphemer: ^1.4.0
ignore: ^5.2.4
@@ -19373,25 +19375,25 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 9880567d52d4e6559e2343caeed68f856d593b42816b8f705cd98d5a5b46cc620e3bebaaf08bbc982061bba18e5be94d6c539c0c816e8772ddabba0ad4e9363e
checksum: ad04000cd6c15d864ff92655baa3aec99bb0ccf4714fedd145fedde60a27590a5feafe480beb2f0f3864b416098bde1e9431bada7480eb7ca4efad891e1d2f6f
languageName: node
linkType: hard
"@typescript-eslint/parser@npm:^6.7.2":
version: 6.19.0
resolution: "@typescript-eslint/parser@npm:6.19.0"
version: 6.19.1
resolution: "@typescript-eslint/parser@npm:6.19.1"
dependencies:
"@typescript-eslint/scope-manager": 6.19.0
"@typescript-eslint/types": 6.19.0
"@typescript-eslint/typescript-estree": 6.19.0
"@typescript-eslint/visitor-keys": 6.19.0
"@typescript-eslint/scope-manager": 6.19.1
"@typescript-eslint/types": 6.19.1
"@typescript-eslint/typescript-estree": 6.19.1
"@typescript-eslint/visitor-keys": 6.19.1
debug: ^4.3.4
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
peerDependenciesMeta:
typescript:
optional: true
checksum: 0ac91ff83fdf693de4494b45be79f25803ea6ca3ee717e4f96785b7ffc1da0180adb0426b61bc6eff5666c8ef9ea58c50efbd4351ef9018c0050116cbd74a62b
checksum: cd29619da08a2d9b7123ba4d8240989c747f8e0d5672179d8b147e413ee1334d1fa48570b0c37cf0ae4e26a275fd2d268cbe702c6fed639d3331abbb3292570a
languageName: node
linkType: hard
@@ -19405,22 +19407,22 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/scope-manager@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/scope-manager@npm:6.19.0"
"@typescript-eslint/scope-manager@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/scope-manager@npm:6.19.1"
dependencies:
"@typescript-eslint/types": 6.19.0
"@typescript-eslint/visitor-keys": 6.19.0
checksum: 47d9d1b70cd64f9d1bb717090850e0ff1a34e453c28b43fd0cecaea4db05cacebd60f5da55b35c4b3cc01491f02e9de358f82a0822b27c00e80e3d1a27de32d1
"@typescript-eslint/types": 6.19.1
"@typescript-eslint/visitor-keys": 6.19.1
checksum: 848cdebc16a3803e8a6d6035a7067605309a652bb2425f475f755b5ace4d80d2c17c8c8901f0f4759556da8d0a5b71024d472b85c3f3c70d0e6dcfe2a972ef35
languageName: node
linkType: hard
"@typescript-eslint/type-utils@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/type-utils@npm:6.19.0"
"@typescript-eslint/type-utils@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/type-utils@npm:6.19.1"
dependencies:
"@typescript-eslint/typescript-estree": 6.19.0
"@typescript-eslint/utils": 6.19.0
"@typescript-eslint/typescript-estree": 6.19.1
"@typescript-eslint/utils": 6.19.1
debug: ^4.3.4
ts-api-utils: ^1.0.1
peerDependencies:
@@ -19428,7 +19430,7 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: a88f022617be636f43429a7c7c5cd2e0e29955e96d4a9fed7d03467dc4a432b1240a71009d62213604ddb3522be9694e6b78882ee805687cda107021d1ddb203
checksum: eab1a30f8d85f7c6e2545de5963fbec2f3bb91913d59623069b4b0db372a671ab048c7018376fc853c3af06ea39417f3e7b27dd665027dd812347a5e64cecd77
languageName: node
linkType: hard
@@ -19439,10 +19441,10 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/types@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/types@npm:6.19.0"
checksum: 1371b5ba41c1d2879b3c2823ab01a30cf034e476ef53ff2a7f9e9a4a0056dfbbfecd3143031b05430aa6c749233cacbd01b72cea38a9ece1c6cf95a5cd43da6a
"@typescript-eslint/types@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/types@npm:6.19.1"
checksum: 598ce222b59c20432d06f60703d0c2dd16d9b2151569c192852136c57b8188e3ef6ef9fddaa2c136c9a756fcc7d873c0e29ec41cfd340564842287ef7b4571cd
languageName: node
linkType: hard
@@ -19464,12 +19466,12 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/typescript-estree@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/typescript-estree@npm:6.19.0"
"@typescript-eslint/typescript-estree@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/typescript-estree@npm:6.19.1"
dependencies:
"@typescript-eslint/types": 6.19.0
"@typescript-eslint/visitor-keys": 6.19.0
"@typescript-eslint/types": 6.19.1
"@typescript-eslint/visitor-keys": 6.19.1
debug: ^4.3.4
globby: ^11.1.0
is-glob: ^4.0.3
@@ -19479,24 +19481,24 @@ __metadata:
peerDependenciesMeta:
typescript:
optional: true
checksum: 919f9588840cdab7e0ef6471f4c35d602523b142b2cffeabe9171d6ce65eb7f41614d0cb17e008e0d8e796374821ab053ced35b84642c3b1d491987362f2fdb5
checksum: fb71a14aeee0468780219c5b8d39075f85d360b04ccd0ee88f4f0a615d2c232a6d3016e36d8c6eda2d9dfda86b4f4cc2c3d7582940fb29d33c7cf305e124d4e2
languageName: node
linkType: hard
"@typescript-eslint/utils@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/utils@npm:6.19.0"
"@typescript-eslint/utils@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/utils@npm:6.19.1"
dependencies:
"@eslint-community/eslint-utils": ^4.4.0
"@types/json-schema": ^7.0.12
"@types/semver": ^7.5.0
"@typescript-eslint/scope-manager": 6.19.0
"@typescript-eslint/types": 6.19.0
"@typescript-eslint/typescript-estree": 6.19.0
"@typescript-eslint/scope-manager": 6.19.1
"@typescript-eslint/types": 6.19.1
"@typescript-eslint/typescript-estree": 6.19.1
semver: ^7.5.4
peerDependencies:
eslint: ^7.0.0 || ^8.0.0
checksum: 05a26251a526232b08850b6c3327637213ef989453e353f3a8255433b74893a70d5c38369c528b762e853b7586d7830d728b372494e65f37770ecb05a88112d4
checksum: fe72e75c3ea17a85772b83f148555ea94ff5d55d13586f3fc038833197a74f8071e14c2bbf1781c40eec20005f052f4be2513a725eea82a15da3cb9af3046c70
languageName: node
linkType: hard
@@ -19528,13 +19530,13 @@ __metadata:
languageName: node
linkType: hard
"@typescript-eslint/visitor-keys@npm:6.19.0":
version: 6.19.0
resolution: "@typescript-eslint/visitor-keys@npm:6.19.0"
"@typescript-eslint/visitor-keys@npm:6.19.1":
version: 6.19.1
resolution: "@typescript-eslint/visitor-keys@npm:6.19.1"
dependencies:
"@typescript-eslint/types": 6.19.0
"@typescript-eslint/types": 6.19.1
eslint-visitor-keys: ^3.4.1
checksum: 35b11143e1b55ecf01e0f513085df2cc83d0781f4a8354dc10f6ec3356f66b91a1ed8abadb6fb66af1c1746f9c874eabc8b5636882466e229cda5d6a39aada08
checksum: bdf057a42e776970a89cdd568e493e3ea7ec085544d8f318d33084da63c3395ad2c0fb9cef9f61ceeca41f5dab54ab064b7078fe596889005e412ec74d2d1ae4
languageName: node
linkType: hard
@@ -25766,32 +25768,32 @@ __metadata:
linkType: hard
"esbuild@npm:^0.19.0, esbuild@npm:~0.19.10":
version: 0.19.11
resolution: "esbuild@npm:0.19.11"
version: 0.19.12
resolution: "esbuild@npm:0.19.12"
dependencies:
"@esbuild/aix-ppc64": 0.19.11
"@esbuild/android-arm": 0.19.11
"@esbuild/android-arm64": 0.19.11
"@esbuild/android-x64": 0.19.11
"@esbuild/darwin-arm64": 0.19.11
"@esbuild/darwin-x64": 0.19.11
"@esbuild/freebsd-arm64": 0.19.11
"@esbuild/freebsd-x64": 0.19.11
"@esbuild/linux-arm": 0.19.11
"@esbuild/linux-arm64": 0.19.11
"@esbuild/linux-ia32": 0.19.11
"@esbuild/linux-loong64": 0.19.11
"@esbuild/linux-mips64el": 0.19.11
"@esbuild/linux-ppc64": 0.19.11
"@esbuild/linux-riscv64": 0.19.11
"@esbuild/linux-s390x": 0.19.11
"@esbuild/linux-x64": 0.19.11
"@esbuild/netbsd-x64": 0.19.11
"@esbuild/openbsd-x64": 0.19.11
"@esbuild/sunos-x64": 0.19.11
"@esbuild/win32-arm64": 0.19.11
"@esbuild/win32-ia32": 0.19.11
"@esbuild/win32-x64": 0.19.11
"@esbuild/aix-ppc64": 0.19.12
"@esbuild/android-arm": 0.19.12
"@esbuild/android-arm64": 0.19.12
"@esbuild/android-x64": 0.19.12
"@esbuild/darwin-arm64": 0.19.12
"@esbuild/darwin-x64": 0.19.12
"@esbuild/freebsd-arm64": 0.19.12
"@esbuild/freebsd-x64": 0.19.12
"@esbuild/linux-arm": 0.19.12
"@esbuild/linux-arm64": 0.19.12
"@esbuild/linux-ia32": 0.19.12
"@esbuild/linux-loong64": 0.19.12
"@esbuild/linux-mips64el": 0.19.12
"@esbuild/linux-ppc64": 0.19.12
"@esbuild/linux-riscv64": 0.19.12
"@esbuild/linux-s390x": 0.19.12
"@esbuild/linux-x64": 0.19.12
"@esbuild/netbsd-x64": 0.19.12
"@esbuild/openbsd-x64": 0.19.12
"@esbuild/sunos-x64": 0.19.12
"@esbuild/win32-arm64": 0.19.12
"@esbuild/win32-ia32": 0.19.12
"@esbuild/win32-x64": 0.19.12
dependenciesMeta:
"@esbuild/aix-ppc64":
optional: true
@@ -25841,7 +25843,7 @@ __metadata:
optional: true
bin:
esbuild: bin/esbuild
checksum: ae949a796d1d06b55275ae7491ce137857468f69a93d8cc9c0943d2a701ac54e14dbb250a2ba56f2ad98283669578f1ec3bd85a4681910a5ff29a2470c3bd62c
checksum: 2936e29107b43e65a775b78b7bc66ddd7d76febd73840ac7e825fb22b65029422ff51038a08d19b05154f543584bd3afe7d1ef1c63900429475b17fbe61cb61f
languageName: node
linkType: hard
@@ -31346,7 +31348,7 @@ __metadata:
languageName: node
linkType: hard
"jest@npm:^29.0.2":
"jest@npm:^29.7.0":
version: 29.7.0
resolution: "jest@npm:29.7.0"
dependencies:
@@ -34632,13 +34634,13 @@ __metadata:
linkType: hard
"msw@npm:^2.0.0, msw@npm:^2.0.8":
version: 2.1.4
resolution: "msw@npm:2.1.4"
version: 2.1.5
resolution: "msw@npm:2.1.5"
dependencies:
"@bundled-es-modules/cookie": ^2.0.0
"@bundled-es-modules/statuses": ^1.0.1
"@mswjs/cookies": ^1.1.0
"@mswjs/interceptors": ^0.25.14
"@mswjs/interceptors": ^0.25.15
"@open-draft/until": ^2.1.0
"@types/cookie": ^0.6.0
"@types/statuses": ^2.0.4
@@ -34660,7 +34662,7 @@ __metadata:
optional: true
bin:
msw: cli/index.js
checksum: da8aaf9682ac48a635966beef9add9493297de797b266066bcd8ae0c2708488b81558251412e41489511a63deda1774b42e28197e9b73ddf14d9ecf8bb916e7a
checksum: 19a54a25baa584d1bafa219e1c2245073688eeed6ea5330f27c868a2f279390ee4fbd8a13d64cf0a056149d7e2faa300f901959bab8854e010f29368524d7c4f
languageName: node
linkType: hard