diff --git a/.changeset/calm-items-double.md b/.changeset/calm-items-double.md new file mode 100644 index 0000000000..3db8ad57d9 --- /dev/null +++ b/.changeset/calm-items-double.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend-module-github': minor +--- + +Allow to force the creation of a pull request from a forked repository diff --git a/.changeset/green-flies-draw.md b/.changeset/green-flies-draw.md new file mode 100644 index 0000000000..6b967feceb --- /dev/null +++ b/.changeset/green-flies-draw.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +Upgrade jest diff --git a/.changeset/nice-carrots-dream.md b/.changeset/nice-carrots-dream.md new file mode 100644 index 0000000000..3b57dfd9e0 --- /dev/null +++ b/.changeset/nice-carrots-dream.md @@ -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 diff --git a/.changeset/selfish-mails-scream.md b/.changeset/selfish-mails-scream.md new file mode 100644 index 0000000000..7d1149f27a --- /dev/null +++ b/.changeset/selfish-mails-scream.md @@ -0,0 +1,5 @@ +--- +'@techdocs/cli': patch +--- + +fix: mkdocs parameter casing diff --git a/.changeset/smart-numbers-call.md b/.changeset/smart-numbers-call.md new file mode 100644 index 0000000000..24a2053259 --- /dev/null +++ b/.changeset/smart-numbers-call.md @@ -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. diff --git a/.changeset/strong-lobsters-hide.md b/.changeset/strong-lobsters-hide.md new file mode 100644 index 0000000000..7b51355eb7 --- /dev/null +++ b/.changeset/strong-lobsters-hide.md @@ -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. diff --git a/.changeset/sweet-ravens-glow.md b/.changeset/sweet-ravens-glow.md new file mode 100644 index 0000000000..045f7edbf2 --- /dev/null +++ b/.changeset/sweet-ravens-glow.md @@ -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 diff --git a/.changeset/thirty-dolls-admire.md b/.changeset/thirty-dolls-admire.md new file mode 100644 index 0000000000..1011508a98 --- /dev/null +++ b/.changeset/thirty-dolls-admire.md @@ -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 diff --git a/.changeset/two-geese-explain.md b/.changeset/two-geese-explain.md new file mode 100644 index 0000000000..12ae307cfe --- /dev/null +++ b/.changeset/two-geese-explain.md @@ -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. diff --git a/.changeset/young-ladybugs-decide.md b/.changeset/young-ladybugs-decide.md new file mode 100644 index 0000000000..53284cbc37 --- /dev/null +++ b/.changeset/young-ladybugs-decide.md @@ -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 diff --git a/docs/tutorials/react18-migration.md b/docs/tutorials/react18-migration.md index 2d3b3a961a..c11bc83a7e 100644 --- a/docs/tutorials/react18-migration.md +++ b/docs/tutorials/react18-migration.md @@ -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", }, diff --git a/microsite/docusaurus.config.js b/microsite/docusaurus.config.js index f07f9da6af..df2833b3e2 100644 --- a/microsite/docusaurus.config.js +++ b/microsite/docusaurus.config.js @@ -277,7 +277,7 @@ module.exports = { to: 'https://developer.spotify.com/', }, { - label: 'Github', + label: 'GitHub', to: 'https://github.com/backstage/', }, ], diff --git a/microsite/src/pages/community/index.tsx b/microsite/src/pages/community/index.tsx index 9b10183ea4..dbc2c4b5e1 100644 --- a/microsite/src/pages/community/index.tsx +++ b/microsite/src/pages/community/index.tsx @@ -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{' '} - Roadie.io and{' '} - Frontside Software. - - ), - 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 Roadie.io. - - ), - 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 = () => { - - Community initiatives} - > - {communityInitiatives.map( - ({ title, content, link, label }, index) => ( - -

{content}

-
- ), - )} -
-
- { - return ( - -
- - Contributor Spotlight nomination}> - - - -
-
- ); -}; - -export default Nominate; diff --git a/microsite/src/pages/nominate/nominate.module.scss b/microsite/src/pages/nominate/nominate.module.scss deleted file mode 100644 index cdec103a8a..0000000000 --- a/microsite/src/pages/nominate/nominate.module.scss +++ /dev/null @@ -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; - } -} diff --git a/packages/backend-common/src/reading/GithubUrlReader.ts b/packages/backend-common/src/reading/GithubUrlReader.ts index 67f6a975c1..462821a20f 100644 --- a/packages/backend-common/src/reading/GithubUrlReader.ts +++ b/packages/backend-common/src/reading/GithubUrlReader.ts @@ -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)'; } diff --git a/packages/cli/package.json b/packages/cli/package.json index 5ca7d675d3..edf03167ec 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -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", diff --git a/packages/cli/src/lib/bundler/packageDetection.ts b/packages/cli/src/lib/bundler/packageDetection.ts index 3aa0955c06..0c357fe79e 100644 --- a/packages/cli/src/lib/bundler/packageDetection.ts +++ b/packages/cli/src/lib/bundler/packageDetection.ts @@ -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}] };`, ); } diff --git a/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts index 349b71319f..c46f82f4fc 100644 --- a/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts +++ b/packages/eslint-plugin/src/no-forbidden-package-imports.test.ts @@ -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({ diff --git a/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts index 6888d258cc..31e1674b76 100644 --- a/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts +++ b/packages/eslint-plugin/src/no-relative-monorepo-imports.test.ts @@ -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({ diff --git a/packages/eslint-plugin/src/no-undeclared-imports.test.ts b/packages/eslint-plugin/src/no-undeclared-imports.test.ts index e92d422057..29e2c3a87c 100644 --- a/packages/eslint-plugin/src/no-undeclared-imports.test.ts +++ b/packages/eslint-plugin/src/no-undeclared-imports.test.ts @@ -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({ diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx new file mode 100644 index 0000000000..7d62103727 --- /dev/null +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.test.tsx @@ -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:
root
, + }; + }, + }), + ], +}); + +describe('DefaultComponentsApi', () => { + it('should provide components', () => { + const tree = resolveAppTree( + 'app', + resolveAppNodeSpecs({ + features: [ + baseOverrides, + createExtensionOverrides({ + extensions: [ + createComponentExtension({ + ref: testRefA, + loader: { sync: () => () =>
test.a
}, + }), + ], + }), + ], + }), + ); + instantiateAppNodeTree(tree.root); + const api = DefaultComponentsApi.fromTree(tree); + + const ComponentA = api.getComponent(testRefA); + render(); + + 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: () => () =>
test.b
}, + }), + ], + }), + ], + }), + ); + instantiateAppNodeTree(tree.root); + const api = DefaultComponentsApi.fromTree(tree); + + const ComponentB1 = api.getComponent(testRefB1); + const ComponentB2 = api.getComponent(testRefB2); + + expect(ComponentB1).toBe(ComponentB2); + + render(); + + expect(screen.getByText('test.b')).toBeInTheDocument(); + }); +}); diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts similarity index 58% rename from packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts rename to packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts index 4d8bc47e24..572b6fc3c4 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/ComponentsApi.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/DefaultComponentsApi.ts @@ -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, ComponentType>; + #components: Map>; - constructor(components: Map, 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()); + return new DefaultComponentsApi(componentEntries ?? new Map()); + } + + constructor(components: Map) { this.#components = components; } getComponent(ref: ComponentRef): ComponentType { - 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}`); } diff --git a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts index f04059c54f..18604f15de 100644 --- a/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts +++ b/packages/frontend-app-api/src/apis/implementations/ComponentsApi/index.ts @@ -14,4 +14,4 @@ * limitations under the License. */ -export { DefaultComponentsApi } from './ComponentsApi'; +export { DefaultComponentsApi } from './DefaultComponentsApi'; diff --git a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts index 8fd9d44638..f056e7a49e 100644 --- a/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts +++ b/packages/frontend-app-api/src/tree/resolveAppNodeSpecs.ts @@ -31,17 +31,22 @@ import { toInternalExtension } from '../../../frontend-plugin-api/src/wiring/res /** @internal */ export function resolveAppNodeSpecs(options: { - features: FrontendFeature[]; - builtinExtensions: Extension[]; - parameters: Array; + features?: FrontendFeature[]; + builtinExtensions?: Extension[]; + parameters?: Array; forbidden?: Set; }): 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', ); diff --git a/packages/frontend-app-api/src/wiring/createApp.tsx b/packages/frontend-app-api/src/wiring/createApp.tsx index 635e36aace..591e1cbf25 100644 --- a/packages/frontend-app-api/src/wiring/createApp.tsx +++ b/packages/frontend-app-api/src/wiring/createApp.tsx @@ -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, any>(), - ); - factoryRegistry.register('static', { api: componentsApiRef, deps: {}, - factory: () => new DefaultComponentsApi(componentsMap), + factory: () => DefaultComponentsApi.fromTree(tree), }); factoryRegistry.register('static', { diff --git a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx index 2efa106750..a1231d8353 100644 --- a/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx +++ b/packages/frontend-plugin-api/src/extensions/createComponentExtension.tsx @@ -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; - + 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; + 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 => ( - - - - ), + impl: ExtensionComponent, }, }; }, diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 634e03dbfc..07b4f7d012 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -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; // (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, diff --git a/packages/integration/package.json b/packages/integration/package.json index 7e57293903..29a4273698 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -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", diff --git a/packages/integration/src/github/GithubIntegration.test.ts b/packages/integration/src/github/GithubIntegration.test.ts index d8e6020a64..70f1c4a16c 100644 --- a/packages/integration/src/github/GithubIntegration.test.ts +++ b/packages/integration/src/github/GithubIntegration.test.ts @@ -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', () => { diff --git a/packages/integration/src/github/GithubIntegration.ts b/packages/integration/src/github/GithubIntegration.ts index 50fc129ccf..880a2bed21 100644 --- a/packages/integration/src/github/GithubIntegration.ts +++ b/packages/integration/src/github/GithubIntegration.ts @@ -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'), + }; + } } /** diff --git a/packages/integration/src/index.ts b/packages/integration/src/index.ts index 5738da3da0..700d7dc67c 100644 --- a/packages/integration/src/index.ts +++ b/packages/integration/src/index.ts @@ -37,5 +37,6 @@ export type { ScmIntegration, ScmIntegrationsFactory, ScmIntegrationsGroup, + RateLimitInfo, } from './types'; export type { ScmIntegrationRegistry } from './registry'; diff --git a/packages/integration/src/types.ts b/packages/integration/src/types.ts index cb4b78eb7c..0f0fca942e 100644 --- a/packages/integration/src/types.ts +++ b/packages/integration/src/types.ts @@ -108,3 +108,12 @@ export interface ScmIntegrationsGroup { export type ScmIntegrationsFactory = (options: { config: Config; }) => ScmIntegrationsGroup; + +/** + * Encapsulates information about the RateLimit state + * + * @public + */ +export interface RateLimitInfo { + isRateLimited: boolean; +} diff --git a/packages/techdocs-cli/src/commands/serve/serve.ts b/packages/techdocs-cli/src/commands/serve/serve.ts index de681c2de4..27f432c22d 100644 --- a/packages/techdocs-cli/src/commands/serve/serve.ts +++ b/packages/techdocs-cli/src/commands/serve/serve.ts @@ -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, }); diff --git a/plugins/catalog-backend-module-github/src/lib/github.ts b/plugins/catalog-backend-module-github/src/lib/github.ts index cf6b400443..eff52ea98d 100644 --- a/plugins/catalog-backend-module-github/src/lib/github.ts +++ b/plugins/catalog-backend-module-github/src/lib/github.ts @@ -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 diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts index 504e5772dd..78c12d92cf 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.test.ts @@ -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; + let entityProviderConnection: EntityProviderConnection; + let logger: Logger; + let gitHubConfig: { host: string }; + let mockGetCredentials: jest.Mock; + 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', () => { diff --git a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts index cabb9e21ce..8387a91e3a 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubMultiOrgEntityProvider.ts @@ -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()); diff --git a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts index c054f26522..486ff47f1c 100644 --- a/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts +++ b/plugins/catalog-backend-module-github/src/providers/GithubOrgEntityProvider.test.ts @@ -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', () => { diff --git a/plugins/scaffolder-backend-module-github/api-report.md b/plugins/scaffolder-backend-module-github/api-report.md index d6a759dfa6..a588840585 100644 --- a/plugins/scaffolder-backend-module-github/api-report.md +++ b/plugins/scaffolder-backend-module-github/api-report.md @@ -375,6 +375,7 @@ export const createPublishGithubPullRequestAction: ( teamReviewers?: string[] | undefined; commitMessage?: string | undefined; update?: boolean | undefined; + forceFork?: boolean | undefined; }, JsonObject >; diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts index c5b1038a01..3fe4f62e04 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.test.ts @@ -687,4 +687,57 @@ describe('createPublishGithubPullRequestAction', () => { }); }); }); + + describe('with force fork', () => { + let input: GithubPullRequestActionInput; + let ctx: ActionContext; + + 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, + }); + }); + }); }); diff --git a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts index 2d8dc7ce8d..6a5c939905 100644 --- a/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts +++ b/plugins/scaffolder-backend-module-github/src/actions/githubPullRequest.ts @@ -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; diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index cc16791390..92e0d86167 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -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 >; diff --git a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts index 701863c7f6..607b1c6add 100644 --- a/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts +++ b/plugins/scaffolder-backend/src/lib/templating/SecureTemplater.ts @@ -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; /* Extra user-provided nunjucks globals */ templateGlobals?: Record; + 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( diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts index 26b249b76d..2a2c138708 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/fetch/template.ts @@ -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) { diff --git a/yarn.lock b/yarn.lock index 1c83813e10..6eff422ebd 100644 --- a/yarn.lock +++ b/yarn.lock @@ -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