diff --git a/.changeset/heavy-tools-hang.md b/.changeset/heavy-tools-hang.md
new file mode 100644
index 0000000000..91a843b0a7
--- /dev/null
+++ b/.changeset/heavy-tools-hang.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-permission-node': patch
+---
+
+New package containing common permission and authorization utilities for backend plugins. For more information, see the [authorization PRFC](https://github.com/backstage/backstage/pull/7761).
diff --git a/.changeset/many-waves-visit.md b/.changeset/many-waves-visit.md
new file mode 100644
index 0000000000..4f4711f371
--- /dev/null
+++ b/.changeset/many-waves-visit.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Providing an empty values array in an EntityFilter will now return no matches.
diff --git a/.changeset/shiny-insects-remember.md b/.changeset/shiny-insects-remember.md
new file mode 100644
index 0000000000..a26882b65d
--- /dev/null
+++ b/.changeset/shiny-insects-remember.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Fix bug where there was error log lines written when failing to `JSON.parse` things that were not `JSON` values.
diff --git a/.changeset/slow-olives-confess.md b/.changeset/slow-olives-confess.md
new file mode 100644
index 0000000000..68b2539157
--- /dev/null
+++ b/.changeset/slow-olives-confess.md
@@ -0,0 +1,7 @@
+---
+'@backstage/core-plugin-api': patch
+---
+
+Deprecated `register` option of `createPlugin` and the `outputs` methods of the plugin instance.
+
+Introduces the `featureFlags` property to define your feature flags instead.
diff --git a/.changeset/strong-seahorses-scream.md b/.changeset/strong-seahorses-scream.md
new file mode 100644
index 0000000000..bbd1d44378
--- /dev/null
+++ b/.changeset/strong-seahorses-scream.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Added Azure DevOps discovery processor
diff --git a/.changeset/thirty-fireants-occur.md b/.changeset/thirty-fireants-occur.md
new file mode 100644
index 0000000000..dc9348ac6e
--- /dev/null
+++ b/.changeset/thirty-fireants-occur.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-catalog-backend': patch
+---
+
+Detect a duplicate entities when adding locations through dry run
diff --git a/.changeset/thirty-tigers-refuse.md b/.changeset/thirty-tigers-refuse.md
new file mode 100644
index 0000000000..0797d36747
--- /dev/null
+++ b/.changeset/thirty-tigers-refuse.md
@@ -0,0 +1,6 @@
+---
+'@backstage/create-app': patch
+'@backstage/plugin-scaffolder-backend': patch
+---
+
+Bump gitbeaker to the latest version
diff --git a/.changeset/yellow-apes-brush.md b/.changeset/yellow-apes-brush.md
new file mode 100644
index 0000000000..06b33e97d1
--- /dev/null
+++ b/.changeset/yellow-apes-brush.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-api-docs': patch
+---
+
+Adjusted some styles in the OpenAPI definition, for elements which were barely readable in dark mode.
diff --git a/.changeset/young-hats-shop.md b/.changeset/young-hats-shop.md
new file mode 100644
index 0000000000..4db46cf9d9
--- /dev/null
+++ b/.changeset/young-hats-shop.md
@@ -0,0 +1,5 @@
+---
+'@backstage/plugin-cost-insights': patch
+---
+
+Supply featureFlags using featureFlag config option.
diff --git a/ADOPTERS.md b/ADOPTERS.md
index 3f32a22c23..c4c84d11be 100644
--- a/ADOPTERS.md
+++ b/ADOPTERS.md
@@ -71,3 +71,4 @@
| [Epic Games](https://www.epicgames.com) | [Brian Jung](https://github.com/brian-at-epic), [Jeff Goldian](https://github.com/jeffgoldian-Epic) | Developer Portal: Service Catalog, Documentation, Software Templates and more making our internal teams' lives easier! |
| [Globo](https://globo.com) | [Carlos Gusmão](https://github.com/caeugusmao), [Guilherme Vierno](https://github.com/vierno), [Denis Aoki](https://github.com/dnsaoki2), [Maycon Dionisio](https://github.com/MayconDionisio), | Reduce the friction of accessing the information engineers need about Globo's digital services through a coherent and centralized experience. |
| [QBE](https://www.qbe.com/) | [Daniel Steel](https://github.com/danielsteelqbe), [Pete Jespers](https://github.com/petejespersqbe) | Developer portal allowing our global teams to explore and create applications, documentation and cloud infrastructure easily and quickly 🚀 |
+| [LogMeIn](https://www.logmein.com) | [Lorenzo Orsatti](https://github.com/lorsatti) | Improve onboarding experience of new developers. Discover faster and painlessly developer documentation, API definitions and team information. Provide useful dev metrics in a central place. Provide easy-to-use templates for new services. |
diff --git a/contrib/chart/backstage/templates/ingress.yaml b/contrib/chart/backstage/templates/ingress.yaml
index 7231afda13..9340467800 100644
--- a/contrib/chart/backstage/templates/ingress.yaml
+++ b/contrib/chart/backstage/templates/ingress.yaml
@@ -1,7 +1,13 @@
{{- $frontendUrl := urlParse .Values.appConfig.app.baseUrl}}
{{- $backendUrl := urlParse .Values.appConfig.backend.baseUrl}}
{{- $lighthouseUrl := urlParse .Values.appConfig.lighthouse.baseUrl}}
+
+{{/* Determine the api type for the ingress */}}
+{{- if lt .Capabilities.KubeVersion.Minor "19" }}
apiVersion: networking.k8s.io/v1beta1
+{{- else if ge .Capabilities.KubeVersion.Minor "19" }}
+apiVersion: networking.k8s.io/v1
+{{- end }}
kind: Ingress
metadata:
name: {{ include "backstage.fullname" . }}-ingress
diff --git a/docs/integrations/azure/discovery.md b/docs/integrations/azure/discovery.md
new file mode 100644
index 0000000000..ad452f47af
--- /dev/null
+++ b/docs/integrations/azure/discovery.md
@@ -0,0 +1,49 @@
+---
+id: discovery
+title: Azure DevOps Discovery
+sidebar_label: Discovery
+# prettier-ignore
+description: Automatically discovering catalog entities from repositories in an Azure DevOps organization
+---
+
+The Azure DevOps integration has a special discovery processor for discovering
+catalog entities within an Azure DevOps. The processor will crawl the Azure
+DevOps organization and register entities matching the configured path. This can
+be useful as an alternative to static locations or manually adding things to the
+catalog.
+
+To use the discovery processor, you'll need a GitHub integration
+[set up](locations.md) with a `AZURE_TOKEN`. Then you can add a location target
+to the catalog configuration:
+
+```yaml
+catalog:
+ locations:
+ # Scan all repositories for a catalog-info.yaml in the root of the default branch
+ - type: azure-discovery
+ target: https://dev.azure.com/myorg/myproject
+ # Or use a custom pattern for a subset of all repositories with default repository
+ - type: azure-discovery
+ target: https://dev.azure.com/myorg/myproject/_git/service-*
+ # Or use a custom file format and location
+ - type: azure-discovery
+ target: https://dev.azure.com/myorg/myproject/_git/*?path=/src/*/catalog-info.yaml
+```
+
+Note the `azure-discovery` type, as this is not a regular `url` processor.
+
+When using a custom pattern, the target is composed of five parts:
+
+- The base instance URL, `https://dev.azure.com` in this case
+- The organization name which is required, `myorg` in this case
+- The project name which is required, `myproject` in this case
+- The repository blob to scan, which accepts \* wildcard tokens and must be
+ added after `_git/`. This can simply be `*` to scan all repositories in the
+ project.
+- The path within each repository to find the catalog YAML file. This will
+ usually be `/catalog-info.yaml`, `/src/*/catalog-info.yaml` or a similar
+ variation for catalog files stored in the root directory of each repository.
+
+_Note:_ the path parameter follows the same rules as the search on Azure DevOps
+web interface. For more details visit the
+[official search documentation](https://docs.microsoft.com/en-us/azure/devops/project/search/get-started-search?view=azure-devops)
diff --git a/docs/plugins/analytics.md b/docs/plugins/analytics.md
index 7adf2650df..ace9a89968 100644
--- a/docs/plugins/analytics.md
+++ b/docs/plugins/analytics.md
@@ -261,10 +261,13 @@ analytics events captured.
Use it like this:
```tsx
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
-import { analyticsApiRef } from '@backstage/core-plugin-api';
-import { MockAnalyticsApi, wrapInTestApp } from '@backstage/test-utils';
import { render, fireEvent, waitFor } from '@testing-library/react';
+import { analyticsApiRef } from '@backstage/core-plugin-api';
+import {
+ MockAnalyticsApi,
+ TestApiProvider,
+ wrapInTestApp,
+} from '@backstage/test-utils';
describe('SomeComponent', () => {
it('should capture event on click', () => {
@@ -274,9 +277,9 @@ describe('SomeComponent', () => {
// Render the component being tested
const { getByText } = render(
wrapInTestApp(
-
+
- ,
+ ,
),
);
diff --git a/microsite/sidebars.json b/microsite/sidebars.json
index a24d5897ab..89eb282d14 100644
--- a/microsite/sidebars.json
+++ b/microsite/sidebars.json
@@ -125,7 +125,11 @@
{
"type": "subcategory",
"label": "Azure",
- "ids": ["integrations/azure/locations", "integrations/azure/org"]
+ "ids": [
+ "integrations/azure/locations",
+ "integrations/azure/discovery",
+ "integrations/azure/org"
+ ]
},
{
"type": "subcategory",
diff --git a/mkdocs.yml b/mkdocs.yml
index 48df25afef..2eed377819 100644
--- a/mkdocs.yml
+++ b/mkdocs.yml
@@ -88,6 +88,7 @@ nav:
- Discovery: 'integrations/aws-s3/discovery.md'
- Azure:
- Locations: 'integrations/azure/locations.md'
+ - Discovery: 'integrations/azure/discovery.md'
- Org Data: 'integrations/azure/org.md'
- Bitbucket:
- Locations: 'integrations/bitbucket/locations.md'
diff --git a/packages/app/package.json b/packages/app/package.json
index 762a09fb47..4f6f6a2ea2 100644
--- a/packages/app/package.json
+++ b/packages/app/package.json
@@ -67,7 +67,7 @@
"devDependencies": {
"@backstage/test-utils": "^0.1.22",
"@rjsf/core": "^3.2.1",
- "@testing-library/cypress": "^7.0.1",
+ "@testing-library/cypress": "^8.0.2",
"@testing-library/jest-dom": "^5.10.1",
"@testing-library/react": "^11.2.5",
"@testing-library/user-event": "^13.1.8",
diff --git a/packages/app/src/components/catalog/EntityPage.test.tsx b/packages/app/src/components/catalog/EntityPage.test.tsx
index 089cb9c7cf..a6878cd28b 100644
--- a/packages/app/src/components/catalog/EntityPage.test.tsx
+++ b/packages/app/src/components/catalog/EntityPage.test.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { EntityLayout } from '@backstage/plugin-catalog';
import {
DefaultStarredEntitiesApi,
@@ -22,7 +21,11 @@ import {
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
import { githubActionsApiRef } from '@backstage/plugin-github-actions';
-import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiProvider,
+} from '@backstage/test-utils';
import React from 'react';
import { cicdContent } from './EntityPage';
@@ -45,22 +48,22 @@ describe('EntityPage Test', () => {
const mockedApi = {
listWorkflowRuns: jest.fn().mockResolvedValue([]),
- getWorkflow: jest.fn(),
- getWorkflowRun: jest.fn(),
- reRunWorkflow: jest.fn(),
- listJobsForWorkflowRun: jest.fn(),
- downloadJobLogsForWorkflowRun: jest.fn(),
- } as jest.Mocked;
-
- const apis = ApiRegistry.with(githubActionsApiRef, mockedApi).with(
- starredEntitiesApiRef,
- new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
- );
+ };
describe('cicdContent', () => {
it('Should render GitHub Actions View', async () => {
const rendered = await renderInTestApp(
-
+
@@ -68,7 +71,7 @@ describe('EntityPage Test', () => {
- ,
+ ,
);
expect(rendered.getByText('ExampleComponent')).toBeInTheDocument();
diff --git a/packages/backend/package.json b/packages/backend/package.json
index 4fb692f040..1cef99c6a0 100644
--- a/packages/backend/package.json
+++ b/packages/backend/package.json
@@ -52,7 +52,7 @@
"@backstage/plugin-tech-insights-node": "^0.1.0",
"@backstage/plugin-tech-insights-backend-module-jsonfc": "^0.1.0",
"@backstage/plugin-todo-backend": "^0.1.13",
- "@gitbeaker/node": "^30.2.0",
+ "@gitbeaker/node": "^34.6.0",
"@octokit/rest": "^18.5.3",
"azure-devops-node-api": "^11.0.1",
"dockerode": "^3.3.1",
diff --git a/packages/core-app-api/src/app/AppManager.test.tsx b/packages/core-app-api/src/app/AppManager.test.tsx
index 07401660e8..20872b26f0 100644
--- a/packages/core-app-api/src/app/AppManager.test.tsx
+++ b/packages/core-app-api/src/app/AppManager.test.tsx
@@ -349,6 +349,78 @@ describe('Integration Test', () => {
});
});
+ it('getFeatureFlags should return feature flags', async () => {
+ const storageFlags = new LocalStorageFeatureFlags();
+ jest.spyOn(storageFlags, 'registerFlag');
+
+ const apis = [
+ noOpAnalyticsApi,
+ createApiFactory({
+ api: featureFlagsApiRef,
+ deps: { configApi: configApiRef },
+ factory() {
+ return storageFlags;
+ },
+ }),
+ ];
+
+ const app = new AppManager({
+ apis,
+ defaultApis: [],
+ themes: [
+ {
+ id: 'light',
+ title: 'Light Theme',
+ variant: 'light',
+ Provider: ({ children }) => <>{children}>,
+ },
+ ],
+ icons,
+ plugins: [
+ createPlugin({
+ id: 'test',
+ featureFlags: [
+ {
+ name: 'foo',
+ },
+ ],
+ register: p => p.featureFlags.register('name'),
+ }),
+ ],
+ components,
+ configLoader: async () => [],
+ bindRoutes: ({ bind }) => {
+ bind(plugin1.externalRoutes, {
+ extRouteRef1: plugin1RouteRef,
+ extRouteRef2: plugin2RouteRef,
+ });
+ },
+ });
+
+ const Provider = app.getProvider();
+ const Router = app.getRouter();
+
+ await renderWithEffects(
+
+
+
+ } />
+ } />
+
+
+ ,
+ );
+
+ expect(storageFlags.registerFlag).toHaveBeenCalledWith({
+ name: 'name',
+ pluginId: 'test',
+ });
+ expect(storageFlags.registerFlag).toHaveBeenCalledWith({
+ name: 'foo',
+ pluginId: 'test',
+ });
+ });
+
it('should track route changes via analytics api', async () => {
const mockAnalyticsApi = new MockAnalyticsApi();
const apis = [createApiFactory(analyticsApiRef, mockAnalyticsApi)];
diff --git a/packages/core-app-api/src/app/AppManager.tsx b/packages/core-app-api/src/app/AppManager.tsx
index 950f57bb7d..5903a8c874 100644
--- a/packages/core-app-api/src/app/AppManager.tsx
+++ b/packages/core-app-api/src/app/AppManager.tsx
@@ -272,17 +272,26 @@ export class AppManager implements BackstageApp {
const featureFlagsApi = this.getApiHolder().get(featureFlagsApiRef)!;
for (const plugin of this.plugins.values()) {
- for (const output of plugin.output()) {
- switch (output.type) {
- case 'feature-flag': {
- featureFlagsApi.registerFlag({
- name: output.name,
- pluginId: plugin.getId(),
- });
- break;
+ if ('getFeatureFlags' in plugin) {
+ for (const flag of plugin.getFeatureFlags()) {
+ featureFlagsApi.registerFlag({
+ name: flag.name,
+ pluginId: plugin.getId(),
+ });
+ }
+ } else {
+ for (const output of plugin.output()) {
+ switch (output.type) {
+ case 'feature-flag': {
+ featureFlagsApi.registerFlag({
+ name: output.name,
+ pluginId: plugin.getId(),
+ });
+ break;
+ }
+ default:
+ break;
}
- default:
- break;
}
}
}
diff --git a/packages/core-plugin-api/api-report.md b/packages/core-plugin-api/api-report.md
index 27f3480930..560071e1fa 100644
--- a/packages/core-plugin-api/api-report.md
+++ b/packages/core-plugin-api/api-report.md
@@ -258,6 +258,7 @@ export type BackstagePlugin<
getId(): string;
output(): PluginOutput[];
getApis(): Iterable;
+ getFeatureFlags(): Iterable;
provide(extension: Extension): T;
routes: Routes;
externalRoutes: ExternalRoutes;
@@ -463,7 +464,7 @@ export type FeatureFlag = {
pluginId: string;
};
-// @public
+// @public @deprecated
export type FeatureFlagOutput = {
type: 'feature-flag';
name: string;
@@ -682,14 +683,20 @@ export type PluginConfig<
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
+ featureFlags?: PluginFeatureFlagConfig[];
};
// @public
+export type PluginFeatureFlagConfig = {
+ name: string;
+};
+
+// @public @deprecated
export type PluginHooks = {
featureFlags: FeatureFlagsHooks;
};
-// @public
+// @public @deprecated
export type PluginOutput = FeatureFlagOutput;
// @public
diff --git a/packages/core-plugin-api/src/plugin/Plugin.test.tsx b/packages/core-plugin-api/src/plugin/Plugin.test.tsx
new file mode 100644
index 0000000000..d3758543a1
--- /dev/null
+++ b/packages/core-plugin-api/src/plugin/Plugin.test.tsx
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2021 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 { createPlugin } from './Plugin';
+
+describe('Plugin Feature Flag', () => {
+ it('should be able to register and receive feature flags', () => {
+ expect(
+ createPlugin({
+ id: 'test',
+ featureFlags: [{ name: 'test' }],
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'test' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'blob' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ featureFlags: [{ name: 'test' }],
+ }).getFeatureFlags(),
+ ).toEqual([{ name: 'test' }, { name: 'blob' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ }).getFeatureFlags(),
+ ).toEqual([]);
+
+ /* deprecated tests */
+
+ expect(
+ createPlugin({
+ id: 'test',
+ featureFlags: [{ name: 'test' }],
+ }).output(),
+ ).toEqual([{ name: 'test', type: 'feature-flag' }]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ }).output(),
+ ).toEqual([{ name: 'blob', type: 'feature-flag' }]);
+ expect(
+ createPlugin({
+ id: 'test',
+ register({ featureFlags }) {
+ featureFlags.register('blob');
+ },
+ featureFlags: [{ name: 'test' }],
+ }).output(),
+ ).toEqual([
+ { name: 'test', type: 'feature-flag' },
+ { name: 'blob', type: 'feature-flag' },
+ ]);
+
+ expect(
+ createPlugin({
+ id: 'test',
+ }).output(),
+ ).toEqual([]);
+ });
+});
diff --git a/packages/core-plugin-api/src/plugin/Plugin.tsx b/packages/core-plugin-api/src/plugin/Plugin.tsx
index 52ca0e76e3..0fb7574cd7 100644
--- a/packages/core-plugin-api/src/plugin/Plugin.tsx
+++ b/packages/core-plugin-api/src/plugin/Plugin.tsx
@@ -21,6 +21,7 @@ import {
Extension,
AnyRoutes,
AnyExternalRoutes,
+ PluginFeatureFlagConfig,
} from './types';
import { AnyApiFactory } from '../apis';
@@ -44,6 +45,14 @@ export class PluginImpl<
return this.config.apis ?? [];
}
+ getFeatureFlags(): Iterable {
+ const registeredFlags = this.output()
+ .filter(({ type }) => type === 'feature-flag')
+ .map(({ name }) => ({ name }));
+
+ return registeredFlags;
+ }
+
get routes(): Routes {
return this.config.routes ?? ({} as Routes);
}
@@ -56,11 +65,18 @@ export class PluginImpl<
if (this.storedOutput) {
return this.storedOutput;
}
- if (!this.config.register) {
- return [];
+ const outputs = new Array();
+ this.storedOutput = outputs;
+
+ if (this.config.featureFlags) {
+ for (const flag of this.config.featureFlags) {
+ outputs.push({ type: 'feature-flag', name: flag.name });
+ }
}
- const outputs = new Array();
+ if (!this.config.register) {
+ return outputs;
+ }
this.config.register({
featureFlags: {
@@ -70,7 +86,6 @@ export class PluginImpl<
},
});
- this.storedOutput = outputs;
return this.storedOutput;
}
diff --git a/packages/core-plugin-api/src/plugin/index.ts b/packages/core-plugin-api/src/plugin/index.ts
index d3272607ef..4f7609fd9c 100644
--- a/packages/core-plugin-api/src/plugin/index.ts
+++ b/packages/core-plugin-api/src/plugin/index.ts
@@ -25,4 +25,5 @@ export type {
PluginConfig,
PluginHooks,
PluginOutput,
+ PluginFeatureFlagConfig,
} from './types';
diff --git a/packages/core-plugin-api/src/plugin/types.ts b/packages/core-plugin-api/src/plugin/types.ts
index aeb7037c51..0f8d3404d8 100644
--- a/packages/core-plugin-api/src/plugin/types.ts
+++ b/packages/core-plugin-api/src/plugin/types.ts
@@ -19,7 +19,7 @@ import { AnyApiFactory } from '../apis/system';
/**
* Replace with using {@link RouteRef}s.
- *
+ * @deprecated will be removed
* @public
*/
export type FeatureFlagOutput = {
@@ -31,6 +31,7 @@ export type FeatureFlagOutput = {
* {@link FeatureFlagOutput} type.
*
* @public
+ * @deprecated Use {@link BackstagePlugin.getFeatureFlags} instead.
*/
export type PluginOutput = FeatureFlagOutput;
@@ -71,13 +72,30 @@ export type BackstagePlugin<
ExternalRoutes extends AnyExternalRoutes = {},
> = {
getId(): string;
+ /**
+ * @deprecated use getFeatureFlags instead.
+ * */
output(): PluginOutput[];
getApis(): Iterable;
+ /**
+ * Returns all registered feature flags for this plugin.
+ */
+ getFeatureFlags(): Iterable;
provide(extension: Extension): T;
routes: Routes;
externalRoutes: ExternalRoutes;
};
+/**
+ * Plugin feature flag configuration.
+ *
+ * @public
+ */
+export type PluginFeatureFlagConfig = {
+ /** Feature flag name */
+ name: string;
+};
+
/**
* Plugin descriptor type.
*
@@ -89,14 +107,17 @@ export type PluginConfig<
> = {
id: string;
apis?: Iterable;
+ /** @deprecated use featureFlags property instead for defining feature flags */
register?(hooks: PluginHooks): void;
routes?: Routes;
externalRoutes?: ExternalRoutes;
+ featureFlags?: PluginFeatureFlagConfig[];
};
/**
* Holds hooks registered by the plugin.
*
+ * @deprecated - feature flags are now registered in plugin config under featureFlags
* @public
*/
export type PluginHooks = {
diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs
index 8f2de6a8ae..6f0726b0f8 100644
--- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs
+++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs
@@ -27,7 +27,7 @@
"@backstage/plugin-search-backend": "^{{version '@backstage/plugin-search-backend'}}",
"@backstage/plugin-search-backend-node": "^{{version '@backstage/plugin-search-backend-node'}}",
"@backstage/plugin-techdocs-backend": "^{{version '@backstage/plugin-techdocs-backend'}}",
- "@gitbeaker/node": "^30.2.0",
+ "@gitbeaker/node": "^34.6.0",
"@octokit/rest": "^18.5.3",
"dockerode": "^3.3.1",
"express": "^4.17.1",
diff --git a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
index 3907dc1007..43eaa6218c 100644
--- a/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
+++ b/packages/dev-utils/src/devApp/SidebarThemeSwitcher.test.tsx
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+
import { AppThemeApi, appThemeApiRef } from '@backstage/core-plugin-api';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { BackstageTheme } from '@backstage/theme';
import userEvent from '@testing-library/user-event';
import React from 'react';
@@ -24,7 +24,6 @@ import { SidebarThemeSwitcher } from './SidebarThemeSwitcher';
describe('SidebarThemeSwitcher', () => {
let appThemeApi: jest.Mocked;
- let apiRegistry: ApiRegistry;
beforeEach(() => {
appThemeApi = {
@@ -51,15 +50,13 @@ describe('SidebarThemeSwitcher', () => {
theme: {} as unknown as BackstageTheme,
},
]);
-
- apiRegistry = ApiRegistry.with(appThemeApiRef, appThemeApi);
});
it('should display current theme', async () => {
const { getByLabelText, getByRole, getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
const button = getByLabelText('Switch Theme');
@@ -76,9 +73,9 @@ describe('SidebarThemeSwitcher', () => {
it('should select different theme', async () => {
const { getByLabelText, getByRole, getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
const button = getByLabelText('Switch Theme');
diff --git a/packages/storybook/.storybook/apis.js b/packages/storybook/.storybook/apis.js
index 3b7f6119ef..2750324c17 100644
--- a/packages/storybook/.storybook/apis.js
+++ b/packages/storybook/.storybook/apis.js
@@ -1,6 +1,5 @@
import {
AlertApiForwarder,
- ApiRegistry,
ErrorAlerter,
ErrorApiForwarder,
GithubAuth,
@@ -27,78 +26,57 @@ import {
configApiRef,
} from '@backstage/core-plugin-api';
-const builder = ApiRegistry.builder();
-
-builder.add(configApiRef, new ConfigReader({}));
-
-const alertApi = builder.add(alertApiRef, new AlertApiForwarder());
-
-builder.add(errorApiRef, new ErrorAlerter(alertApi, new ErrorApiForwarder()));
-
-builder.add(identityApiRef, {
+const configApi = new ConfigReader({});
+const alertApi = new AlertApiForwarder();
+const errorApi = new ErrorAlerter(alertApi, new ErrorApiForwarder());
+const identityApi = {
getUserId: () => 'guest',
getProfile: () => ({ email: 'guest@example.com' }),
getIdToken: () => undefined,
signOut: async () => {},
+};
+const oauthRequestApi = new OAuthRequestManager();
+const googleAuthApi = GoogleAuth.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
+});
+const githubAuthApi = GithubAuth.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
+});
+const gitlabAuthApi = GitlabAuth.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
+});
+const oktaAuthApi = OktaAuth.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
+});
+const auth0AuthApi = Auth0Auth.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
+});
+const oauth2Api = OAuth2.create({
+ apiOrigin: 'http://localhost:7007',
+ basePath: '/auth/',
+ oauthRequestApi,
});
-const oauthRequestApi = builder.add(
- oauthRequestApiRef,
- new OAuthRequestManager(),
-);
-
-builder.add(
- googleAuthApiRef,
- GoogleAuth.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-builder.add(
- githubAuthApiRef,
- GithubAuth.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-builder.add(
- gitlabAuthApiRef,
- GitlabAuth.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-builder.add(
- oktaAuthApiRef,
- OktaAuth.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-builder.add(
- auth0AuthApiRef,
- Auth0Auth.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-builder.add(
- oauth2ApiRef,
- OAuth2.create({
- apiOrigin: 'http://localhost:7007',
- basePath: '/auth/',
- oauthRequestApi,
- }),
-);
-
-export const apis = builder.build();
+export const apis = [
+ [configApiRef, configApi],
+ [alertApiRef, alertApi],
+ [errorApiRef, errorApi],
+ [identityApiRef, identityApi],
+ [oauthRequestApiRef, oauthRequestApi],
+ [googleAuthApiRef, googleAuthApi],
+ [githubAuthApiRef, githubAuthApi],
+ [gitlabAuthApiRef, gitlabAuthApi],
+ [oktaAuthApiRef, oktaAuthApi],
+ [auth0AuthApiRef, auth0AuthApi],
+ [oauth2ApiRef, oauth2Api],
+];
diff --git a/packages/storybook/.storybook/preview.js b/packages/storybook/.storybook/preview.js
index 6186b82ca5..2b1a4af329 100644
--- a/packages/storybook/.storybook/preview.js
+++ b/packages/storybook/.storybook/preview.js
@@ -6,17 +6,17 @@ import { useDarkMode } from 'storybook-dark-mode';
import { apis } from './apis';
import { Content, AlertDisplay } from '@backstage/core-components';
-import { ApiProvider } from '@backstage/core-app-api';
+import { TestApiProvider } from '@backstage/test-utils';
addDecorator(story => (
-
+
{story()}
-
+
));
addParameters({
diff --git a/packages/techdocs-cli/.snyk b/packages/techdocs-cli/.snyk
new file mode 100644
index 0000000000..cfb30e5aa7
--- /dev/null
+++ b/packages/techdocs-cli/.snyk
@@ -0,0 +1,17 @@
+# Snyk (https://snyk.io) policy file, patches or ignores known vulnerabilities.
+version: v1.22.1
+# ignores vulnerabilities until expiry date; change duration by modifying expiry date
+ignore:
+ SNYK-JS-BROWSERSLIST-1090194:
+ - '*':
+ reason: Developer tools are not a valid target for ReDoS attacks
+ expires: 2022-05-20T00:00:00.000Z
+ created: 2021-11-20T00:00:00.000Z
+
+ SNYK-JS-IMMER-1540542:
+ - '*':
+ reason: Prototype pollution is not an effective attack against a CLI as it already executes arbitrary code
+ expires: 2022-05-20T00:00:00.000Z
+ created: 2021-11-20T00:00:00.000Z
+
+patch: {}
diff --git a/packages/test-utils/src/testUtils/appWrappers.test.tsx b/packages/test-utils/src/testUtils/appWrappers.test.tsx
index 25809912ec..39e5da3df2 100644
--- a/packages/test-utils/src/testUtils/appWrappers.test.tsx
+++ b/packages/test-utils/src/testUtils/appWrappers.test.tsx
@@ -14,7 +14,6 @@
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import {
createExternalRouteRef,
createRouteRef,
@@ -29,6 +28,7 @@ import React, { useEffect } from 'react';
import { Route, Routes } from 'react-router';
import { MockErrorApi } from './apis';
import { renderInTestApp, wrapInTestApp } from './appWrappers';
+import { TestApiProvider } from './TestApiProvider';
describe('wrapInTestApp', () => {
it('should provide routing and warn about missing act()', async () => {
@@ -111,9 +111,9 @@ describe('wrapInTestApp', () => {
};
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('foo')).toBeInTheDocument();
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
index 7b0ffac58e..32a6ec0f77 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiDefinitionCard.test.tsx
@@ -16,13 +16,12 @@
import { ApiEntity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { OpenApiDefinitionWidget } from '../OpenApiDefinitionWidget';
import { ApiDefinitionCard } from './ApiDefinitionCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const apiDocsConfig: jest.Mocked = {
@@ -31,10 +30,10 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(apiDocsConfigRef, apiDocsConfig);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx b/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
index 450e9e57ac..5476857a06 100644
--- a/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
+++ b/plugins/api-docs/src/components/ApiDefinitionCard/ApiTypeTitle.test.tsx
@@ -15,11 +15,10 @@
*/
import { ApiEntity } from '@backstage/catalog-model';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { ApiTypeTitle } from './ApiTypeTitle';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const apiDocsConfig: jest.Mocked = {
@@ -28,10 +27,10 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(apiDocsConfigRef, apiDocsConfig);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
index cf40e5160c..3629110d47 100644
--- a/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
+++ b/plugins/api-docs/src/components/ApiExplorerPage/ApiExplorerPage.test.tsx
@@ -15,11 +15,7 @@
*/
import { Entity, RELATION_MEMBER_OF } from '@backstage/catalog-model';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ConfigReader } from '@backstage/core-app-api';
import { TableColumn, TableProps } from '@backstage/core-components';
import {
ConfigApi,
@@ -34,7 +30,11 @@ import {
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
-import { MockStorageApi, wrapInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ TestApiProvider,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import DashboardIcon from '@material-ui/icons/Dashboard';
import { render } from '@testing-library/react';
import React from 'react';
@@ -88,8 +88,8 @@ describe('ApiCatalogPage', () => {
const renderWrapped = (children: React.ReactNode) =>
render(
wrapInTestApp(
- {
new DefaultStarredEntitiesApi({ storageApi }),
],
[apiDocsConfigRef, apiDocsConfig],
- ])}
+ ]}
>
{children}
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
index ac09b717f7..5e7b22524a 100644
--- a/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ConsumedApisCard.test.tsx
@@ -21,12 +21,11 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { ConsumedApisCard } from './ConsumedApisCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const apiDocsConfig: jest.Mocked = {
@@ -43,13 +42,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi).with(
- apiDocsConfigRef,
- apiDocsConfig,
- );
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
index ac833251e3..4102bebf0e 100644
--- a/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/HasApisCard.test.tsx
@@ -21,12 +21,11 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { HasApisCard } from './HasApisCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const apiDocsConfig: jest.Mocked = {
@@ -43,13 +42,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi).with(
- apiDocsConfigRef,
- apiDocsConfig,
- );
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
index f2719d0f4b..b29075aee9 100644
--- a/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
+++ b/plugins/api-docs/src/components/ApisCards/ProvidedApisCard.test.tsx
@@ -21,12 +21,11 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ApiDocsConfig, apiDocsConfigRef } from '../../config';
import { ProvidedApisCard } from './ProvidedApisCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const apiDocsConfig: jest.Mocked = {
@@ -43,13 +42,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi).with(
- apiDocsConfigRef,
- apiDocsConfig,
- );
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
index 274962c346..06022eec69 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ConsumingComponentsCard.test.tsx
@@ -21,11 +21,10 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ConsumingComponentsCard } from './ConsumingComponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -39,10 +38,10 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
index 879944749a..275338d2f9 100644
--- a/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
+++ b/plugins/api-docs/src/components/ComponentsCards/ProvidingComponentsCard.test.tsx
@@ -21,11 +21,10 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ProvidingComponentsCard } from './ProvidingComponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -39,10 +38,10 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx
index 4a74b238f7..8d0accb254 100644
--- a/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx
+++ b/plugins/api-docs/src/components/OpenApiDefinitionWidget/OpenApiDefinition.tsx
@@ -61,12 +61,13 @@ const useStyles = makeStyles(theme => ({
[`& .opblock .opblock-summary-operation-id,
.opblock .opblock-summary-path,
.opblock .opblock-summary-path__deprecated,
- .opblock .opblock-section-header h4,
+ .opblock h4,
+ .opblock h5,
+ .opblock a,
+ .opblock li,
.parameter__name,
.response-col_status,
.response-col_links,
- .responses-inner h4,
- .responses-inner h5,
.opblock-section-header .btn,
.tab li,
.info li,
@@ -89,6 +90,7 @@ const useStyles = makeStyles(theme => ({
color: theme.palette.text.disabled,
},
[`& .opblock-description-wrapper p,
+ .opblock-description-wrapper li,
.opblock-external-docs-wrapper p,
.opblock-title_normal p,
.response-control-media-type__accept-message,
diff --git a/plugins/badges/src/components/EntityBadgesDialog.test.tsx b/plugins/badges/src/components/EntityBadgesDialog.test.tsx
index 1f5ee94e04..ea44eb20fc 100644
--- a/plugins/badges/src/components/EntityBadgesDialog.test.tsx
+++ b/plugins/badges/src/components/EntityBadgesDialog.test.tsx
@@ -16,12 +16,11 @@
import React from 'react';
import { Entity } from '@backstage/catalog-model';
-import { renderWithEffects } from '@backstage/test-utils';
+import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
import { BadgesApi, badgesApiRef } from '../api';
import { EntityBadgesDialog } from './EntityBadgesDialog';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { ErrorApi, errorApiRef } from '@backstage/core-plugin-api';
describe('EntityBadgesDialog', () => {
@@ -42,16 +41,16 @@ describe('EntityBadgesDialog', () => {
const mockEntity = { metadata: { name: 'mock' } } as Entity;
const rendered = await renderWithEffects(
-
- ,
+ ,
);
await expect(
diff --git a/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.test.tsx b/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.test.tsx
index a206e28de6..82faa3e39c 100644
--- a/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.test.tsx
+++ b/plugins/bitrise/src/components/BitriseBuildsTableComponent/BitriseBuildsTableComponent.test.tsx
@@ -21,14 +21,11 @@ import { setupServer } from 'msw/node';
import {
setupRequestMockHandlers,
renderInTestApp,
+ TestApiRegistry,
} from '@backstage/test-utils';
import { useBitriseBuilds } from '../../hooks/useBitriseBuilds';
import { BitriseBuildsTable } from './BitriseBuildsTableComponent';
-import {
- ApiProvider,
- ApiRegistry,
- UrlPatternDiscovery,
-} from '@backstage/core-app-api';
+import { ApiProvider, UrlPatternDiscovery } from '@backstage/core-app-api';
jest.mock('../../hooks/useBitriseBuilds', () => ({
useBitriseBuilds: jest.fn(),
@@ -40,10 +37,13 @@ describe('BitriseBuildsFetchComponent', () => {
setupRequestMockHandlers(server);
const mockBaseUrl = 'http://backstage:9191';
const discoveryApi = UrlPatternDiscovery.compile(mockBaseUrl);
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
beforeEach(() => {
- apis = ApiRegistry.with(bitriseApiRef, new BitriseClientApi(discoveryApi));
+ apis = TestApiRegistry.from([
+ bitriseApiRef,
+ new BitriseClientApi(discoveryApi),
+ ]);
});
it('should display `no records` message if there are no builds', async () => {
diff --git a/plugins/catalog-backend/api-report.md b/plugins/catalog-backend/api-report.md
index 5008cf02f1..7363a803c8 100644
--- a/plugins/catalog-backend/api-report.md
+++ b/plugins/catalog-backend/api-report.md
@@ -174,6 +174,26 @@ export class AwsS3DiscoveryProcessor implements CatalogProcessor {
): Promise;
}
+// Warning: (ae-missing-release-tag) "AzureDevOpsDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
+//
+// @public
+export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor {
+ constructor(options: { integrations: ScmIntegrations; logger: Logger_2 });
+ // (undocumented)
+ static fromConfig(
+ config: Config,
+ options: {
+ logger: Logger_2;
+ },
+ ): AzureDevOpsDiscoveryProcessor;
+ // (undocumented)
+ readLocation(
+ location: LocationSpec,
+ _optional: boolean,
+ emit: CatalogProcessorEmit,
+ ): Promise;
+}
+
// Warning: (ae-missing-release-tag) "BitbucketDiscoveryProcessor" is exported by the package, but it is missing a release tag (@alpha, @beta, @public, or @internal)
//
// @public (undocumented)
diff --git a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts
new file mode 100644
index 0000000000..ab053e8748
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.test.ts
@@ -0,0 +1,277 @@
+/*
+ * Copyright 2021 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 { getVoidLogger } from '@backstage/backend-common';
+import { LocationSpec } from '@backstage/catalog-model';
+import { ConfigReader } from '@backstage/config';
+import { codeSearch } from './azure';
+import {
+ AzureDevOpsDiscoveryProcessor,
+ parseUrl,
+} from './AzureDevOpsDiscoveryProcessor';
+
+jest.mock('./azure');
+const mockCodeSearch = codeSearch as jest.MockedFunction;
+
+describe('AzureDevOpsDiscoveryProcessor', () => {
+ describe('parseUrl', () => {
+ it('parses well formed URLs', () => {
+ expect(parseUrl('https://dev.azure.com/my-org/my-proj')).toEqual({
+ baseUrl: 'https://dev.azure.com',
+ org: 'my-org',
+ project: 'my-proj',
+ repo: '',
+ catalogPath: '/catalog-info.yaml',
+ });
+
+ expect(
+ parseUrl(
+ 'https://dev.azure.com/spotify/engineering/_git/backstage?path=/catalog.yaml',
+ ),
+ ).toEqual({
+ baseUrl: 'https://dev.azure.com',
+ org: 'spotify',
+ project: 'engineering',
+ repo: 'backstage',
+ catalogPath: '/catalog.yaml',
+ });
+
+ expect(
+ parseUrl(
+ 'https://azuredevops.mycompany.com/spotify/engineering/_git/backstage?path=/src/*/catalog.yaml',
+ ),
+ ).toEqual({
+ baseUrl: 'https://azuredevops.mycompany.com',
+ org: 'spotify',
+ project: 'engineering',
+ repo: 'backstage',
+ catalogPath: '/src/*/catalog.yaml',
+ });
+ });
+
+ it('throws on incorrectly formed URLs', () => {
+ expect(() => parseUrl('https://dev.azure.com')).toThrow();
+ expect(() => parseUrl('https://dev.azure.com//')).toThrow();
+ expect(() => parseUrl('https://dev.azure.com//foo')).toThrow();
+ });
+ });
+
+ describe('reject unrelated entries', () => {
+ it('rejects unknown types', async () => {
+ const processor = AzureDevOpsDiscoveryProcessor.fromConfig(
+ new ConfigReader({
+ integrations: {
+ azure: [{ host: 'dev.azure.com', token: 'blob' }],
+ },
+ }),
+ { logger: getVoidLogger() },
+ );
+ const location: LocationSpec = {
+ type: 'not-azure-discovery',
+ target: 'https://dev.azure.com',
+ };
+ await expect(
+ processor.readLocation(location, false, () => {}),
+ ).resolves.toBeFalsy();
+ });
+ });
+
+ it('rejects unknown targets', async () => {
+ const processor = AzureDevOpsDiscoveryProcessor.fromConfig(
+ new ConfigReader({
+ integrations: {
+ github: [
+ { host: 'dev.azure.com', token: 'blob' },
+ { host: 'azure.myorg.com', token: 'blob' },
+ ],
+ },
+ }),
+ { logger: getVoidLogger() },
+ );
+ const location: LocationSpec = {
+ type: 'azure-discovery',
+ target: 'https://not.azure.com/org/project',
+ };
+ await expect(
+ processor.readLocation(location, false, () => {}),
+ ).rejects.toThrow(
+ /There is no Azure integration that matches https:\/\/not.azure.com\/org\/project. Please add a configuration entry for it under integrations.azure/,
+ );
+ });
+
+ describe('handles repositories', () => {
+ const processor = AzureDevOpsDiscoveryProcessor.fromConfig(
+ new ConfigReader({
+ integrations: {
+ github: [{ host: 'dev.azure.com', token: 'blob' }],
+ },
+ }),
+ { logger: getVoidLogger() },
+ );
+
+ beforeEach(() => {
+ mockCodeSearch.mockClear();
+ });
+
+ it('output all locations found on from code search', async () => {
+ const location: LocationSpec = {
+ type: 'azure-discovery',
+ target: 'https://dev.azure.com/shopify/engineering',
+ };
+ mockCodeSearch.mockResolvedValueOnce([
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/src/catalog-info.yaml',
+ repository: {
+ name: 'ios-app',
+ },
+ },
+ ]);
+ const emitter = jest.fn();
+
+ await processor.readLocation(location, false, emitter);
+
+ expect(mockCodeSearch).toHaveBeenCalledWith(
+ { host: 'dev.azure.com' },
+ 'shopify',
+ 'engineering',
+ '',
+ '/catalog-info.yaml',
+ );
+ expect(emitter).toHaveBeenCalledTimes(2);
+ expect(emitter).toHaveBeenCalledWith({
+ type: 'location',
+ location: {
+ type: 'url',
+ target:
+ 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/catalog-info.yaml',
+ },
+ optional: true,
+ });
+ expect(emitter).toHaveBeenCalledWith({
+ type: 'location',
+ location: {
+ type: 'url',
+ target:
+ 'https://dev.azure.com/shopify/engineering/_git/ios-app?path=/src/catalog-info.yaml',
+ },
+ optional: true,
+ });
+ });
+
+ it('output single locations from code search', async () => {
+ const location: LocationSpec = {
+ type: 'azure-discovery',
+ target: 'https://dev.azure.com/shopify/engineering/_git/backstage',
+ };
+ mockCodeSearch.mockResolvedValueOnce([
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ ]);
+ const emitter = jest.fn();
+
+ await processor.readLocation(location, false, emitter);
+
+ expect(mockCodeSearch).toHaveBeenCalledWith(
+ { host: 'dev.azure.com' },
+ 'shopify',
+ 'engineering',
+ 'backstage',
+ '/catalog-info.yaml',
+ );
+ expect(emitter).toHaveBeenCalledTimes(1);
+ expect(emitter).toHaveBeenCalledWith({
+ type: 'location',
+ location: {
+ type: 'url',
+ target:
+ 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/catalog-info.yaml',
+ },
+ optional: true,
+ });
+ });
+
+ it('output single locations with different file name from code search', async () => {
+ const location: LocationSpec = {
+ type: 'azure-discovery',
+ target:
+ 'https://dev.azure.com/shopify/engineering?path=/src/*/catalog.yaml',
+ };
+ mockCodeSearch.mockResolvedValueOnce([
+ {
+ fileName: 'catalog.yaml',
+ path: '/src/main/catalog.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ ]);
+ const emitter = jest.fn();
+
+ await processor.readLocation(location, false, emitter);
+
+ expect(mockCodeSearch).toHaveBeenCalledWith(
+ { host: 'dev.azure.com' },
+ 'shopify',
+ 'engineering',
+ '',
+ '/src/*/catalog.yaml',
+ );
+ expect(emitter).toHaveBeenCalledTimes(1);
+ expect(emitter).toHaveBeenCalledWith({
+ type: 'location',
+ location: {
+ type: 'url',
+ target:
+ 'https://dev.azure.com/shopify/engineering/_git/backstage?path=/src/main/catalog.yaml',
+ },
+ optional: true,
+ });
+ });
+
+ it('output nothing when code search does not find anything', async () => {
+ const location: LocationSpec = {
+ type: 'azure-discovery',
+ target: 'https://dev.azure.com/shopify/engineering/_git/backstage',
+ };
+ mockCodeSearch.mockResolvedValueOnce([]);
+ const emitter = jest.fn();
+
+ await processor.readLocation(location, false, emitter);
+
+ expect(mockCodeSearch).toHaveBeenCalledWith(
+ { host: 'dev.azure.com' },
+ 'shopify',
+ 'engineering',
+ 'backstage',
+ '/catalog-info.yaml',
+ );
+ expect(emitter).not.toHaveBeenCalled();
+ });
+ });
+});
diff --git a/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts
new file mode 100644
index 0000000000..58ee2155e9
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/AzureDevOpsDiscoveryProcessor.ts
@@ -0,0 +1,151 @@
+/*
+ * Copyright 2021 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 { LocationSpec } from '@backstage/catalog-model';
+import { Config } from '@backstage/config';
+import { ScmIntegrations } from '@backstage/integration';
+import { Logger } from 'winston';
+import * as results from './results';
+import { CatalogProcessor, CatalogProcessorEmit } from './types';
+import { codeSearch } from './azure';
+
+/**
+ * Extracts repositories out of an Azure DevOps org.
+ *
+ * The following will create locations for all projects which have a catalog-info.yaml
+ * on the default branch. The first is shorthand for the second.
+ *
+ * target: "https://dev.azure.com/org/project"
+ * or
+ * target: https://dev.azure.com/org/project?path=/catalog-info.yaml
+ *
+ * You may also explicitly specify a single repo:
+ *
+ * target: https://dev.azure.com/org/project/_git/repo
+ **/
+export class AzureDevOpsDiscoveryProcessor implements CatalogProcessor {
+ private readonly integrations: ScmIntegrations;
+ private readonly logger: Logger;
+
+ static fromConfig(config: Config, options: { logger: Logger }) {
+ const integrations = ScmIntegrations.fromConfig(config);
+
+ return new AzureDevOpsDiscoveryProcessor({
+ ...options,
+ integrations,
+ });
+ }
+
+ constructor(options: { integrations: ScmIntegrations; logger: Logger }) {
+ this.integrations = options.integrations;
+ this.logger = options.logger;
+ }
+
+ async readLocation(
+ location: LocationSpec,
+ _optional: boolean,
+ emit: CatalogProcessorEmit,
+ ): Promise {
+ if (location.type !== 'azure-discovery') {
+ return false;
+ }
+
+ const azureConfig = this.integrations.azure.byUrl(location.target)?.config;
+ if (!azureConfig) {
+ throw new Error(
+ `There is no Azure integration that matches ${location.target}. Please add a configuration entry for it under integrations.azure`,
+ );
+ }
+
+ const { baseUrl, org, project, repo, catalogPath } = parseUrl(
+ location.target,
+ );
+ this.logger.info(
+ `Reading Azure DevOps repositories from ${location.target}`,
+ );
+
+ const files = await codeSearch(
+ azureConfig,
+ org,
+ project,
+ repo,
+ catalogPath,
+ );
+
+ this.logger.debug(
+ `Found ${files.length} files in Azure DevOps from ${location.target}.`,
+ );
+
+ for (const file of files) {
+ emit(
+ results.location(
+ {
+ type: 'url',
+ target: `${baseUrl}/${org}/${project}/_git/${file.repository.name}?path=${file.path}`,
+ },
+ // Not all locations may actually exist, since the user defined them as a wildcard pattern.
+ // Thus, we emit them as optional and let the downstream processor find them while not outputting
+ // an error if it couldn't.
+ true,
+ ),
+ );
+ }
+
+ return true;
+ }
+}
+
+/**
+ * parseUrl extracts segments from the Azure DevOps URL.
+ **/
+export function parseUrl(urlString: string): {
+ baseUrl: string;
+ org: string;
+ project: string;
+ repo: string;
+ catalogPath: string;
+} {
+ const url = new URL(urlString);
+ const path = url.pathname.substr(1).split('/');
+
+ const catalogPath = url.searchParams.get('path') || '/catalog-info.yaml';
+
+ if (path.length === 2 && path[0].length && path[1].length) {
+ return {
+ baseUrl: url.origin,
+ org: decodeURIComponent(path[0]),
+ project: decodeURIComponent(path[1]),
+ repo: '',
+ catalogPath,
+ };
+ } else if (
+ path.length === 4 &&
+ path[0].length &&
+ path[1].length &&
+ path[2].length &&
+ path[3].length
+ ) {
+ return {
+ baseUrl: url.origin,
+ org: decodeURIComponent(path[0]),
+ project: decodeURIComponent(path[1]),
+ repo: decodeURIComponent(path[3]),
+ catalogPath,
+ };
+ }
+
+ throw new Error(`Failed to parse ${urlString}`);
+}
diff --git a/plugins/catalog-backend/src/ingestion/processors/azure/azure.test.ts b/plugins/catalog-backend/src/ingestion/processors/azure/azure.test.ts
new file mode 100644
index 0000000000..67cc120bab
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/azure/azure.test.ts
@@ -0,0 +1,232 @@
+/*
+ * Copyright 2021 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 { setupServer } from 'msw/node';
+import { setupRequestMockHandlers } from '@backstage/test-utils';
+import { rest } from 'msw';
+import { codeSearch, CodeSearchResponse } from './azure';
+
+describe('azure', () => {
+ const server = setupServer();
+ setupRequestMockHandlers(server);
+
+ describe('codeSearch', () => {
+ it('returns empty when nothing is found', async () => {
+ const response: CodeSearchResponse = { count: 0, results: [] };
+
+ server.use(
+ rest.post(
+ `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`,
+ (req, res, ctx) => {
+ expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
+ expect(req.body).toEqual({
+ searchText: 'path:/catalog-info.yaml repo:*',
+ $skip: 0,
+ $top: 1000,
+ });
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await expect(
+ codeSearch(
+ { host: 'dev.azure.com', token: 'ABC' },
+ 'shopify',
+ 'engineering',
+ '',
+ '/catalog-info.yaml',
+ ),
+ ).resolves.toEqual([]);
+ });
+ });
+
+ it('returns entries when request matches some files', async () => {
+ const response: CodeSearchResponse = {
+ count: 2,
+ results: [
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'ios-app',
+ },
+ },
+ ],
+ };
+
+ server.use(
+ rest.post(
+ `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`,
+ (req, res, ctx) => {
+ expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
+ expect(req.body).toEqual({
+ searchText: 'path:/catalog-info.yaml repo:*',
+ $skip: 0,
+ $top: 1000,
+ });
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await expect(
+ codeSearch(
+ { host: 'dev.azure.com', token: 'ABC' },
+ 'shopify',
+ 'engineering',
+ '',
+ '/catalog-info.yaml',
+ ),
+ ).resolves.toEqual(response.results);
+ });
+
+ it('searches in specific repo if parameter is set', async () => {
+ const response: CodeSearchResponse = {
+ count: 1,
+ results: [
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ ],
+ };
+
+ server.use(
+ rest.post(
+ `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`,
+ (req, res, ctx) => {
+ expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
+ expect(req.body).toEqual({
+ searchText: 'path:/catalog-info.yaml repo:backstage',
+ $skip: 0,
+ $top: 1000,
+ });
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await expect(
+ codeSearch(
+ { host: 'dev.azure.com', token: 'ABC' },
+ 'shopify',
+ 'engineering',
+ 'backstage',
+ '/catalog-info.yaml',
+ ),
+ ).resolves.toEqual(response.results);
+ });
+
+ it('can search using onpremise api', async () => {
+ const response: CodeSearchResponse = {
+ count: 1,
+ results: [
+ {
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ },
+ ],
+ };
+
+ server.use(
+ rest.post(
+ `https://azuredevops.mycompany.com/shopify/engineering/_apis/search/codesearchresults`,
+ (req, res, ctx) => {
+ expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
+ expect(req.body).toEqual({
+ searchText: 'path:/catalog-info.yaml repo:*',
+ $skip: 0,
+ $top: 1000,
+ });
+ return res(ctx.json(response));
+ },
+ ),
+ );
+
+ await expect(
+ codeSearch(
+ { host: 'azuredevops.mycompany.com', token: 'ABC' },
+ 'shopify',
+ 'engineering',
+ '',
+ '/catalog-info.yaml',
+ ),
+ ).resolves.toEqual(response.results);
+ });
+
+ it('searches multiple pages if response contains many items', async () => {
+ const totalCount = 2401;
+ const generateItems = (count: number) => {
+ return Array.from(Array(count).keys()).map(_ => ({
+ fileName: 'catalog-info.yaml',
+ path: '/catalog-info.yaml',
+ repository: {
+ name: 'backstage',
+ },
+ }));
+ };
+
+ server.use(
+ rest.post(
+ `https://almsearch.dev.azure.com/shopify/engineering/_apis/search/codesearchresults`,
+ (req, res, ctx) => {
+ expect(req.headers.get('Authorization')).toBe('Basic OkFCQw==');
+ expect(req.body).toMatchObject({
+ searchText: 'path:/catalog-info.yaml repo:backstage',
+ $top: 1000,
+ });
+
+ const body = req.body as { $skip: number; $top: number };
+ const countItemsToReturn =
+ body.$top + body.$skip > totalCount
+ ? totalCount - body.$skip
+ : body.$top;
+
+ return res(
+ ctx.json({
+ count: totalCount,
+ results: generateItems(countItemsToReturn),
+ }),
+ );
+ },
+ ),
+ );
+
+ await expect(
+ codeSearch(
+ { host: 'dev.azure.com', token: 'ABC' },
+ 'shopify',
+ 'engineering',
+ 'backstage',
+ '/catalog-info.yaml',
+ ),
+ ).resolves.toHaveLength(totalCount);
+ });
+});
diff --git a/plugins/catalog-backend/src/ingestion/processors/azure/azure.ts b/plugins/catalog-backend/src/ingestion/processors/azure/azure.ts
new file mode 100644
index 0000000000..0c7b17483a
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/azure/azure.ts
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2021 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 fetch from 'cross-fetch';
+import {
+ AzureIntegrationConfig,
+ getAzureRequestOptions,
+} from '@backstage/integration';
+
+export interface CodeSearchResponse {
+ count: number;
+ results: CodeSearchResultItem[];
+}
+
+export interface CodeSearchResultItem {
+ fileName: string;
+ path: string;
+ repository: {
+ name: string;
+ };
+}
+
+const isCloud = (host: string) => host === 'dev.azure.com';
+const PAGE_SIZE = 1000;
+
+// codeSearch returns all files that matches the given search path.
+export async function codeSearch(
+ azureConfig: AzureIntegrationConfig,
+ org: string,
+ project: string,
+ repo: string,
+ path: string,
+): Promise {
+ const searchBaseUrl = isCloud(azureConfig.host)
+ ? 'https://almsearch.dev.azure.com'
+ : `https://${azureConfig.host}`;
+ const searchUrl = `${searchBaseUrl}/${org}/${project}/_apis/search/codesearchresults?api-version=6.0-preview.1`;
+
+ let items: CodeSearchResultItem[] = [];
+ let hasMorePages = true;
+
+ do {
+ const response = await fetch(searchUrl, {
+ ...getAzureRequestOptions(azureConfig, {
+ 'Content-Type': 'application/json',
+ }),
+ method: 'POST',
+ body: JSON.stringify({
+ searchText: `path:${path} repo:${repo || '*'}`,
+ $skip: items.length,
+ $top: PAGE_SIZE,
+ }),
+ });
+
+ if (response.status !== 200) {
+ throw new Error(
+ `Azure DevOps search failed with response status ${response.status}`,
+ );
+ }
+
+ const body: CodeSearchResponse = await response.json();
+ items = [...items, ...body.results];
+ hasMorePages = body.count > items.length;
+ } while (hasMorePages);
+
+ return items;
+}
diff --git a/plugins/catalog-backend/src/ingestion/processors/azure/index.ts b/plugins/catalog-backend/src/ingestion/processors/azure/index.ts
new file mode 100644
index 0000000000..d4ff56c4c0
--- /dev/null
+++ b/plugins/catalog-backend/src/ingestion/processors/azure/index.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 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.
+ */
+
+export * from './azure';
diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts
index 4f3c502a8e..63feac51af 100644
--- a/plugins/catalog-backend/src/ingestion/processors/index.ts
+++ b/plugins/catalog-backend/src/ingestion/processors/index.ts
@@ -26,6 +26,7 @@ export { BuiltinKindsEntityProcessor } from './BuiltinKindsEntityProcessor';
export { CodeOwnersProcessor } from './CodeOwnersProcessor';
export { FileReaderProcessor } from './FileReaderProcessor';
export { GithubDiscoveryProcessor } from './GithubDiscoveryProcessor';
+export { AzureDevOpsDiscoveryProcessor } from './AzureDevOpsDiscoveryProcessor';
export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor';
export { GithubMultiOrgReaderProcessor } from './GithubMultiOrgReaderProcessor';
export { GitLabDiscoveryProcessor } from './GitLabDiscoveryProcessor';
diff --git a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.ts b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.ts
index b8e45c7cf3..6fb52e245e 100644
--- a/plugins/catalog-backend/src/legacy/service/CatalogBuilder.ts
+++ b/plugins/catalog-backend/src/legacy/service/CatalogBuilder.ts
@@ -42,6 +42,7 @@ import {
CodeOwnersProcessor,
FileReaderProcessor,
GithubDiscoveryProcessor,
+ AzureDevOpsDiscoveryProcessor,
GithubOrgReaderProcessor,
GitLabDiscoveryProcessor,
LocationEntityProcessor,
@@ -317,6 +318,7 @@ export class CatalogBuilder {
new FileReaderProcessor(),
BitbucketDiscoveryProcessor.fromConfig(config, { logger }),
GithubDiscoveryProcessor.fromConfig(config, { logger }),
+ AzureDevOpsDiscoveryProcessor.fromConfig(config, { logger }),
GithubOrgReaderProcessor.fromConfig(config, { logger }),
GitLabDiscoveryProcessor.fromConfig(config, { logger }),
new UrlReaderProcessor({ reader, logger }),
diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts
index a089989d20..562bae4867 100644
--- a/plugins/catalog-backend/src/service/DefaultLocationService.test.ts
+++ b/plugins/catalog-backend/src/service/DefaultLocationService.test.ts
@@ -148,6 +148,57 @@ describe('DefaultLocationServiceTest', () => {
expect(result.exists).toBe(true);
});
+ it('should fail when there are duplicate entities using dry run', async () => {
+ store.listLocations.mockResolvedValueOnce([]);
+ orchestrator.process.mockResolvedValueOnce({
+ ok: true,
+ state: {},
+ completedEntity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'foo',
+ },
+ },
+ deferredEntities: [
+ {
+ entity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'foo',
+ },
+ },
+ locationKey: 'file:///tmp/mock.yaml',
+ },
+ ],
+ relations: [],
+ errors: [],
+ });
+
+ orchestrator.process.mockResolvedValueOnce({
+ ok: true,
+ state: {},
+ completedEntity: {
+ apiVersion: 'backstage.io/v1alpha1',
+ kind: 'Location',
+ metadata: {
+ name: 'foo',
+ },
+ },
+ deferredEntities: [],
+ relations: [],
+ errors: [],
+ });
+
+ await expect(
+ locationService.createLocation(
+ { type: 'url', target: 'https://backstage.io/catalog-info.yaml' },
+ true,
+ ),
+ ).rejects.toThrowError('Duplicate nested entity: location:default/foo');
+ });
+
it('should return exists false when the location does not exist beforehand', async () => {
orchestrator.process.mockResolvedValueOnce({
ok: true,
diff --git a/plugins/catalog-backend/src/service/DefaultLocationService.ts b/plugins/catalog-backend/src/service/DefaultLocationService.ts
index 2166341935..8dd2274842 100644
--- a/plugins/catalog-backend/src/service/DefaultLocationService.ts
+++ b/plugins/catalog-backend/src/service/DefaultLocationService.ts
@@ -19,6 +19,7 @@ import {
LocationSpec,
LOCATION_ANNOTATION,
ORIGIN_LOCATION_ANNOTATION,
+ stringifyEntityRef,
} from '@backstage/catalog-model';
import {
CatalogProcessingOrchestrator,
@@ -54,6 +55,43 @@ export class DefaultLocationService implements LocationService {
return this.store.deleteLocation(id);
}
+ private async processEntities(
+ unprocessedEntities: DeferredEntity[],
+ ): Promise {
+ const entities: Entity[] = [];
+ while (unprocessedEntities.length) {
+ const currentEntity = unprocessedEntities.pop();
+ if (!currentEntity) {
+ continue;
+ }
+ const processed = await this.orchestrator.process({
+ entity: currentEntity.entity,
+ state: {}, // we process without the existing cache
+ });
+
+ if (processed.ok) {
+ if (
+ entities.some(
+ e =>
+ stringifyEntityRef(e) ===
+ stringifyEntityRef(processed.completedEntity),
+ )
+ ) {
+ throw new Error(
+ `Duplicate nested entity: ${stringifyEntityRef(
+ processed.completedEntity,
+ )}`,
+ );
+ }
+ unprocessedEntities.push(...processed.deferredEntities);
+ entities.push(processed.completedEntity);
+ } else {
+ throw Error(processed.errors.map(String).join(', '));
+ }
+ }
+ return entities;
+ }
+
private async dryRunCreateLocation(
spec: LocationSpec,
): Promise<{ location: Location; entities: Entity[]; exists?: boolean }> {
@@ -86,24 +124,7 @@ export class DefaultLocationService implements LocationService {
const unprocessedEntities: DeferredEntity[] = [
{ entity, locationKey: `${spec.type}:${spec.target}` },
];
- const entities: Entity[] = [];
- while (unprocessedEntities.length) {
- const currentEntity = unprocessedEntities.pop();
- if (!currentEntity) {
- continue;
- }
- const processed = await this.orchestrator.process({
- entity: currentEntity.entity,
- state: {}, // we process without the existing cache
- });
-
- if (processed.ok) {
- unprocessedEntities.push(...processed.deferredEntities);
- entities.push(processed.completedEntity);
- } else {
- throw Error(processed.errors.map(String).join(', '));
- }
- }
+ const entities: Entity[] = await this.processEntities(unprocessedEntities);
return {
exists: await existsPromise,
diff --git a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts
index eed420cd53..c9ebc8f723 100644
--- a/plugins/catalog-backend/src/service/NextCatalogBuilder.ts
+++ b/plugins/catalog-backend/src/service/NextCatalogBuilder.ts
@@ -44,6 +44,7 @@ import {
CatalogProcessorParser,
CodeOwnersProcessor,
FileReaderProcessor,
+ AzureDevOpsDiscoveryProcessor,
GithubDiscoveryProcessor,
GithubOrgReaderProcessor,
GitLabDiscoveryProcessor,
@@ -292,6 +293,7 @@ export class NextCatalogBuilder {
return [
new FileReaderProcessor(),
BitbucketDiscoveryProcessor.fromConfig(config, { logger }),
+ AzureDevOpsDiscoveryProcessor.fromConfig(config, { logger }),
GithubDiscoveryProcessor.fromConfig(config, { logger }),
GithubOrgReaderProcessor.fromConfig(config, { logger }),
GitLabDiscoveryProcessor.fromConfig(config, { logger }),
diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts
index ee86e78c6b..d0f284c11c 100644
--- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts
+++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.test.ts
@@ -435,5 +435,37 @@ describe('NextEntitiesCatalog', () => {
expect(entities).toContainEqual(entity1);
},
);
+
+ it.each(databases.eachSupportedId())(
+ 'should return no matches for an empty values array',
+ // NOTE: An empty values array is not a sensible input in a realistic scenario.
+ async databaseId => {
+ const { knex } = await createDatabase(databaseId);
+ const entity1: Entity = {
+ apiVersion: 'a',
+ kind: 'k',
+ metadata: { name: 'one' },
+ spec: {},
+ };
+ const entity2: Entity = {
+ apiVersion: 'a',
+ kind: 'k',
+ metadata: { name: 'two' },
+ spec: {},
+ };
+ await addEntityToSearch(knex, entity1);
+ await addEntityToSearch(knex, entity2);
+ const catalog = new NextEntitiesCatalog(knex);
+
+ const testFilter = {
+ key: 'kind',
+ values: [],
+ };
+ const request = { filter: testFilter };
+ const { entities } = await catalog.entities(request);
+
+ expect(entities.length).toBe(0);
+ },
+ );
});
});
diff --git a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts
index 8c2de9d7c1..f9b3abf190 100644
--- a/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts
+++ b/plugins/catalog-backend/src/service/NextEntitiesCatalog.ts
@@ -91,7 +91,7 @@ function addCondition(
if (filter.values) {
if (filter.values.length === 1) {
this.where({ value: filter.values[0].toLowerCase() });
- } else if (filter.values.length > 1) {
+ } else {
this.andWhere(
'value',
'in',
diff --git a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
index 9fe482a30b..3c167946d4 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphCard/CatalogGraphCard.test.tsx
@@ -14,14 +14,19 @@
* limitations under the License.
*/
import { Entity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import {
CatalogApi,
catalogApiRef,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockAnalyticsApi,
+ renderInTestApp,
+ TestApiProvider,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { catalogEntityRouteRef, catalogGraphRouteRef } from '../../routes';
@@ -31,7 +36,7 @@ describe('', () => {
let entity: Entity;
let wrapper: JSX.Element;
let catalog: jest.Mocked;
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
beforeAll(() => {
Object.defineProperty(window.SVGElement.prototype, 'getBBox', {
@@ -61,7 +66,7 @@ describe('', () => {
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
};
- apis = ApiRegistry.with(catalogApiRef, catalog);
+ apis = TestApiRegistry.from([catalogApiRef, catalog]);
wrapper = (
@@ -123,9 +128,9 @@ describe('', () => {
test('captures analytics event on click', async () => {
const analyticsSpy = new MockAnalyticsApi();
const { findByText } = await renderInTestApp(
-
+
{wrapper}
- ,
+ ,
{
mountedRoutes: {
'/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
diff --git a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
index b992d0c976..7bd1045fff 100644
--- a/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
+++ b/plugins/catalog-graph/src/components/CatalogGraphPage/CatalogGraphPage.test.tsx
@@ -14,10 +14,13 @@
* limitations under the License.
*/
import { RELATION_HAS_PART, RELATION_PART_OF } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { analyticsApiRef } from '@backstage/core-plugin-api';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { MockAnalyticsApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockAnalyticsApi,
+ renderInTestApp,
+ TestApiProvider,
+} from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { catalogEntityRouteRef } from '../../routes';
@@ -90,10 +93,9 @@ describe('', () => {
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
};
- const apis = ApiRegistry.with(catalogApiRef, catalog);
wrapper = (
-
+
', () => {
selectedKinds: ['b'],
}}
/>
-
+
);
});
@@ -172,9 +174,9 @@ describe('', () => {
test('should capture analytics event when selecting other entity', async () => {
const analyticsSpy = new MockAnalyticsApi();
const { getByText, findAllByTestId } = await renderInTestApp(
-
+
{wrapper}
- ,
+ ,
{
mountedRoutes: {
'/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
@@ -195,9 +197,9 @@ describe('', () => {
test('should capture analytics event when navigating to entity', async () => {
const analyticsSpy = new MockAnalyticsApi();
const { getByText, findAllByTestId } = await renderInTestApp(
-
+
{wrapper}
- ,
+ ,
{
mountedRoutes: {
'/entity/{kind}/{namespace}/{name}': catalogEntityRouteRef,
diff --git a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx
index 2d6c5cc93d..be0c4dc5e5 100644
--- a/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx
+++ b/plugins/catalog-graph/src/components/EntityRelationsGraph/EntityRelationsGraph.test.tsx
@@ -21,9 +21,8 @@ import {
RELATION_PART_OF,
stringifyEntityRef,
} from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import React, { FunctionComponent } from 'react';
import { EntityRelationsGraph } from './EntityRelationsGraph';
@@ -158,10 +157,11 @@ describe('', () => {
refreshEntity: jest.fn(),
getEntityAncestors: jest.fn(),
};
- const apis = ApiRegistry.with(catalogApiRef, catalog);
Wrapper = ({ children }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx
index 69ae38207d..784cbfbe9e 100644
--- a/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx
+++ b/plugins/catalog-import/src/components/DefaultImportPage/DefaultImportPage.test.tsx
@@ -15,14 +15,10 @@
*/
import { CatalogClient } from '@backstage/catalog-client';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { configApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import React from 'react';
import { catalogImportApiRef, CatalogImportClient } from '../../api';
import { DefaultImportPage } from './DefaultImportPage';
@@ -43,15 +39,13 @@ describe('', () => {
},
};
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
beforeEach(() => {
- apis = ApiRegistry.with(
- configApiRef,
- new ConfigReader({ integrations: {} }),
- )
- .with(catalogApiRef, new CatalogClient({ discoveryApi: {} as any }))
- .with(
+ apis = TestApiRegistry.from(
+ [configApiRef, new ConfigReader({ integrations: {} })],
+ [catalogApiRef, new CatalogClient({ discoveryApi: {} as any })],
+ [
catalogImportApiRef,
new CatalogImportClient({
discoveryApi: {} as any,
@@ -63,7 +57,8 @@ describe('', () => {
catalogApi: {} as any,
configApi: {} as any,
}),
- );
+ ],
+ );
});
it('renders without exploding', async () => {
diff --git a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.test.tsx b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.test.tsx
index 4e0c3694cd..9a27532c57 100644
--- a/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.test.tsx
+++ b/plugins/catalog-import/src/components/ImportInfoCard/ImportInfoCard.test.tsx
@@ -14,19 +14,19 @@
* limitations under the License.
*/
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { configApiRef } from '@backstage/core-plugin-api';
-import { renderInTestApp } from '@backstage/test-utils';
+import {
+ renderInTestApp,
+ TestApiProvider,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import React from 'react';
import { CatalogImportApi, catalogImportApiRef } from '../../api';
import { ImportInfoCard } from './ImportInfoCard';
describe('', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
let catalogImportApi: jest.Mocked;
beforeEach(() => {
@@ -35,26 +35,29 @@ describe('', () => {
submitPullRequest: jest.fn(),
};
- apis = ApiRegistry.with(
- configApiRef,
- new ConfigReader({
- integrations: {
- github: [{ token: 'my-token' }],
- },
- }),
- ).with(catalogImportApiRef, catalogImportApi);
+ apis = TestApiRegistry.from(
+ [
+ configApiRef,
+ new ConfigReader({
+ integrations: {
+ github: [{ token: 'my-token' }],
+ },
+ }),
+ ],
+ [catalogImportApiRef, catalogImportApi],
+ );
});
it('renders without exploding', async () => {
- apis = ApiRegistry.with(
- configApiRef,
- new ConfigReader({ integrations: {} }),
- ).with(catalogImportApiRef, catalogImportApi);
-
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
expect(getByText('Register an existing component')).toBeInTheDocument();
diff --git a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx
index 92a6077792..1778af73dc 100644
--- a/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx
+++ b/plugins/catalog-import/src/components/ImportPage/ImportPage.test.tsx
@@ -15,14 +15,10 @@
*/
import { CatalogClient } from '@backstage/catalog-client';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { configApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import React from 'react';
import { useOutlet } from 'react-router';
import { catalogImportApiRef, CatalogImportClient } from '../../api';
@@ -49,15 +45,13 @@ describe('', () => {
},
};
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
beforeEach(() => {
- apis = ApiRegistry.with(
- configApiRef,
- new ConfigReader({ integrations: {} }),
- )
- .with(catalogApiRef, new CatalogClient({ discoveryApi: {} as any }))
- .with(
+ apis = TestApiRegistry.from(
+ [configApiRef, new ConfigReader({ integrations: {} })],
+ [catalogApiRef, new CatalogClient({ discoveryApi: {} as any })],
+ [
catalogImportApiRef,
new CatalogImportClient({
discoveryApi: {} as any,
@@ -67,7 +61,8 @@ describe('', () => {
catalogApi: {} as any,
configApi: new ConfigReader({}),
}),
- );
+ ],
+ );
});
afterEach(() => jest.resetAllMocks());
diff --git a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx
index 4a72a653ea..8d28fb8604 100644
--- a/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx
+++ b/plugins/catalog-import/src/components/StepInitAnalyzeUrl/StepInitAnalyzeUrl.test.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
+import { TestApiProvider } from '@backstage/test-utils';
import { act, render } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import React from 'react';
@@ -34,14 +34,14 @@ describe('', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
{children}
-
+
);
const location = {
diff --git a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx
index 0a55d6337b..3b6f353155 100644
--- a/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx
+++ b/plugins/catalog-import/src/components/StepPrepareCreatePullRequest/StepPrepareCreatePullRequest.test.tsx
@@ -14,9 +14,9 @@
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
+import { TestApiProvider } from '@backstage/test-utils';
import { TextField } from '@material-ui/core';
import { act, render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
@@ -54,13 +54,15 @@ describe('', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
{children}
-
+
);
const onPrepareFn = jest.fn();
diff --git a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
index 8eba11aeca..f6e9d296df 100644
--- a/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
+++ b/plugins/catalog-react/src/components/EntityTypePicker/EntityTypePicker.test.tsx
@@ -17,16 +17,15 @@
import React from 'react';
import { fireEvent, waitFor } from '@testing-library/react';
import { capitalize } from 'lodash';
-import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { EntityTypePicker } from './EntityTypePicker';
import { MockEntityListContextProvider } from '../../testUtils/providers';
import { catalogApiRef } from '../../api';
import { EntityKindFilter, EntityTypeFilter } from '../../filters';
-import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
-import { renderWithEffects } from '@backstage/test-utils';
+import { alertApiRef } from '@backstage/core-plugin-api';
+import { ApiProvider } from '@backstage/core-app-api';
+import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
const entities: Entity[] = [
{
@@ -61,11 +60,20 @@ const entities: Entity[] = [
},
];
-const apis = ApiRegistry.with(catalogApiRef, {
- getEntities: jest.fn().mockResolvedValue({ items: entities }),
-} as unknown as CatalogApi).with(alertApiRef, {
- post: jest.fn(),
-} as unknown as AlertApi);
+const apis = TestApiRegistry.from(
+ [
+ catalogApiRef,
+ {
+ getEntities: jest.fn().mockResolvedValue({ items: entities }),
+ },
+ ],
+ [
+ alertApiRef,
+ {
+ post: jest.fn(),
+ },
+ ],
+);
describe('', () => {
it('renders available entity types', async () => {
diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx
index eccf824c2e..2b29bba4fd 100644
--- a/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx
+++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/UnregisterEntityDialog.test.tsx
@@ -24,7 +24,7 @@ import { CatalogClient } from '@backstage/catalog-client';
import { catalogApiRef } from '../../api';
import { entityRouteRef } from '../../routes';
import { screen, waitFor } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import * as state from './useUnregisterEntityDialogState';
import {
@@ -32,7 +32,6 @@ import {
alertApiRef,
DiscoveryApi,
} from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('UnregisterEntityDialog', () => {
const discoveryApi: DiscoveryApi = {
@@ -49,11 +48,6 @@ describe('UnregisterEntityDialog', () => {
},
};
- const apis = ApiRegistry.with(
- catalogApiRef,
- new CatalogClient({ discoveryApi }),
- ).with(alertApiRef, alertApi);
-
const entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
@@ -68,7 +62,14 @@ describe('UnregisterEntityDialog', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
const stateSpy = jest.spyOn(state, 'useUnregisterEntityDialogState');
diff --git a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
index 8b4f436aed..8d6083f111 100644
--- a/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
+++ b/plugins/catalog-react/src/components/UnregisterEntityDialog/useUnregisterEntityDialogState.test.tsx
@@ -31,7 +31,7 @@ import {
UseUnregisterEntityDialogState,
useUnregisterEntityDialogState,
} from './useUnregisterEntityDialogState';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { TestApiProvider } from '@backstage/test-utils';
function defer(): { promise: Promise; resolve: (value: T) => void } {
let resolve: (value: T) => void = () => {};
@@ -51,9 +51,9 @@ describe('useUnregisterEntityDialogState', () => {
const catalogApi = catalogApiMock as Partial as CatalogApi;
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
let entity: Entity;
diff --git a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
index 4662e58dd2..f6a4c59374 100644
--- a/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
+++ b/plugins/catalog-react/src/components/UserListPicker/UserListPicker.test.tsx
@@ -26,9 +26,9 @@ import { MockEntityListContextProvider } from '../../testUtils/providers';
import { EntityTagFilter, UserListFilter } from '../../filters';
import { CatalogApi } from '@backstage/catalog-client';
import { catalogApiRef } from '../../api';
-import { MockStorageApi } from '@backstage/test-utils';
+import { MockStorageApi, TestApiRegistry } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import {
ConfigApi,
configApiRef,
@@ -62,12 +62,12 @@ const mockIdentityApi = {
getIdToken: async () => undefined,
} as Partial;
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[configApiRef, mockConfigApi],
[catalogApiRef, mockCatalogApi],
[identityApiRef, mockIdentityApi],
[storageApiRef, MockStorageApi.create()],
-]);
+);
const mockIsOwnedEntity = (entity: Entity) =>
entity.metadata.name === 'component-1';
diff --git a/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx b/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx
index d801e508a6..cdcac8c194 100644
--- a/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityKinds.test.tsx
@@ -15,12 +15,12 @@
*/
import React, { PropsWithChildren } from 'react';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
import { catalogApiRef } from '../api';
import { renderHook } from '@testing-library/react-hooks';
import { useEntityKinds } from './useEntityKinds';
+import { TestApiProvider } from '@backstage/test-utils';
const entities: Entity[] = [
{
@@ -59,9 +59,9 @@ const mockCatalogApi: Partial = {
const wrapper = ({ children }: PropsWithChildren<{}>) => {
return (
-
+
{children}
-
+
);
};
diff --git a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
index e448ee73f1..b96f63304a 100644
--- a/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityListProvider.test.tsx
@@ -16,7 +16,6 @@
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import {
ConfigApi,
configApiRef,
@@ -24,7 +23,7 @@ import {
identityApiRef,
storageApiRef,
} from '@backstage/core-plugin-api';
-import { MockStorageApi } from '@backstage/test-utils';
+import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import qs from 'qs';
import React, { PropsWithChildren } from 'react';
@@ -76,16 +75,6 @@ const mockCatalogApi: Partial = {
getEntities: jest.fn().mockImplementation(async () => ({ items: entities })),
getEntityByName: async () => undefined,
};
-const apis = ApiRegistry.from([
- [configApiRef, mockConfigApi],
- [catalogApiRef, mockCatalogApi],
- [identityApiRef, mockIdentityApi],
- [storageApiRef, MockStorageApi.create()],
- [
- starredEntitiesApiRef,
- new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
- ],
-]);
const wrapper = ({
userFilter,
@@ -94,13 +83,26 @@ const wrapper = ({
userFilter?: UserListFilterKind;
}>) => {
return (
-
+
{children}
-
+
);
};
diff --git a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx
index 4edad98c12..f01c13839d 100644
--- a/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx
+++ b/plugins/catalog-react/src/hooks/useEntityOwnership.test.tsx
@@ -21,8 +21,8 @@ import {
RELATION_OWNED_BY,
UserEntity,
} from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
+import { TestApiProvider } from '@backstage/test-utils';
import { renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { catalogApiRef } from '../api';
@@ -50,14 +50,14 @@ describe('useEntityOwnership', () => {
const catalogApi = mockCatalogApi as unknown as CatalogApi;
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
{children}
-
+
);
const ownedEntity: ComponentEntity = {
diff --git a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
index 4f41ef090f..aef876a1b2 100644
--- a/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
+++ b/plugins/catalog-react/src/hooks/useStarredEntities.test.tsx
@@ -15,9 +15,8 @@
*/
import { Entity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { StorageApi } from '@backstage/core-plugin-api';
-import { MockStorageApi } from '@backstage/test-utils';
+import { MockStorageApi, TestApiProvider } from '@backstage/test-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import React, { PropsWithChildren } from 'react';
import { DefaultStarredEntitiesApi, starredEntitiesApiRef } from '../apis';
@@ -47,14 +46,16 @@ describe('useStarredEntities', () => {
beforeEach(() => {
mockStorage = MockStorageApi.create();
wrapper = ({ children }: PropsWithChildren<{}>) => (
-
{children}
-
+
);
});
diff --git a/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
index b8577aec0c..8ffc891092 100644
--- a/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
+++ b/plugins/catalog-react/src/hooks/useStarredEntity.test.tsx
@@ -15,7 +15,7 @@
*/
import { Entity, EntityName } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { TestApiProvider } from '@backstage/test-utils';
import { renderHook } from '@testing-library/react-hooks';
import React, { PropsWithChildren } from 'react';
import Observable from 'zen-observable';
@@ -31,11 +31,9 @@ describe('useStarredEntity', () => {
beforeEach(() => {
wrapper = ({ children }: PropsWithChildren<{}>) => (
-
+
{children}
-
+
);
});
diff --git a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
index d6f13ed22d..afbed3bdc7 100644
--- a/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
+++ b/plugins/catalog/src/components/AboutCard/AboutCard.test.tsx
@@ -15,11 +15,7 @@
*/
import { RELATION_OWNED_BY } from '@backstage/catalog-model';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ConfigReader } from '@backstage/core-app-api';
import {
ScmIntegrationsApi,
scmIntegrationsApiRef,
@@ -30,7 +26,7 @@ import {
CatalogApi,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { viewTechDocRouteRef } from '../../routes';
@@ -71,21 +67,25 @@ describe('', () => {
},
],
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {},
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -116,28 +116,32 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {
- github: [
- {
- host: 'github.com',
- token: '...',
- },
- ],
- },
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -167,28 +171,32 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {
- github: [
- {
- host: 'github.com',
- token: '...',
- },
- ],
- },
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByTitle } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -216,17 +224,21 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(new ConfigReader({})),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -253,17 +265,21 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(new ConfigReader({})),
- ).with(catalogApiRef, catalogApi);
const { getByTitle } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -295,17 +311,21 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(new ConfigReader({})),
- ).with(catalogApiRef, catalogApi);
const { queryByTitle } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -332,28 +352,32 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {
- github: [
- {
- host: 'github.com',
- token: '...',
- },
- ],
- },
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/docs/:namespace/:kind/:name': viewTechDocRouteRef,
@@ -381,28 +405,32 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {
- github: [
- {
- host: 'github.com',
- token: '...',
- },
- ],
- },
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -429,28 +457,32 @@ describe('', () => {
lifecycle: 'production',
},
};
- const apis = ApiRegistry.with(
- scmIntegrationsApiRef,
- ScmIntegrationsApi.fromConfig(
- new ConfigReader({
- integrations: {
- github: [
- {
- host: 'github.com',
- token: '...',
- },
- ],
- },
- }),
- ),
- ).with(catalogApiRef, catalogApi);
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
index 2730665fb6..b320d6d220 100644
--- a/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
+++ b/plugins/catalog/src/components/CatalogKindHeader/CatalogKindHeader.test.tsx
@@ -18,13 +18,12 @@ import React from 'react';
import { fireEvent } from '@testing-library/react';
import { Entity } from '@backstage/catalog-model';
import {
- CatalogApi,
catalogApiRef,
EntityKindFilter,
MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
-import { renderWithEffects } from '@backstage/test-utils';
+import { ApiProvider } from '@backstage/core-app-api';
+import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
import { CatalogKindHeader } from './CatalogKindHeader';
const entities: Entity[] = [
@@ -58,9 +57,12 @@ const entities: Entity[] = [
},
];
-const apis = ApiRegistry.with(catalogApiRef, {
- getEntities: jest.fn().mockResolvedValue({ items: entities }),
-} as Partial);
+const apis = TestApiRegistry.from([
+ catalogApiRef,
+ {
+ getEntities: jest.fn().mockResolvedValue({ items: entities }),
+ },
+]);
describe('', () => {
it('renders available kinds', async () => {
diff --git a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
index 6b33b33b08..0a46580c12 100644
--- a/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
+++ b/plugins/catalog/src/components/CatalogPage/CatalogPage.test.tsx
@@ -20,7 +20,6 @@ import {
RELATION_MEMBER_OF,
RELATION_OWNED_BY,
} from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { TableColumn, TableProps } from '@backstage/core-components';
import {
IdentityApi,
@@ -38,6 +37,7 @@ import {
mockBreakpoint,
MockStorageApi,
renderWithEffects,
+ TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import DashboardIcon from '@material-ui/icons/Dashboard';
@@ -129,8 +129,8 @@ describe('CatalogPage', () => {
const renderWrapped = (children: React.ReactNode) =>
renderWithEffects(
wrapInTestApp(
- {
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi }),
],
- ])}
+ ]}
>
{children}
- ,
+ ,
{
mountedRoutes: {
'/create': createComponentRouteRef,
diff --git a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
index 822913a385..4c6adcf400 100644
--- a/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
+++ b/plugins/catalog/src/components/CatalogTable/CatalogTable.test.tsx
@@ -19,7 +19,7 @@ import {
Entity,
VIEW_URL_ANNOTATION,
} from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import {
entityRouteRef,
DefaultStarredEntitiesApi,
@@ -27,7 +27,11 @@ import {
starredEntitiesApiRef,
UserListFilter,
} from '@backstage/plugin-catalog-react';
-import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import { act, fireEvent } from '@testing-library/react';
import * as React from 'react';
import { CatalogTable } from './CatalogTable';
@@ -51,10 +55,10 @@ const entities: Entity[] = [
];
describe('CatalogTable component', () => {
- const mockApis = ApiRegistry.with(
+ const mockApis = TestApiRegistry.from([
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
- );
+ ]);
beforeEach(() => {
window.open = jest.fn();
diff --git a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
index 7dd823a8f0..e43104b67d 100644
--- a/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/DependencyOfComponentsCard/DependencyOfComponentsCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { DependencyOfComponentsCard } from './DependencyOfComponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -97,7 +89,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
index b9fd75def9..b654136d18 100644
--- a/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/DependsOnComponentsCard/DependsOnComponentsCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { DependsOnComponentsCard } from './DependsOnComponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -97,7 +89,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
index bee759254c..40685705aa 100644
--- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
+++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { DependsOnResourcesCard } from './DependsOnResourcesCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -97,7 +89,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx
index 2216070984..3f3fc804e1 100644
--- a/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx
+++ b/plugins/catalog/src/components/EntityLayout/EntityLayout.test.tsx
@@ -16,7 +16,7 @@
import { CatalogApi } from '@backstage/catalog-client';
import { Entity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
import {
AsyncEntityProvider,
@@ -26,7 +26,11 @@ import {
entityRouteRef,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
-import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import { fireEvent } from '@testing-library/react';
import React from 'react';
import { act } from 'react-dom/test-utils';
@@ -40,12 +44,14 @@ const mockEntity = {
},
} as Entity;
-const mockApis = ApiRegistry.with(catalogApiRef, {} as CatalogApi)
- .with(alertApiRef, {} as AlertApi)
- .with(
+const mockApis = TestApiRegistry.from(
+ [catalogApiRef, {} as CatalogApi],
+ [alertApiRef, {} as AlertApi],
+ [
starredEntitiesApiRef,
new DefaultStarredEntitiesApi({ storageApi: MockStorageApi.create() }),
- );
+ ],
+);
describe('EntityLayout', () => {
it('renders simplest case', async () => {
diff --git a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx
index 862ef9504b..f58e6b9b66 100644
--- a/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx
+++ b/plugins/catalog/src/components/EntityOrphanWarning/DeleteEntityDialog.test.tsx
@@ -20,10 +20,9 @@ import { ORIGIN_LOCATION_ANNOTATION } from '@backstage/catalog-model';
import { CatalogApi } from '@backstage/catalog-client';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
import { screen, waitFor } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('DeleteEntityDialog', () => {
const alertApi: jest.Mocked = {
@@ -34,10 +33,6 @@ describe('DeleteEntityDialog', () => {
const catalogClient: jest.Mocked = {
removeEntityByUid: jest.fn(),
} as any;
- const apis = ApiRegistry.with(catalogApiRef, catalogClient).with(
- alertApiRef,
- alertApi,
- );
const entity = {
apiVersion: 'backstage.io/v1alpha1',
@@ -54,7 +49,14 @@ describe('DeleteEntityDialog', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
afterEach(() => {
diff --git a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx
index 9e26184a8d..a8aac405fe 100644
--- a/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx
+++ b/plugins/catalog/src/components/EntityOrphanWarning/EntityOrphanWarning.test.tsx
@@ -15,23 +15,16 @@
*/
import {
- CatalogApi,
catalogApiRef,
catalogRouteRef,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { EntityOrphanWarning } from './EntityOrphanWarning';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogClient: jest.Mocked = {
- removeEntityByUid: jest.fn(),
- } as any;
- const apis = ApiRegistry.with(catalogApiRef, catalogClient);
-
it('renders EntityOrphanWarning if the entity is orphan', async () => {
const entity = {
apiVersion: 'v1',
@@ -50,11 +43,20 @@ describe('', () => {
};
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/create': catalogRouteRef,
diff --git a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.test.tsx b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.test.tsx
index c406d0ed3c..fcc4475f6a 100644
--- a/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.test.tsx
+++ b/plugins/catalog/src/components/EntityProcessingErrorsPanel/EntityProcessingErrorsPanel.test.tsx
@@ -21,17 +21,17 @@ import {
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import React from 'react';
import { EntityProcessingErrorsPanel } from './EntityProcessingErrorsPanel';
import { Entity, getEntityName } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
describe('', () => {
- const catalogClient: jest.Mocked = {
- getEntityAncestors: jest.fn(),
- } as any;
- const apis = ApiRegistry.with(catalogApiRef, catalogClient);
+ const getEntityAncestors: jest.MockedFunction<
+ CatalogApi['getEntityAncestors']
+ > = jest.fn();
+ const apis = TestApiRegistry.from([catalogApiRef, { getEntityAncestors }]);
it('renders EntityProcessErrors if the entity has errors', async () => {
const entity: Entity = {
@@ -97,7 +97,7 @@ describe('', () => {
},
};
- catalogClient.getEntityAncestors.mockResolvedValue({
+ getEntityAncestors.mockResolvedValue({
root: getEntityName(entity),
items: [{ entity, parents: [] }],
});
@@ -198,7 +198,7 @@ describe('', () => {
],
},
};
- catalogClient.getEntityAncestors.mockResolvedValue({
+ getEntityAncestors.mockResolvedValue({
root: getEntityName(entity),
items: [
{ entity, parents: [getEntityName(parent)] },
diff --git a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
index 7f8bf35b74..8768e9728e 100644
--- a/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
+++ b/plugins/catalog/src/components/EntitySwitch/EntitySwitch.test.tsx
@@ -21,17 +21,14 @@ import React from 'react';
import { isKind } from './conditions';
import { EntitySwitch } from './EntitySwitch';
import { featureFlagsApiRef } from '@backstage/core-plugin-api';
-import {
- LocalStorageFeatureFlags,
- ApiProvider,
- ApiRegistry,
-} from '@backstage/core-app-api';
+import { LocalStorageFeatureFlags } from '@backstage/core-app-api';
+import { TestApiProvider } from '@backstage/test-utils';
const mockFeatureFlagsApi = new LocalStorageFeatureFlags();
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
describe('EntitySwitch', () => {
diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
index d484ab026a..45f7561d6e 100644
--- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
+++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { HasComponentsCard } from './HasComponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -97,7 +89,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
index 45604ceb56..a01922d988 100644
--- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
+++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { HasResourcesCard } from './HasResourcesCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -92,7 +84,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
index 431e757656..e517bd38ae 100644
--- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
+++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { HasSubcomponentsCard } from './HasSubcomponentsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -97,7 +89,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
index 3fcfed6099..050550101f 100644
--- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
+++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx
@@ -21,28 +21,20 @@ import {
EntityProvider,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { HasSystemsCard } from './HasSystemsCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
- const catalogApi: jest.Mocked = {
- getLocationById: jest.fn(),
- getEntityByName: jest.fn(),
- getEntities: jest.fn(),
- addLocation: jest.fn(),
- getLocationByEntity: jest.fn(),
- removeEntityByUid: jest.fn(),
- } as any;
+ const getEntities: jest.MockedFunction = jest.fn();
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
@@ -95,7 +87,7 @@ describe('', () => {
},
],
};
- catalogApi.getEntities.mockResolvedValue({
+ getEntities.mockResolvedValue({
items: [
{
apiVersion: 'v1',
diff --git a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx
index 2b35d0f3b6..e0ec48fbed 100644
--- a/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx
+++ b/plugins/catalog/src/components/SystemDiagramCard/SystemDiagramCard.test.tsx
@@ -21,10 +21,9 @@ import {
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { Entity, RELATION_PART_OF } from '@backstage/catalog-model';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { SystemDiagramCard } from './SystemDiagramCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
beforeAll(() => {
@@ -55,11 +54,11 @@ describe('', () => {
};
const { queryByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -114,11 +113,11 @@ describe('', () => {
};
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
@@ -173,11 +172,11 @@ describe('', () => {
};
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx
index 4a38af3dbc..e29bb78f2f 100644
--- a/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx
+++ b/plugins/cost-insights/src/components/CostInsightsHeader/CostInsightsHeader.test.tsx
@@ -15,10 +15,10 @@
*/
import { CostInsightsHeader } from './CostInsightsHeader';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import React from 'react';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
describe('', () => {
@@ -29,7 +29,7 @@ describe('', () => {
}),
};
- const apis = ApiRegistry.from([[identityApiRef, identityApi]]);
+ const apis = TestApiRegistry.from([identityApiRef, identityApi]);
it('Shows nothing to do when no alerts exist', async () => {
const rendered = await renderInTestApp(
diff --git a/plugins/cost-insights/src/plugin.ts b/plugins/cost-insights/src/plugin.ts
index 1714c5fbd5..a422098e08 100644
--- a/plugins/cost-insights/src/plugin.ts
+++ b/plugins/cost-insights/src/plugin.ts
@@ -34,9 +34,7 @@ export const unlabeledDataflowAlertRef = createRouteRef({
export const costInsightsPlugin = createPlugin({
id: 'cost-insights',
- register({ featureFlags }) {
- featureFlags.register('cost-insights-currencies');
- },
+ featureFlags: [{ name: 'cost-insights-currencies' }],
routes: {
root: rootRouteRef,
growthAlerts: projectGrowthAlertRef,
diff --git a/plugins/cost-insights/src/testUtils/providers.tsx b/plugins/cost-insights/src/testUtils/providers.tsx
index 75450c0bb7..09d2feb71b 100644
--- a/plugins/cost-insights/src/testUtils/providers.tsx
+++ b/plugins/cost-insights/src/testUtils/providers.tsx
@@ -30,8 +30,9 @@ import { Group, Duration } from '../types';
// TODO(Rugvip): Could be good to have a clear place to put test utils that is linted accordingly
// eslint-disable-next-line import/no-extraneous-dependencies
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { IdentityApi, identityApiRef } from '@backstage/core-plugin-api';
+// eslint-disable-next-line import/no-extraneous-dependencies
+import { TestApiProvider } from '@backstage/test-utils';
type PartialPropsWithChildren = PropsWithChildren>;
@@ -199,16 +200,17 @@ export const MockCostInsightsApiProvider = ({
getUserGroups: jest.fn(),
};
- // TODO: defaultConfigApiRef: ConfigApiRef
-
- const defaultContext = ApiRegistry.from([
- [identityApiRef, { ...defaultIdentityApi, ...context.identityApi }],
- [
- costInsightsApiRef,
- { ...defaultCostInsightsApi, ...context.costInsightsApi },
- ],
- // [configApiRef, { ...defaultConfigApiRef, ...context.configApiRef }]
- ]);
-
- return {children};
+ return (
+
+ {children}
+
+ );
};
diff --git a/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx b/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx
index 8927cf088b..d10c262357 100644
--- a/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx
+++ b/plugins/explore/src/components/DefaultExplorePage/DefaultExplorePage.test.tsx
@@ -15,11 +15,10 @@
*/
import { catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor, getByText } from '@testing-library/react';
import React from 'react';
import { DefaultExplorePage } from './DefaultExplorePage';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -36,9 +35,9 @@ describe('', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
beforeEach(() => {
diff --git a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx
index d78fc0428d..46a8f1adf2 100644
--- a/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx
+++ b/plugins/explore/src/components/DomainExplorerContent/DomainExplorerContent.test.tsx
@@ -16,12 +16,11 @@
import { DomainEntity } from '@backstage/catalog-model';
import { catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { catalogEntityRouteRef } from '../../routes';
import { DomainExplorerContent } from './DomainExplorerContent';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -38,9 +37,9 @@ describe('', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
const mountedRoutes = {
diff --git a/plugins/explore/src/components/ExploreLayout/ExploreLayout.test.tsx b/plugins/explore/src/components/ExploreLayout/ExploreLayout.test.tsx
index 80944628da..7574c685a4 100644
--- a/plugins/explore/src/components/ExploreLayout/ExploreLayout.test.tsx
+++ b/plugins/explore/src/components/ExploreLayout/ExploreLayout.test.tsx
@@ -14,16 +14,12 @@
* limitations under the License.
*/
-import {
- ApiProvider,
- ApiRegistry,
- FeatureFlagged,
-} from '@backstage/core-app-api';
+import { FeatureFlagged } from '@backstage/core-app-api';
import {
FeatureFlagsApi,
featureFlagsApiRef,
} from '@backstage/core-plugin-api';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ExploreLayout } from './ExploreLayout';
@@ -35,11 +31,11 @@ const featureFlagsApi: jest.Mocked = {
registerFlag: jest.fn(),
};
-const mockApis = ApiRegistry.with(featureFlagsApiRef, featureFlagsApi);
-
describe('', () => {
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
afterEach(() => {
diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
index 66f54900c9..7c57de84bc 100644
--- a/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
+++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsDiagram.test.tsx
@@ -20,10 +20,9 @@ import {
entityRouteRef,
} from '@backstage/plugin-catalog-react';
import { Entity } from '@backstage/catalog-model';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { GroupsDiagram } from './GroupsDiagram';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
beforeAll(() => {
@@ -57,9 +56,9 @@ describe('', () => {
};
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/catalog/:namespace/:kind/:name': entityRouteRef,
diff --git a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
index f0815e0116..5d3f7c358b 100644
--- a/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
+++ b/plugins/explore/src/components/GroupsExplorerContent/GroupsExplorerContent.test.tsx
@@ -16,11 +16,10 @@
import { Entity } from '@backstage/catalog-model';
import { catalogApiRef, entityRouteRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { GroupsExplorerContent } from '../GroupsExplorerContent';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -37,9 +36,9 @@ describe('', () => {
};
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
const mountedRoutes = {
diff --git a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx
index 8accf8fd30..da35c9fa39 100644
--- a/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx
+++ b/plugins/explore/src/components/ToolExplorerContent/ToolExplorerContent.test.tsx
@@ -18,13 +18,12 @@ import {
ExploreTool,
exploreToolsConfigRef,
} from '@backstage/plugin-explore-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { waitFor } from '@testing-library/react';
import React from 'react';
import { ToolExplorerContent } from './ToolExplorerContent';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const exploreToolsConfigApi: jest.Mocked = {
@@ -33,11 +32,9 @@ describe('', () => {
const Wrapper = ({ children }: { children?: React.ReactNode }) => (
-
+
{children}
-
+
);
diff --git a/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx
index 1123f014da..20dbb77e86 100644
--- a/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx
+++ b/plugins/firehydrant/src/components/ServiceDetailsCard/ServiceDetailsCard.test.tsx
@@ -14,19 +14,19 @@
* limitations under the License.
*/
import React from 'react';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { fireHydrantApiRef } from '../../api';
import { screen } from '@testing-library/react';
import { ServiceDetailsCard } from './ServiceDetailsCard';
import { Service, Incident } from '../types';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
const mockFireHydrantApi = {
- getServiceDetails: () => {},
- getServiceAnalytics: () => {},
+ getServiceDetails: jest.fn(),
+ getServiceAnalytics: jest.fn(),
};
-const apis = ApiRegistry.from([[fireHydrantApiRef, mockFireHydrantApi]]);
+const apis = TestApiRegistry.from([fireHydrantApiRef, mockFireHydrantApi]);
jest.mock('@backstage/plugin-catalog-react', () => ({
useEntity: () => {
diff --git a/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx b/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
index 79b401e4a8..046e3e9860 100644
--- a/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
+++ b/plugins/fossa/src/components/FossaCard/FossaCard.test.tsx
@@ -16,11 +16,10 @@
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { FossaApi, fossaApiRef } from '../../api';
import { FossaCard } from './FossaCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const fossaApi: jest.Mocked = {
@@ -30,10 +29,10 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(fossaApiRef, fossaApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
index 778f5ee9c6..421ff4431f 100644
--- a/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
+++ b/plugins/fossa/src/components/FossaPage/FossaPage.test.tsx
@@ -20,11 +20,10 @@ import {
catalogApiRef,
entityRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { FossaApi, fossaApiRef } from '../../api';
import { FossaPage } from './FossaPage';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('', () => {
const catalogApi: jest.Mocked = {
@@ -46,13 +45,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(fossaApiRef, fossaApi).with(
- catalogApiRef,
- catalogApi,
- );
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx
index 4a4452be5c..0f9573e051 100644
--- a/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx
+++ b/plugins/github-actions/src/components/Cards/RecentWorkflowRunsCard.test.tsx
@@ -24,16 +24,13 @@ import { useWorkflowRuns } from '../useWorkflowRuns';
import type { Props as RecentWorkflowRunsCardProps } from './RecentWorkflowRunsCard';
import { RecentWorkflowRunsCard } from './RecentWorkflowRunsCard';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ConfigReader } from '@backstage/core-app-api';
import {
errorApiRef,
configApiRef,
ConfigApi,
} from '@backstage/core-plugin-api';
+import { TestApiProvider } from '@backstage/test-utils';
jest.mock('../useWorkflowRuns', () => ({
useWorkflowRuns: jest.fn(),
@@ -82,16 +79,16 @@ describe('', () => {
render(
-
-
+
,
);
diff --git a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx
index 08eeb4a3ce..dba5bbfe1c 100644
--- a/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx
+++ b/plugins/github-deployments/src/components/GithubDeploymentsCard.test.tsx
@@ -19,6 +19,7 @@ import { fireEvent } from '@testing-library/react';
import {
setupRequestMockHandlers,
renderInTestApp,
+ TestApiRegistry,
} from '@backstage/test-utils';
import {
GithubDeployment,
@@ -42,11 +43,7 @@ import { Entity } from '@backstage/catalog-model';
import { GithubDeploymentsTable } from './GithubDeploymentsTable';
import { Box } from '@material-ui/core';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import {
errorApiRef,
configApiRef,
@@ -95,14 +92,14 @@ const githubAuthApi: OAuthApi = {
getAccessToken: async _ => 'access_token',
};
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[configApiRef, configApi],
[errorApiRef, errorApiMock],
[
githubDeploymentsApiRef,
new GithubDeploymentsApiClient({ scmIntegrationsApi, githubAuthApi }),
],
-]);
+);
const assertFetchedData = async () => {
const rendered = await renderInTestApp(
diff --git a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx
index 8dba498507..e158bcf6ad 100644
--- a/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx
+++ b/plugins/gitops-profiles/src/components/ProfileCatalog/ProfileCatalog.test.tsx
@@ -14,7 +14,7 @@
* limitations under the License.
*/
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import React from 'react';
@@ -23,7 +23,6 @@ import ProfileCatalog from './ProfileCatalog';
import {
ApiProvider,
- ApiRegistry,
GithubAuth,
OAuthRequestManager,
UrlPatternDiscovery,
@@ -34,7 +33,7 @@ import { githubAuthApiRef } from '@backstage/core-plugin-api';
describe('ProfileCatalog', () => {
it('should render', async () => {
const oauthRequestApi = new OAuthRequestManager();
- const apis = ApiRegistry.from([
+ const apis = TestApiRegistry.from(
[gitOpsApiRef, new GitOpsRestApi('http://localhost:3008')],
[
githubAuthApiRef,
@@ -45,7 +44,7 @@ describe('ProfileCatalog', () => {
oauthRequestApi,
}),
],
- ]);
+ );
const { getByText } = await renderInTestApp(
diff --git a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.test.tsx b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.test.tsx
index 4e426d4364..ae84b549ec 100644
--- a/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.test.tsx
+++ b/plugins/graphiql/src/components/GraphiQLPage/GraphiQLPage.test.tsx
@@ -19,14 +19,10 @@ import { GraphiQLPage } from './GraphiQLPage';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { act } from 'react-dom/test-utils';
-import { renderWithEffects } from '@backstage/test-utils';
+import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
import { GraphQLBrowseApi, graphQlBrowseApiRef } from '../../lib/api';
import { configApiRef } from '@backstage/core-plugin-api';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ConfigReader } from '@backstage/core-app-api';
jest.mock('../GraphiQLBrowser', () => ({
GraphiQLBrowser: () => '',
@@ -43,17 +39,17 @@ describe('GraphiQLPage', () => {
};
const rendered = await renderWithEffects(
-
,
- ,
+ ,
);
act(() => {
jest.advanceTimersByTime(250);
@@ -71,16 +67,16 @@ describe('GraphiQLPage', () => {
};
const rendered = await renderWithEffects(
-
- ,
+ ,
);
rendered.getByText('GraphiQL');
@@ -95,16 +91,16 @@ describe('GraphiQLPage', () => {
};
const rendered = await renderWithEffects(
-
- ,
+ ,
);
rendered.getByText('GraphiQL');
diff --git a/plugins/jenkins/src/components/Cards/Cards.test.tsx b/plugins/jenkins/src/components/Cards/Cards.test.tsx
index 2508461710..0af91da179 100644
--- a/plugins/jenkins/src/components/Cards/Cards.test.tsx
+++ b/plugins/jenkins/src/components/Cards/Cards.test.tsx
@@ -15,11 +15,10 @@
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { LatestRunCard } from './Cards';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { JenkinsApi, jenkinsApiRef } from '../../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { Project } from '../../api/JenkinsApi';
describe('', () => {
@@ -41,14 +40,12 @@ describe('', () => {
};
it('should show success status of latest build', async () => {
- const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApi]]);
-
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
expect(getByText('Completed')).toBeInTheDocument();
@@ -59,14 +56,12 @@ describe('', () => {
getProjects: () => Promise.reject(new Error('Unauthorized')),
};
- const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]);
-
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
expect(getByText("Error: Can't connect to Jenkins")).toBeInTheDocument();
@@ -82,14 +77,12 @@ describe('', () => {
}),
};
- const apis = ApiRegistry.from([[jenkinsApiRef, jenkinsApiWithError]]);
-
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
);
expect(getByText("Error: Can't find Jenkins project")).toBeInTheDocument();
diff --git a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx
index a90d3d608e..0be9cabacd 100644
--- a/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx
+++ b/plugins/kafka/src/components/ConsumerGroupOffsets/useConsumerGroupsOffsetsForEntity.test.tsx
@@ -26,8 +26,8 @@ import {
import { useConsumerGroupsOffsetsForEntity } from './useConsumerGroupsOffsetsForEntity';
import * as data from './__fixtures__/consumer-group-offsets.json';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
+import { TestApiProvider } from '@backstage/test-utils';
const consumerGroupOffsets = data as ConsumerGroupOffsetsResponse;
@@ -59,14 +59,14 @@ describe('useConsumerGroupOffsets', () => {
const wrapper = ({ children }: PropsWithChildren<{}>) => {
return (
-
{children}
-
+
);
};
diff --git a/plugins/kubernetes/dev/index.tsx b/plugins/kubernetes/dev/index.tsx
index a2c1114c2d..12de5f098f 100644
--- a/plugins/kubernetes/dev/index.tsx
+++ b/plugins/kubernetes/dev/index.tsx
@@ -29,7 +29,7 @@ import {
} from '@backstage/plugin-kubernetes-common';
import fixture1 from '../src/__fixtures__/1-deployments.json';
import fixture2 from '../src/__fixtures__/2-deployments.json';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { TestApiProvider } from '@backstage/test-utils';
const mockEntity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
@@ -80,32 +80,26 @@ createDevApp()
path: '/fixture-1',
title: 'Fixture 1',
element: (
-
-
+
),
})
.addPage({
path: '/fixture-2',
title: 'Fixture 2',
element: (
-
-
+
),
})
.registerPlugin(kubernetesPlugin)
diff --git a/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx b/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx
index 1433a186fb..53de33d21b 100644
--- a/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx
+++ b/plugins/lighthouse/src/components/AuditList/AuditListForEntity.test.tsx
@@ -30,8 +30,9 @@ import { useWebsiteForEntity } from '../../hooks/useWebsiteForEntity';
import * as data from '../../__fixtures__/website-list-response.json';
import { AuditListForEntity } from './AuditListForEntity';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
+import { TestApiRegistry } from '@backstage/test-utils';
jest.mock('../../hooks/useWebsiteForEntity', () => ({
useWebsiteForEntity: jest.fn(),
@@ -41,7 +42,7 @@ const websiteListResponse = data as WebsiteListResponse;
const entityWebsite = websiteListResponse.items[0];
describe('', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
const mockErrorApi: jest.Mocked = {
post: jest.fn(),
@@ -49,10 +50,10 @@ describe('', () => {
};
beforeEach(() => {
- apis = ApiRegistry.from([
+ apis = TestApiRegistry.from(
[lighthouseApiRef, new LighthouseRestApi('http://lighthouse')],
[errorApiRef, mockErrorApi],
- ]);
+ );
(useWebsiteForEntity as jest.Mock).mockReturnValue({
value: entityWebsite,
diff --git a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
index 4c92997755..d8a54928b2 100644
--- a/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
+++ b/plugins/lighthouse/src/components/AuditList/AuditListTable.test.tsx
@@ -16,7 +16,11 @@
import React from 'react';
import { render } from '@testing-library/react';
-import { wrapInTestApp, setupRequestMockHandlers } from '@backstage/test-utils';
+import {
+ wrapInTestApp,
+ setupRequestMockHandlers,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import AuditListTable from './AuditListTable';
import {
@@ -28,19 +32,20 @@ import { formatTime } from '../../utils';
import { setupServer } from 'msw/node';
import * as data from '../../__fixtures__/website-list-response.json';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const websiteListResponse = data as WebsiteListResponse;
describe('AuditListTable', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
const server = setupServer();
setupRequestMockHandlers(server);
beforeEach(() => {
- apis = ApiRegistry.from([
- [lighthouseApiRef, new LighthouseRestApi('http://lighthouse')],
+ apis = TestApiRegistry.from([
+ lighthouseApiRef,
+ new LighthouseRestApi('http://lighthouse'),
]);
});
diff --git a/plugins/lighthouse/src/components/AuditList/index.test.tsx b/plugins/lighthouse/src/components/AuditList/index.test.tsx
index ea3587fea9..2b422cce7a 100644
--- a/plugins/lighthouse/src/components/AuditList/index.test.tsx
+++ b/plugins/lighthouse/src/components/AuditList/index.test.tsx
@@ -23,7 +23,11 @@ jest.mock('react-router-dom', () => {
};
});
-import { setupRequestMockHandlers, wrapInTestApp } from '@backstage/test-utils';
+import {
+ setupRequestMockHandlers,
+ TestApiRegistry,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import { fireEvent, render } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
@@ -35,20 +39,21 @@ import {
} from '../../api';
import * as data from '../../__fixtures__/website-list-response.json';
import AuditList from './index';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const { useNavigate } = jest.requireMock('react-router-dom');
const websiteListResponse = data as WebsiteListResponse;
describe('AuditList', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
const server = setupServer();
setupRequestMockHandlers(server);
beforeEach(() => {
- apis = ApiRegistry.from([
- [lighthouseApiRef, new LighthouseRestApi('http://lighthouse')],
+ apis = TestApiRegistry.from([
+ lighthouseApiRef,
+ new LighthouseRestApi('http://lighthouse'),
]);
});
diff --git a/plugins/lighthouse/src/components/AuditView/index.test.tsx b/plugins/lighthouse/src/components/AuditView/index.test.tsx
index ed1bbbcaf9..00b2843a50 100644
--- a/plugins/lighthouse/src/components/AuditView/index.test.tsx
+++ b/plugins/lighthouse/src/components/AuditView/index.test.tsx
@@ -26,7 +26,11 @@ jest.mock('react-router-dom', () => {
};
});
-import { setupRequestMockHandlers, wrapInTestApp } from '@backstage/test-utils';
+import {
+ setupRequestMockHandlers,
+ TestApiRegistry,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import { render } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
@@ -35,14 +39,14 @@ import { Audit, lighthouseApiRef, LighthouseRestApi, Website } from '../../api';
import { formatTime } from '../../utils';
import * as data from '../../__fixtures__/website-response.json';
import AuditView from './index';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const { useParams }: { useParams: jest.Mock } =
jest.requireMock('react-router-dom');
const websiteResponse = data as Website;
describe('AuditView', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
let id: string;
const server = setupServer();
@@ -55,8 +59,9 @@ describe('AuditView', () => {
),
);
- apis = ApiRegistry.from([
- [lighthouseApiRef, new LighthouseRestApi('https://lighthouse')],
+ apis = TestApiRegistry.from([
+ lighthouseApiRef,
+ new LighthouseRestApi('https://lighthouse'),
]);
id = websiteResponse.audits.find(a => a.status === 'COMPLETED')
?.id as string;
diff --git a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
index 84493e243f..59847b516e 100644
--- a/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
+++ b/plugins/lighthouse/src/components/CreateAudit/index.test.tsx
@@ -23,7 +23,11 @@ jest.mock('react-router-dom', () => {
};
});
-import { setupRequestMockHandlers, wrapInTestApp } from '@backstage/test-utils';
+import {
+ setupRequestMockHandlers,
+ TestApiRegistry,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import { fireEvent, render, waitFor } from '@testing-library/react';
import { rest } from 'msw';
import { setupServer } from 'msw/node';
@@ -32,7 +36,7 @@ import { Audit, lighthouseApiRef, LighthouseRestApi } from '../../api';
import * as data from '../../__fixtures__/create-audit-response.json';
import CreateAudit from './index';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { ErrorApi, errorApiRef } from '@backstage/core-plugin-api';
const { useNavigate }: { useNavigate: jest.Mock } =
@@ -41,17 +45,17 @@ const createAuditResponse = data as Audit;
// TODO add act() to these tests without breaking them!
describe('CreateAudit', () => {
- let apis: ApiRegistry;
+ let apis: TestApiRegistry;
let errorApi: ErrorApi;
const server = setupServer();
setupRequestMockHandlers(server);
beforeEach(() => {
errorApi = { post: jest.fn(), error$: jest.fn() };
- apis = ApiRegistry.from([
+ apis = TestApiRegistry.from(
[lighthouseApiRef, new LighthouseRestApi('http://lighthouse')],
[errorApiRef, errorApi],
- ]);
+ );
});
it('renders the form', () => {
diff --git a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx
index 64f7483081..13da0f6f4f 100644
--- a/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx
+++ b/plugins/lighthouse/src/hooks/useWebsiteForEntity.test.tsx
@@ -21,8 +21,8 @@ import { lighthouseApiRef, WebsiteListResponse } from '../api';
import * as data from '../__fixtures__/website-list-response.json';
import { useWebsiteForEntity } from './useWebsiteForEntity';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
+import { TestApiProvider } from '@backstage/test-utils';
const websiteListResponse = data as WebsiteListResponse;
const website = websiteListResponse.items[0];
@@ -55,14 +55,14 @@ describe('useWebsiteForEntity', () => {
const wrapper = ({ children }: PropsWithChildren<{}>) => {
return (
-
{children}
-
+
);
};
diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx
index 0a5a0cfef1..b6b549d934 100644
--- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx
+++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.stories.tsx
@@ -15,8 +15,8 @@
*/
import { Entity, GroupEntity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { catalogApiRef, EntityProvider } from '@backstage/plugin-catalog-react';
+import { TestApiProvider } from '@backstage/test-utils';
import { Grid } from '@material-ui/core';
import React from 'react';
import { MemoryRouter } from 'react-router';
@@ -99,12 +99,9 @@ const catalogApi = (items: Entity[]) => ({
getEntities: () => Promise.resolve({ items }),
});
-const apiRegistry = (items: Entity[]) =>
- ApiRegistry.from([[catalogApiRef, catalogApi(items)]]);
-
export const Default = () => (
-
+
@@ -112,13 +109,13 @@ export const Default = () => (
-
+
);
export const Empty = () => (
-
+
@@ -126,6 +123,6 @@ export const Empty = () => (
-
+
);
diff --git a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx
index 75cb9efd7f..b0c68d9a83 100644
--- a/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx
+++ b/plugins/org/src/components/Cards/Group/MembersList/MembersListCard.test.tsx
@@ -20,10 +20,13 @@ import {
catalogApiRef,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import {
+ renderWithEffects,
+ TestApiProvider,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import React from 'react';
import { MembersListCard } from './MembersListCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('MemberTab Test', () => {
const groupEntity: GroupEntity = {
@@ -103,17 +106,15 @@ describe('MemberTab Test', () => {
}),
};
- const apis = ApiRegistry.from([[catalogApiRef, catalogApi]]);
-
it('Display Profile Card', async () => {
const rendered = await renderWithEffects(
wrapInTestApp(
-
+
,
- ,
+ ,
),
);
diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx
index 14fed985b6..6a20035dfa 100644
--- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx
+++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.stories.tsx
@@ -15,14 +15,14 @@
*/
import { GroupEntity } from '@backstage/catalog-model';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import {
CatalogApi,
catalogApiRef,
catalogRouteRef,
EntityProvider,
} from '@backstage/plugin-catalog-react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import {
BackstageTheme,
createTheme,
@@ -86,11 +86,11 @@ const catalogApi: Partial = {
getEntities: () => Promise.resolve({ items: [serviceA, serviceB, websiteA] }),
};
-const apiRegistry = ApiRegistry.from([[catalogApiRef, catalogApi]]);
+const apis = TestApiRegistry.from([catalogApiRef, catalogApi]);
export const Default = () =>
wrapInTestApp(
-
+
@@ -123,7 +123,7 @@ const monochromeTheme = (outer: BackstageTheme) =>
export const Themed = () =>
wrapInTestApp(
-
+
diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx
index b5ffd37083..8d6fc1a002 100644
--- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx
+++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.test.tsx
@@ -21,11 +21,10 @@ import {
EntityProvider,
catalogRouteRef,
} from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { queryByText } from '@testing-library/react';
import React from 'react';
import { OwnershipCard } from './OwnershipCard';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('OwnershipCard', () => {
const groupEntity: GroupEntity = {
@@ -121,11 +120,11 @@ describe('OwnershipCard', () => {
});
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/create': catalogRouteRef,
@@ -157,11 +156,11 @@ describe('OwnershipCard', () => {
});
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/create': catalogRouteRef,
@@ -205,11 +204,11 @@ describe('OwnershipCard', () => {
});
const { getByText } = await renderInTestApp(
-
+
- ,
+ ,
{
mountedRoutes: {
'/create': catalogRouteRef,
diff --git a/plugins/pagerduty/src/components/ChangeEvents/ChangeEvents.test.tsx b/plugins/pagerduty/src/components/ChangeEvents/ChangeEvents.test.tsx
index 446c867f35..723e7b7b19 100644
--- a/plugins/pagerduty/src/components/ChangeEvents/ChangeEvents.test.tsx
+++ b/plugins/pagerduty/src/components/ChangeEvents/ChangeEvents.test.tsx
@@ -16,15 +16,15 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { ChangeEvent } from '../types';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { pagerDutyApiRef } from '../../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { ChangeEvents } from './ChangeEvents';
const mockPagerDutyApi = {
- getChangeEventsByServiceId: () => [],
+ getChangeEventsByServiceId: jest.fn(),
};
-const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]);
+const apis = TestApiRegistry.from([pagerDutyApiRef, mockPagerDutyApi]);
describe('Incidents', () => {
it('Renders an empty state when there are no change events', async () => {
diff --git a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
index 52aa9a860f..47f18002fe 100644
--- a/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
+++ b/plugins/pagerduty/src/components/Escalation/Escalation.test.tsx
@@ -16,15 +16,15 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { EscalationPolicy } from './EscalationPolicy';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { User } from '../types';
import { pagerDutyApiRef } from '../../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const mockPagerDutyApi = {
- getOnCallByPolicyId: () => [],
+ getOnCallByPolicyId: jest.fn(),
};
-const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]);
+const apis = TestApiRegistry.from([pagerDutyApiRef, mockPagerDutyApi]);
describe('Escalation', () => {
it('Handles an empty response', async () => {
diff --git a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
index d66acc0879..74e7d82e8b 100644
--- a/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
+++ b/plugins/pagerduty/src/components/Incident/Incidents.test.tsx
@@ -16,15 +16,15 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { Incidents } from './Incidents';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { pagerDutyApiRef } from '../../api';
import { Incident } from '../types';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const mockPagerDutyApi = {
- getIncidentsByServiceId: () => [],
+ getIncidentsByServiceId: jest.fn(),
};
-const apis = ApiRegistry.from([[pagerDutyApiRef, mockPagerDutyApi]]);
+const apis = TestApiRegistry.from([pagerDutyApiRef, mockPagerDutyApi]);
describe('Incidents', () => {
it('Renders an empty state when there are no incidents', async () => {
diff --git a/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx b/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
index d644aa019a..e0a857ccc7 100644
--- a/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
+++ b/plugins/pagerduty/src/components/PagerDutyCard/index.test.tsx
@@ -18,12 +18,12 @@ import { render, waitFor, fireEvent, act } from '@testing-library/react';
import { PagerDutyCard } from '../PagerDutyCard';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { pagerDutyApiRef, UnauthorizedError, PagerDutyClient } from '../../api';
import { Service, User } from '../types';
-import { alertApiRef, createApiRef } from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { alertApiRef } from '@backstage/core-plugin-api';
+import { ApiProvider } from '@backstage/core-app-api';
const mockPagerDutyApi: Partial = {
getServiceByIntegrationKey: async () => [],
@@ -31,16 +31,10 @@ const mockPagerDutyApi: Partial = {
getIncidentsByServiceId: async () => [],
};
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[pagerDutyApiRef, mockPagerDutyApi],
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
-]);
+ [alertApiRef, {}],
+);
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
kind: 'Component',
diff --git a/plugins/pagerduty/src/components/TriggerButton/index.test.tsx b/plugins/pagerduty/src/components/TriggerButton/index.test.tsx
index 21f0dd5f1d..3776bcaf1c 100644
--- a/plugins/pagerduty/src/components/TriggerButton/index.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerButton/index.test.tsx
@@ -15,16 +15,15 @@
*/
import React from 'react';
import { act, fireEvent, screen, waitFor } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { pagerDutyApiRef } from '../../api';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { TriggerButton } from './';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import {
alertApiRef,
- createApiRef,
IdentityApi,
identityApiRef,
} from '@backstage/core-plugin-api';
@@ -39,17 +38,11 @@ describe('TriggerButton', () => {
triggerAlarm: mockTriggerAlarmFn,
};
- const apis = ApiRegistry.from([
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
+ const apis = TestApiRegistry.from(
+ [alertApiRef, {}],
[identityApiRef, mockIdentityApi],
[pagerDutyApiRef, mockPagerDutyApi],
- ]);
+ );
it('renders the trigger button, opens and closes dialog', async () => {
const entity: Entity = {
diff --git a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
index 444e820b98..d6f378b4bf 100644
--- a/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/pagerduty/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -15,16 +15,15 @@
*/
import React from 'react';
import { fireEvent, act } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { pagerDutyApiRef } from '../../api';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
import { TriggerDialog } from './TriggerDialog';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import {
alertApiRef,
- createApiRef,
IdentityApi,
identityApiRef,
} from '@backstage/core-plugin-api';
@@ -39,17 +38,11 @@ describe('TriggerDialog', () => {
triggerAlarm: mockTriggerAlarmFn,
};
- const apis = ApiRegistry.from([
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
+ const apis = TestApiRegistry.from(
+ [alertApiRef, {}],
[identityApiRef, mockIdentityApi],
[pagerDutyApiRef, mockPagerDutyApi],
- ]);
+ );
it('open the dialog and trigger an alarm', async () => {
const entity: Entity = {
diff --git a/plugins/permission-node/.eslintrc.js b/plugins/permission-node/.eslintrc.js
new file mode 100644
index 0000000000..16a033dbc6
--- /dev/null
+++ b/plugins/permission-node/.eslintrc.js
@@ -0,0 +1,3 @@
+module.exports = {
+ extends: [require.resolve('@backstage/cli/config/eslint.backend')],
+};
diff --git a/plugins/permission-node/README.md b/plugins/permission-node/README.md
new file mode 100644
index 0000000000..82dfe54f9c
--- /dev/null
+++ b/plugins/permission-node/README.md
@@ -0,0 +1,7 @@
+# @backstage/plugin-permission-node
+
+> NOTE: THIS PACKAGE IS EXPERIMENTAL, HERE BE DRAGONS
+
+Common permission and authorization utilities for backend plugins. For more
+information, see the [authorization
+PRFC](https://github.com/backstage/backstage/pull/7761).
diff --git a/plugins/permission-node/api-report.md b/plugins/permission-node/api-report.md
new file mode 100644
index 0000000000..e5fe9a7087
--- /dev/null
+++ b/plugins/permission-node/api-report.md
@@ -0,0 +1,130 @@
+## API Report File for "@backstage/plugin-permission-node"
+
+> Do not edit this file. It is a report generated by [API Extractor](https://api-extractor.com/).
+
+```ts
+import { AuthorizeRequest } from '@backstage/plugin-permission-common';
+import { AuthorizeResult } from '@backstage/plugin-permission-common';
+import { BackstageIdentity } from '@backstage/plugin-auth-backend';
+import { PermissionCondition } from '@backstage/plugin-permission-common';
+import { PermissionCriteria } from '@backstage/plugin-permission-common';
+import { Router } from 'express';
+
+// @public
+export type ApplyConditionsRequest = {
+ resourceRef: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+};
+
+// @public
+export type ApplyConditionsResponse = {
+ result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
+};
+
+// @public
+export type Condition = TRule extends PermissionRule<
+ any,
+ any,
+ infer TParams
+>
+ ? (...params: TParams) => PermissionCondition
+ : never;
+
+// @public
+export type ConditionalPolicyResult = {
+ result: AuthorizeResult.CONDITIONAL;
+ conditions: {
+ pluginId: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+ };
+};
+
+// @public
+export type Conditions<
+ TRules extends Record>,
+> = {
+ [Name in keyof TRules]: Condition;
+};
+
+// @public
+export type ConditionTransformer = (
+ conditions: PermissionCriteria,
+) => PermissionCriteria;
+
+// @public
+export const createConditionExports: <
+ TResource,
+ TRules extends Record>,
+>(options: {
+ pluginId: string;
+ resourceType: string;
+ rules: TRules;
+}) => {
+ conditions: Conditions;
+ createConditions: (conditions: PermissionCriteria) => {
+ pluginId: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+ };
+};
+
+// @public
+export const createConditionFactory: (
+ rule: PermissionRule,
+) => (...params: TParams) => {
+ rule: string;
+ params: TParams;
+};
+
+// @public
+export const createConditionTransformer: <
+ TQuery,
+ TRules extends PermissionRule[],
+>(
+ permissionRules: [...TRules],
+) => ConditionTransformer;
+
+// @public
+export const createPermissionIntegrationRouter: ({
+ resourceType,
+ rules,
+ getResource,
+}: {
+ resourceType: string;
+ rules: PermissionRule[];
+ getResource: (resourceRef: string) => Promise;
+}) => Router;
+
+// @public
+export interface PermissionPolicy {
+ // (undocumented)
+ handle(
+ request: PolicyAuthorizeRequest,
+ user?: BackstageIdentity,
+ ): Promise;
+}
+
+// @public
+export type PermissionRule<
+ TResource,
+ TQuery,
+ TParams extends unknown[] = unknown[],
+> = {
+ name: string;
+ description: string;
+ apply(resource: TResource, ...params: TParams): boolean;
+ toQuery(...params: TParams): PermissionCriteria;
+};
+
+// @public
+export type PolicyAuthorizeRequest = Omit;
+
+// @public
+export type PolicyResult =
+ | {
+ result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
+ }
+ | ConditionalPolicyResult;
+```
diff --git a/plugins/permission-node/package.json b/plugins/permission-node/package.json
new file mode 100644
index 0000000000..e22e2d2de4
--- /dev/null
+++ b/plugins/permission-node/package.json
@@ -0,0 +1,46 @@
+{
+ "name": "@backstage/plugin-permission-node",
+ "description": "Common permission and authorization utilities for backend plugins",
+ "version": "0.0.0",
+ "main": "src/index.ts",
+ "types": "src/index.ts",
+ "license": "Apache-2.0",
+ "publishConfig": {
+ "access": "public",
+ "main": "dist/index.cjs.js",
+ "types": "dist/index.d.ts"
+ },
+ "homepage": "https://backstage.io",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/backstage/backstage",
+ "directory": "plugins/permission-node"
+ },
+ "keywords": [
+ "backstage",
+ "permissions"
+ ],
+ "scripts": {
+ "build": "backstage-cli backend:build",
+ "lint": "backstage-cli lint",
+ "test": "backstage-cli test",
+ "prepack": "backstage-cli prepack",
+ "postpack": "backstage-cli postpack",
+ "clean": "backstage-cli clean"
+ },
+ "dependencies": {
+ "@backstage/plugin-auth-backend": "^0.4.6",
+ "@backstage/plugin-permission-common": "^0.1.0",
+ "@types/express": "^4.17.6",
+ "express": "^4.17.1",
+ "zod": "^3.11.6"
+ },
+ "devDependencies": {
+ "@backstage/cli": "^0.9.0",
+ "@types/supertest": "^2.0.8",
+ "supertest": "^6.1.3"
+ },
+ "files": [
+ "dist"
+ ]
+}
diff --git a/plugins/permission-node/src/index.ts b/plugins/permission-node/src/index.ts
new file mode 100644
index 0000000000..39527bac71
--- /dev/null
+++ b/plugins/permission-node/src/index.ts
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2021 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.
+ */
+
+/**
+ * Common permission and authorization utilities for backend plugins
+ *
+ * @packageDocumentation
+ */
+export * from './integration';
+export * from './policy';
+export * from './types';
diff --git a/plugins/permission-node/src/integration/createConditionExports.test.ts b/plugins/permission-node/src/integration/createConditionExports.test.ts
new file mode 100644
index 0000000000..6f0f8f8632
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionExports.test.ts
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2021 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 { createConditionExports } from './createConditionExports';
+
+const testIntegration = () =>
+ createConditionExports({
+ pluginId: 'test-plugin',
+ resourceType: 'test-resource',
+ rules: {
+ testRule1: {
+ name: 'testRule1',
+ description: 'Test rule 1',
+ apply: jest.fn(
+ (_resource: any, _firstParam: string, _secondParam: number) => true,
+ ),
+ toQuery: jest.fn((firstParam: string, secondParam: number) => ({
+ query: 'testRule1',
+ params: [firstParam, secondParam],
+ })),
+ },
+ testRule2: {
+ name: 'testRule2',
+ description: 'Test rule 2',
+ apply: jest.fn((_firstParam: object) => false),
+ toQuery: jest.fn((firstParam: object) => ({
+ query: 'testRule2',
+ params: [firstParam],
+ })),
+ },
+ },
+ });
+
+describe('createConditionExports', () => {
+ describe('conditions', () => {
+ it('creates condition factories for the supplied rules', () => {
+ const { conditions } = testIntegration();
+
+ expect(conditions.testRule1('a', 1)).toEqual({
+ rule: 'testRule1',
+ params: ['a', 1],
+ });
+
+ expect(conditions.testRule2({ baz: 'quux' })).toEqual({
+ rule: 'testRule2',
+ params: [{ baz: 'quux' }],
+ });
+ });
+ });
+
+ describe('createConditions', () => {
+ it('wraps conditions in an object with resourceType and pluginId', () => {
+ const { createConditions } = testIntegration();
+
+ expect(
+ createConditions({ allOf: [{ rule: 'testRule1', params: ['a', 1] }] }),
+ ).toEqual({
+ pluginId: 'test-plugin',
+ resourceType: 'test-resource',
+ conditions: {
+ allOf: [{ rule: 'testRule1', params: ['a', 1] }],
+ },
+ });
+ });
+ });
+});
diff --git a/plugins/permission-node/src/integration/createConditionExports.ts b/plugins/permission-node/src/integration/createConditionExports.ts
new file mode 100644
index 0000000000..30cff6628a
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionExports.ts
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2021 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 {
+ PermissionCondition,
+ PermissionCriteria,
+} from '@backstage/plugin-permission-common';
+import { PermissionRule } from '../types';
+import { createConditionFactory } from './createConditionFactory';
+
+/**
+ * A utility type for mapping a single {@link PermissionRule} to its
+ * corresponding {@link @backstage/plugin-permission-common#PermissionCondition}.
+ *
+ * @public
+ */
+export type Condition = TRule extends PermissionRule<
+ any,
+ any,
+ infer TParams
+>
+ ? (...params: TParams) => PermissionCondition
+ : never;
+
+/**
+ * A utility type for mapping {@link PermissionRule}s to their corresponding
+ * {@link @backstage/plugin-permission-common#PermissionCondition}s.
+ *
+ * @public
+ */
+export type Conditions<
+ TRules extends Record>,
+> = {
+ [Name in keyof TRules]: Condition;
+};
+
+/**
+ * Creates the recommended condition-related exports for a given plugin based on the built-in
+ * {@link PermissionRule}s it supports.
+ *
+ * @remarks
+ *
+ * The function returns a `conditions` object containing a
+ * {@link @backstage/plugin-permission-common#PermissionCondition} factory for each of the
+ * supplied {@link PermissionRule}s, along with a `createConditions` function which builds the
+ * wrapper object needed to enclose conditions when authoring {@link PermissionPolicy} implementations.
+ *
+ * Plugin authors should generally call this method with all the built-in {@link PermissionRule}s
+ * the plugin supports, and export the resulting `conditions` object and `createConditions`
+ * function so that they can be used by {@link PermissionPolicy} authors.
+ *
+ * @public
+ */
+export const createConditionExports = <
+ TResource,
+ TRules extends Record>,
+>(options: {
+ pluginId: string;
+ resourceType: string;
+ rules: TRules;
+}): {
+ conditions: Conditions;
+ createConditions: (conditions: PermissionCriteria) => {
+ pluginId: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+ };
+} => {
+ const { pluginId, resourceType, rules } = options;
+
+ return {
+ conditions: Object.entries(rules).reduce(
+ (acc, [key, rule]) => ({
+ ...acc,
+ [key]: createConditionFactory(rule),
+ }),
+ {} as Conditions,
+ ),
+ createConditions: (
+ conditions: PermissionCriteria,
+ ) => ({
+ pluginId,
+ resourceType,
+ conditions,
+ }),
+ };
+};
diff --git a/plugins/permission-node/src/integration/createConditionFactory.test.ts b/plugins/permission-node/src/integration/createConditionFactory.test.ts
new file mode 100644
index 0000000000..8dd43f5da6
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionFactory.test.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 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 { createConditionFactory } from './createConditionFactory';
+
+describe('createConditionFactory', () => {
+ const testRule = {
+ name: 'test-rule',
+ description: 'test-description',
+ apply: jest.fn(),
+ toQuery: jest.fn(),
+ };
+
+ it('returns a function', () => {
+ expect(createConditionFactory(testRule)).toEqual(expect.any(Function));
+ });
+
+ describe('return value', () => {
+ it('constructs a condition with the rule name and supplied params', () => {
+ const conditionFactory = createConditionFactory(testRule);
+ expect(conditionFactory('a', 'b', 1, 2)).toEqual({
+ rule: 'test-rule',
+ params: ['a', 'b', 1, 2],
+ });
+ });
+ });
+});
diff --git a/plugins/permission-node/src/integration/createConditionFactory.ts b/plugins/permission-node/src/integration/createConditionFactory.ts
new file mode 100644
index 0000000000..84a8dc86a6
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionFactory.ts
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2021 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 { PermissionRule } from '../types';
+
+/**
+ * Creates a condition factory function for a given authorization rule and parameter types.
+ *
+ * @remarks
+ *
+ * For example, an isEntityOwner rule for catalog entities might take an array of entityRef strings.
+ * The rule itself defines _how_ to check a given resource, whereas a condition also includes _what_
+ * to verify.
+ *
+ * Plugin authors should generally use the {@link createConditionExports} in order to efficiently
+ * create multiple condition factories. This helper should generally only be used to construct
+ * condition factories for third-party rules that aren't part of the backend plugin with which
+ * they're intended to integrate.
+ *
+ * @public
+ */
+export const createConditionFactory =
+ (rule: PermissionRule) =>
+ (...params: TParams) => ({
+ rule: rule.name,
+ params,
+ });
diff --git a/plugins/permission-node/src/integration/createConditionTransformer.test.ts b/plugins/permission-node/src/integration/createConditionTransformer.test.ts
new file mode 100644
index 0000000000..8a93a4ea27
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionTransformer.test.ts
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2021 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 {
+ PermissionCondition,
+ PermissionCriteria,
+} from '@backstage/plugin-permission-common';
+import { createConditionTransformer } from './createConditionTransformer';
+
+const transformConditions = createConditionTransformer([
+ {
+ name: 'test-rule-1',
+ description: 'Test rule 1',
+ apply: jest.fn(),
+ toQuery: jest.fn(
+ (firstParam: string, secondParam: number) =>
+ `test-rule-1:${firstParam}/${secondParam}`,
+ ),
+ },
+ {
+ name: 'test-rule-2',
+ description: 'Test rule 2',
+ apply: jest.fn(),
+ toQuery: jest.fn(
+ (firstParam: object) => `test-rule-2:${JSON.stringify(firstParam)}`,
+ ),
+ },
+]);
+
+describe('createConditionTransformer', () => {
+ const testCases: {
+ conditions: PermissionCriteria;
+ expectedResult: PermissionCriteria;
+ }[] = [
+ {
+ conditions: { rule: 'test-rule-1', params: ['abc', 123] },
+ expectedResult: 'test-rule-1:abc/123',
+ },
+ {
+ conditions: { rule: 'test-rule-2', params: [{ foo: 0 }] },
+ expectedResult: 'test-rule-2:{"foo":0}',
+ },
+ {
+ conditions: {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+ expectedResult: {
+ anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
+ },
+ },
+ {
+ conditions: {
+ allOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+ expectedResult: {
+ allOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
+ },
+ },
+ {
+ conditions: {
+ not: { rule: 'test-rule-2', params: [{}] },
+ },
+ expectedResult: {
+ not: 'test-rule-2:{}',
+ },
+ },
+ {
+ conditions: {
+ allOf: [
+ {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+ {
+ not: {
+ allOf: [
+ { rule: 'test-rule-1', params: ['b', 2] },
+ { rule: 'test-rule-2', params: [{ c: 3 }] },
+ ],
+ },
+ },
+ ],
+ },
+ expectedResult: {
+ allOf: [
+ {
+ anyOf: ['test-rule-1:a/1', 'test-rule-2:{}'],
+ },
+ {
+ not: {
+ allOf: ['test-rule-1:b/2', 'test-rule-2:{"c":3}'],
+ },
+ },
+ ],
+ },
+ },
+ {
+ conditions: {
+ allOf: [
+ {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{ b: 2 }] },
+ ],
+ },
+ {
+ not: {
+ allOf: [
+ { rule: 'test-rule-1', params: ['c', 3] },
+ { not: { rule: 'test-rule-2', params: [{ d: 4 }] } },
+ ],
+ },
+ },
+ ],
+ },
+ expectedResult: {
+ allOf: [
+ {
+ anyOf: ['test-rule-1:a/1', 'test-rule-2:{"b":2}'],
+ },
+ {
+ not: {
+ allOf: ['test-rule-1:c/3', { not: 'test-rule-2:{"d":4}' }],
+ },
+ },
+ ],
+ },
+ },
+ ];
+
+ it.each(testCases)(
+ 'works with criteria %#',
+ ({ conditions, expectedResult }) => {
+ expect(transformConditions(conditions)).toEqual(expectedResult);
+ },
+ );
+});
diff --git a/plugins/permission-node/src/integration/createConditionTransformer.ts b/plugins/permission-node/src/integration/createConditionTransformer.ts
new file mode 100644
index 0000000000..0e736b832c
--- /dev/null
+++ b/plugins/permission-node/src/integration/createConditionTransformer.ts
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2021 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 {
+ PermissionCondition,
+ PermissionCriteria,
+} from '@backstage/plugin-permission-common';
+import { PermissionRule } from '../types';
+import {
+ createGetRule,
+ isAndCriteria,
+ isNotCriteria,
+ isOrCriteria,
+} from './util';
+
+const mapConditions = (
+ criteria: PermissionCriteria,
+ getRule: (name: string) => PermissionRule,
+): PermissionCriteria => {
+ if (isAndCriteria(criteria)) {
+ return {
+ allOf: criteria.allOf.map(child => mapConditions(child, getRule)),
+ };
+ } else if (isOrCriteria(criteria)) {
+ return {
+ anyOf: criteria.anyOf.map(child => mapConditions(child, getRule)),
+ };
+ } else if (isNotCriteria(criteria)) {
+ return {
+ not: mapConditions(criteria.not, getRule),
+ };
+ }
+
+ return getRule(criteria.rule).toQuery(...criteria.params);
+};
+
+/**
+ * A function which accepts {@link @backstage/plugin-permission-common#PermissionCondition}s
+ * logically grouped in a {@link @backstage/plugin-permission-common#PermissionCriteria}
+ * object, and transforms the {@link @backstage/plugin-permission-common#PermissionCondition}s
+ * into plugin specific query fragments while retaining the enclosing criteria shape.
+ *
+ * @public
+ */
+export type ConditionTransformer = (
+ conditions: PermissionCriteria,
+) => PermissionCriteria;
+
+/**
+ * A higher-order helper function which accepts an array of
+ * {@link PermissionRule}s, and returns a {@link ConditionTransformer}
+ * which transforms input conditions into equivalent plugin-specific
+ * query fragments using the supplied rules.
+ *
+ * @public
+ */
+export const createConditionTransformer = <
+ TQuery,
+ TRules extends PermissionRule[],
+>(
+ permissionRules: [...TRules],
+): ConditionTransformer => {
+ const getRule = createGetRule(permissionRules);
+
+ return conditions => mapConditions(conditions, getRule);
+};
diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts
new file mode 100644
index 0000000000..af216dfcb8
--- /dev/null
+++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.test.ts
@@ -0,0 +1,210 @@
+/*
+ * Copyright 2021 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 { AuthorizeResult } from '@backstage/plugin-permission-common';
+import express, { Express, Router } from 'express';
+import request from 'supertest';
+import { createPermissionIntegrationRouter } from './createPermissionIntegrationRouter';
+
+const mockGetResource: jest.MockedFunction<
+ (resourceRef: string) => Promise
+> = jest.fn((resourceRef: string) =>
+ Promise.resolve({
+ resourceRef,
+ }),
+);
+
+const testRule1 = {
+ name: 'test-rule-1',
+ description: 'Test rule 1',
+ apply: jest.fn(
+ (_resource: any, _firstParam: string, _secondParam: number) => true,
+ ),
+ toQuery: jest.fn(),
+};
+
+const testRule2 = {
+ name: 'test-rule-2',
+ description: 'Test rule 2',
+ apply: jest.fn((_firstParam: object) => false),
+ toQuery: jest.fn(),
+};
+
+describe('createPermissionIntegrationRouter', () => {
+ let app: Express;
+ let router: Router;
+
+ beforeEach(() => {
+ router = createPermissionIntegrationRouter({
+ resourceType: 'test-resource',
+ getResource: mockGetResource,
+ rules: [testRule1, testRule2],
+ });
+
+ app = express().use(router);
+ });
+
+ it('works', async () => {
+ expect(router).toBeDefined();
+ });
+
+ describe('POST /permissions/apply-conditions', () => {
+ it.each([
+ { rule: 'test-rule-1', params: ['abc', 123] },
+ {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+
+ {
+ not: { rule: 'test-rule-2', params: [{}] },
+ },
+ {
+ allOf: [
+ {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+ {
+ not: {
+ allOf: [
+ { rule: 'test-rule-1', params: ['b', 2] },
+ { rule: 'test-rule-2', params: [{ c: 3 }] },
+ ],
+ },
+ },
+ ],
+ },
+ ])('returns 200/ALLOW when criteria match (case %#)', async conditions => {
+ const response = await request(app)
+ .post('/permissions/apply-conditions')
+ .send({
+ resourceRef: 'default:test/resource',
+ resourceType: 'test-resource',
+ conditions,
+ });
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ result: AuthorizeResult.ALLOW });
+ });
+
+ it.each([
+ { rule: 'test-rule-2', params: [{ foo: 0 }] },
+ {
+ allOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{}] },
+ ],
+ },
+ {
+ allOf: [
+ {
+ anyOf: [
+ { rule: 'test-rule-1', params: ['a', 1] },
+ { rule: 'test-rule-2', params: [{ b: 2 }] },
+ ],
+ },
+ {
+ not: {
+ allOf: [
+ { rule: 'test-rule-1', params: ['c', 3] },
+ { not: { rule: 'test-rule-2', params: [{ d: 4 }] } },
+ ],
+ },
+ },
+ ],
+ },
+ ])(
+ 'returns 200/DENY when criteria do not match (case %#)',
+ async conditions => {
+ const response = await request(app)
+ .post('/permissions/apply-conditions')
+ .send({
+ resourceRef: 'default:test/resource',
+ resourceType: 'test-resource',
+ conditions,
+ });
+
+ expect(response.status).toEqual(200);
+ expect(response.body).toEqual({ result: AuthorizeResult.DENY });
+ },
+ );
+
+ it('returns 400 when called with incorrect resource type', async () => {
+ const response = await request(app)
+ .post('/permissions/apply-conditions')
+ .send({
+ resourceRef: 'default:test/resource',
+ resourceType: 'test-incorrect-resource',
+ conditions: {
+ anyOf: [],
+ },
+ });
+
+ expect(response.status).toEqual(400);
+ expect(response.error && response.error.text).toMatch(
+ /unexpected resource type: test-incorrect-resource/i,
+ );
+ });
+
+ it('returns 400 when resource is not found', async () => {
+ mockGetResource.mockReturnValueOnce(Promise.resolve(undefined));
+
+ const response = await request(app)
+ .post('/permissions/apply-conditions')
+ .send({
+ resourceRef: 'default:test/resource',
+ resourceType: 'test-resource',
+ conditions: {
+ not: {
+ rule: 'testRule1',
+ params: ['a', 1],
+ },
+ },
+ });
+
+ expect(response.status).toEqual(400);
+ expect(response.error && response.error.text).toMatch(
+ /resource for ref default:test\/resource not found/i,
+ );
+ });
+
+ it.each([
+ undefined,
+ {},
+ { resourceType: 'test-resource-type' },
+ { resourceRef: 'test/resource-ref' },
+ {
+ resourceType: 'test-resource-type',
+ resourceRef: 'test/resource-ref',
+ },
+ { conditions: { anyOf: [] } },
+ ])(`returns 400 for invalid input %#`, async input => {
+ const response = await request(app)
+ .post('/permissions/apply-conditions')
+ .send(input);
+
+ expect(response.status).toEqual(400);
+ expect(response.error && response.error.text).toMatch(
+ /invalid request body/i,
+ );
+ });
+ });
+});
diff --git a/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts
new file mode 100644
index 0000000000..99e282b144
--- /dev/null
+++ b/plugins/permission-node/src/integration/createPermissionIntegrationRouter.ts
@@ -0,0 +1,177 @@
+/*
+ * Copyright 2021 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 express, { Response, Router } from 'express';
+import { z } from 'zod';
+import {
+ AuthorizeResult,
+ PermissionCondition,
+ PermissionCriteria,
+} from '@backstage/plugin-permission-common';
+import { PermissionRule } from '../types';
+import {
+ createGetRule,
+ isAndCriteria,
+ isNotCriteria,
+ isOrCriteria,
+} from './util';
+
+const permissionCriteriaSchema: z.ZodSchema<
+ PermissionCriteria
+> = z.lazy(() =>
+ z.union([
+ z.object({ anyOf: z.array(permissionCriteriaSchema) }),
+ z.object({ allOf: z.array(permissionCriteriaSchema) }),
+ z.object({ not: permissionCriteriaSchema }),
+ z.object({
+ rule: z.string(),
+ params: z.array(z.unknown()),
+ }),
+ ]),
+);
+
+const applyConditionsRequestSchema = z.object({
+ resourceRef: z.string(),
+ resourceType: z.string(),
+ conditions: permissionCriteriaSchema,
+});
+
+/**
+ * A request to load the referenced resource and apply conditions in order to
+ * finalize a conditional authorization response.
+ *
+ * @public
+ */
+export type ApplyConditionsRequest = {
+ resourceRef: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+};
+
+/**
+ * The result of applying the conditions, expressed as a definitive authorize
+ * result of ALLOW or DENY.
+ *
+ * @public
+ */
+export type ApplyConditionsResponse = {
+ result: AuthorizeResult.ALLOW | AuthorizeResult.DENY;
+};
+
+const applyConditions = (
+ criteria: PermissionCriteria,
+ resource: TResource,
+ getRule: (name: string) => PermissionRule,
+): boolean => {
+ if (isAndCriteria(criteria)) {
+ return criteria.allOf.every(child =>
+ applyConditions(child, resource, getRule),
+ );
+ } else if (isOrCriteria(criteria)) {
+ return criteria.anyOf.some(child =>
+ applyConditions(child, resource, getRule),
+ );
+ } else if (isNotCriteria(criteria)) {
+ return !applyConditions(criteria.not, resource, getRule);
+ }
+
+ return getRule(criteria.rule).apply(resource, ...criteria.params);
+};
+
+/**
+ * Create an express Router which provides an authorization route to allow integration between the
+ * permission backend and other Backstage backend plugins. Plugin owners that wish to support
+ * conditional authorization for their resources should add the router created by this function
+ * to their express app inside their `createRouter` implementation.
+ *
+ * @remarks
+ *
+ * To make this concrete, we can use the Backstage software catalog as an example. The catalog has
+ * conditional rules around access to specific _entities_ in the catalog. The _type_ of resource is
+ * captured here as `resourceType`, a string identifier (`catalog-entity` in this example) that can
+ * be provided with permission definitions. This is merely a _type_ to verify that conditions in an
+ * authorization policy are constructed correctly, not a reference to a specific resource.
+ *
+ * The `rules` parameter is an array of {@link PermissionRule}s that introduce conditional
+ * filtering logic for resources; for the catalog, these are things like `isEntityOwner` or
+ * `hasAnnotation`. Rules describe how to filter a list of resources, and the `conditions` returned
+ * allow these rules to be applied with specific parameters (such as 'group:default/team-a', or
+ * 'backstage.io/edit-url').
+ *
+ * The `getResource` argument should load a resource by reference. For the catalog, this is an
+ * {@link @backstage/catalog-model#EntityRef}. For other plugins, this can be any serialized format.
+ * This is used to construct the `createPermissionIntegrationRouter`, a function to add an
+ * authorization route to your backend plugin. This route will be called by the `permission-backend`
+ * when authorization conditions relating to this plugin need to be evaluated.
+ * @public
+ */
+export const createPermissionIntegrationRouter = ({
+ resourceType,
+ rules,
+ getResource,
+}: {
+ resourceType: string;
+ rules: PermissionRule[];
+ getResource: (resourceRef: string) => Promise;
+}): Router => {
+ const router = Router();
+
+ const getRule = createGetRule(rules);
+
+ router.post(
+ '/permissions/apply-conditions',
+ express.json(),
+ async (
+ req,
+ res: Response<
+ | {
+ result: Omit;
+ }
+ | string
+ >,
+ ) => {
+ const parseResult = applyConditionsRequestSchema.safeParse(req.body);
+
+ if (!parseResult.success) {
+ return res.status(400).send(`Invalid request body.`);
+ }
+
+ const { data: body } = parseResult;
+
+ if (body.resourceType !== resourceType) {
+ return res
+ .status(400)
+ .send(`Unexpected resource type: ${body.resourceType}.`);
+ }
+
+ const resource = await getResource(body.resourceRef);
+
+ if (!resource) {
+ return res
+ .status(400)
+ .send(`Resource for ref ${body.resourceRef} not found.`);
+ }
+
+ return res.status(200).json({
+ result: applyConditions(body.conditions, resource, getRule)
+ ? AuthorizeResult.ALLOW
+ : AuthorizeResult.DENY,
+ });
+ },
+ );
+
+ return router;
+};
diff --git a/plugins/permission-node/src/integration/index.ts b/plugins/permission-node/src/integration/index.ts
new file mode 100644
index 0000000000..f070d57c8f
--- /dev/null
+++ b/plugins/permission-node/src/integration/index.ts
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2021 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.
+ */
+
+export * from './createConditionFactory';
+export * from './createConditionExports';
+export * from './createConditionTransformer';
+export * from './createPermissionIntegrationRouter';
diff --git a/plugins/permission-node/src/integration/util.test.ts b/plugins/permission-node/src/integration/util.test.ts
new file mode 100644
index 0000000000..d27c0a4243
--- /dev/null
+++ b/plugins/permission-node/src/integration/util.test.ts
@@ -0,0 +1,86 @@
+/*
+ * Copyright 2021 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 {
+ createGetRule,
+ isAndCriteria,
+ isNotCriteria,
+ isOrCriteria,
+} from './util';
+
+describe('permission integration utils', () => {
+ describe('createGetRule', () => {
+ let getRule: ReturnType;
+
+ const testRule1 = {
+ name: 'test-rule-1',
+ description: 'Test rule 1',
+ apply: jest.fn(),
+ toQuery: jest.fn(),
+ };
+
+ const testRule2 = {
+ name: 'test-rule-2',
+ description: 'Test rule 2',
+ apply: jest.fn(),
+ toQuery: jest.fn(),
+ };
+
+ beforeEach(() => {
+ getRule = createGetRule([testRule1, testRule2]);
+ });
+
+ it('returns the rule matching the supplied name', () => {
+ expect(getRule('test-rule-1')).toBe(testRule1);
+ });
+
+ it('throws if there is no rule for the supplied name', () => {
+ expect(() => getRule('test-rule-3')).toThrowError(
+ /unexpected permission rule/i,
+ );
+ });
+ });
+
+ describe('isOrCriteria', () => {
+ it('returns true if input has a top-level "anyOf" property', () => {
+ expect(isOrCriteria({ anyOf: { not: { allOf: [] } } })).toEqual(true);
+ });
+
+ it('returns false if input does not have a top-level "anyOf" property', () => {
+ expect(isOrCriteria({ allOf: { not: { anyOf: [] } } })).toEqual(false);
+ });
+ });
+
+ describe('isAndCriteria', () => {
+ it('returns true if input has a top-level "allOf" property', () => {
+ expect(isAndCriteria({ allOf: { not: { anyOf: [] } } })).toEqual(true);
+ });
+
+ it('returns false if input does not have a top-level "allOf" property', () => {
+ expect(isAndCriteria({ anyOf: { not: { allOf: [] } } })).toEqual(false);
+ });
+ });
+
+ describe('isNotCriteria', () => {
+ it('returns true if input has a top-level "not" property', () => {
+ expect(isNotCriteria({ not: { allOf: [{ anyOf: [] }] } })).toEqual(true);
+ });
+
+ it('returns false if input does not have a top-level "not" property', () => {
+ expect(isNotCriteria({ anyOf: { not: { allOf: [] } } })).toEqual(false);
+ });
+ });
+});
diff --git a/plugins/permission-node/src/integration/util.ts b/plugins/permission-node/src/integration/util.ts
new file mode 100644
index 0000000000..d9457cfefc
--- /dev/null
+++ b/plugins/permission-node/src/integration/util.ts
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2021 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 { PermissionCriteria } from '@backstage/plugin-permission-common';
+import { PermissionRule } from '../types';
+
+export const isAndCriteria = (
+ filter: PermissionCriteria,
+): filter is { allOf: PermissionCriteria[] } =>
+ Object.prototype.hasOwnProperty.call(filter, 'allOf');
+
+export const isOrCriteria = (
+ filter: PermissionCriteria,
+): filter is { anyOf: PermissionCriteria[] } =>
+ Object.prototype.hasOwnProperty.call(filter, 'anyOf');
+
+export const isNotCriteria = (
+ filter: PermissionCriteria,
+): filter is { not: PermissionCriteria } =>
+ Object.prototype.hasOwnProperty.call(filter, 'not');
+
+export const createGetRule = (
+ rules: PermissionRule[],
+) => {
+ const rulesMap = new Map(Object.values(rules).map(rule => [rule.name, rule]));
+
+ return (name: string): PermissionRule => {
+ const rule = rulesMap.get(name);
+
+ if (!rule) {
+ throw new Error(`Unexpected permission rule: ${name}`);
+ }
+
+ return rule;
+ };
+};
diff --git a/plugins/permission-node/src/policy/index.ts b/plugins/permission-node/src/policy/index.ts
new file mode 100644
index 0000000000..f04c2a094c
--- /dev/null
+++ b/plugins/permission-node/src/policy/index.ts
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2021 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.
+ */
+
+export type {
+ ConditionalPolicyResult,
+ PermissionPolicy,
+ PolicyAuthorizeRequest,
+ PolicyResult,
+} from './types';
diff --git a/plugins/permission-node/src/policy/types.ts b/plugins/permission-node/src/policy/types.ts
new file mode 100644
index 0000000000..44380bad26
--- /dev/null
+++ b/plugins/permission-node/src/policy/types.ts
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2021 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 {
+ AuthorizeRequest,
+ AuthorizeResult,
+ PermissionCondition,
+ PermissionCriteria,
+} from '@backstage/plugin-permission-common';
+import { BackstageIdentity } from '@backstage/plugin-auth-backend';
+
+/**
+ * An authorization request to be evaluated by the {@link PermissionPolicy}.
+ *
+ * @remarks
+ *
+ * This differs from {@link @backstage/permission-common#AuthorizeRequest} in that `resourceRef`
+ * should never be provided. This forces policies to be written in a way that's compatible with
+ * filtering collections of resources at data load time.
+ *
+ * @public
+ */
+export type PolicyAuthorizeRequest = Omit;
+
+/**
+ * A conditional result to an authorization request, returned by the {@link PermissionPolicy}.
+ *
+ * @remarks
+ *
+ * This indicates that the policy allows authorization for the request, given that the returned
+ * conditions hold when evaluated. The conditions will be evaluated by the corresponding plugin
+ * which knows about the referenced permission rules.
+ *
+ * Similar to {@link @backstage/permission-common#AuthorizeResult}, but with the plugin and resource
+ * identifiers needed to evaluate the returned conditions.
+ * @public
+ */
+export type ConditionalPolicyResult = {
+ result: AuthorizeResult.CONDITIONAL;
+ conditions: {
+ pluginId: string;
+ resourceType: string;
+ conditions: PermissionCriteria;
+ };
+};
+
+/**
+ * The result of evaluating an authorization request with a {@link PermissionPolicy}.
+ *
+ * @public
+ */
+export type PolicyResult =
+ | { result: AuthorizeResult.ALLOW | AuthorizeResult.DENY }
+ | ConditionalPolicyResult;
+
+/**
+ * A policy to evaluate authorization requests for any permissioned action performed in Backstage.
+ *
+ * @remarks
+ *
+ * This takes as input a permission and an optional Backstage identity, and should return ALLOW if
+ * the user is permitted to execute that action; otherwise DENY. For permissions relating to
+ * resources, such a catalog entities, a conditional response can also be returned. This states
+ * that the action is allowed if the conditions provided hold true.
+ *
+ * Conditions are a rule, and parameters to evaluate against that rule. For example, the rule might
+ * be `isOwner` and the parameters a collection of entityRefs; if one of the entityRefs matches
+ * the `owner` field on a catalog entity, this would resolve to ALLOW.
+ *
+ * @public
+ */
+export interface PermissionPolicy {
+ handle(
+ request: PolicyAuthorizeRequest,
+ user?: BackstageIdentity,
+ ): Promise;
+}
diff --git a/plugins/permission-node/src/setupTests.ts b/plugins/permission-node/src/setupTests.ts
new file mode 100644
index 0000000000..a330613afb
--- /dev/null
+++ b/plugins/permission-node/src/setupTests.ts
@@ -0,0 +1,17 @@
+/*
+ * Copyright 2021 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.
+ */
+
+export {};
diff --git a/plugins/permission-node/src/types.ts b/plugins/permission-node/src/types.ts
new file mode 100644
index 0000000000..678befc99c
--- /dev/null
+++ b/plugins/permission-node/src/types.ts
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2021 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 type { PermissionCriteria } from '@backstage/plugin-permission-common';
+
+/**
+ * A conditional rule that can be provided in an
+ * {@link @backstage/permission-common#AuthorizeResult} response to an authorization request.
+ *
+ * @remarks
+ *
+ * Rules can either be evaluated against a resource loaded in memory, or used as filters when
+ * loading a collection of resources from a data source. The `apply` and `toQuery` methods implement
+ * these two concepts.
+ *
+ * The two operations should always have the same logical result. If they don’t, the effective
+ * outcome of an authorization operation will sometimes differ depending on how the authorization
+ * check was performed.
+ *
+ * @public
+ */
+export type PermissionRule<
+ TResource,
+ TQuery,
+ TParams extends unknown[] = unknown[],
+> = {
+ name: string;
+ description: string;
+
+ /**
+ * Apply this rule to a resource already loaded from a backing data source. The params are
+ * arguments supplied for the rule; for example, a rule could be `isOwner` with entityRefs as the
+ * params.
+ */
+ apply(resource: TResource, ...params: TParams): boolean;
+
+ /**
+ * Translate this rule to criteria suitable for use in querying a backing data store. The criteria
+ * can be used for loading a collection of resources efficiently with conditional criteria already
+ * applied.
+ */
+ toQuery(...params: TParams): PermissionCriteria;
+};
diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json
index 374cfeffd6..36ac76c95f 100644
--- a/plugins/scaffolder-backend/package.json
+++ b/plugins/scaffolder-backend/package.json
@@ -40,8 +40,8 @@
"@backstage/plugin-scaffolder-common": "^0.1.1",
"@backstage/plugin-scaffolder-backend-module-cookiecutter": "^0.1.4",
"@backstage/types": "^0.1.1",
- "@gitbeaker/core": "^30.2.0",
- "@gitbeaker/node": "^30.2.0",
+ "@gitbeaker/core": "^34.6.0",
+ "@gitbeaker/node": "^34.6.0",
"@octokit/rest": "^18.5.3",
"@octokit/webhooks": "^9.14.1",
"@types/express": "^4.17.6",
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
similarity index 95%
rename from plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
rename to plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
index 1f7877791c..b2e2e85a6c 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/DefaultWorkflowRunner.test.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.test.ts
@@ -270,6 +270,31 @@ describe('DefaultWorkflowRunner', () => {
);
});
+ it('should not try and parse something that is not parsable', async () => {
+ jest.spyOn(logger, 'error');
+ const task = createMockTaskWithSpec({
+ apiVersion: 'scaffolder.backstage.io/v1beta3',
+ steps: [
+ {
+ id: 'test',
+ name: 'name',
+ action: 'jest-mock-action',
+ input: {
+ foo: 'bob',
+ },
+ },
+ ],
+ output: {},
+ parameters: {
+ input: 'BACKSTAGE',
+ },
+ });
+
+ await runner.execute(task);
+
+ expect(logger.error).not.toHaveBeenCalled();
+ });
+
it('should keep the original types for the input and not parse things that arent meant to be parsed', async () => {
const task = createMockTaskWithSpec({
apiVersion: 'scaffolder.backstage.io/v1beta3',
diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
index c66d967103..5b42eef459 100644
--- a/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
+++ b/plugins/scaffolder-backend/src/scaffolder/tasks/NunjucksWorkflowRunner.ts
@@ -114,9 +114,10 @@ export class NunjucksWorkflowRunner implements WorkflowRunner {
private isSingleTemplateString(input: string) {
const { parser, nodes } = require('nunjucks');
const parsed = parser.parse(input, {}, this.nunjucksOptions);
+
return (
parsed.children.length === 1 &&
- !(parsed.children[0] instanceof nodes.TemplateData)
+ !(parsed.children[0]?.children?.[0] instanceof nodes.TemplateData)
);
}
diff --git a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
index 77c8240974..ce0354b817 100644
--- a/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
+++ b/plugins/scaffolder/src/components/ActionsPage/ActionsPage.test.tsx
@@ -17,8 +17,8 @@ import React from 'react';
import { ScaffolderApi, scaffolderApiRef } from '../../api';
import { ActionsPage } from './ActionsPage';
import { rootRouteRef } from '../../routes';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
+import { ApiProvider } from '@backstage/core-app-api';
const scaffolderApiMock: jest.Mocked = {
scaffold: jest.fn(),
@@ -29,7 +29,7 @@ const scaffolderApiMock: jest.Mocked = {
listActions: jest.fn(),
};
-const apis = ApiRegistry.from([[scaffolderApiRef, scaffolderApiMock]]);
+const apis = TestApiRegistry.from([scaffolderApiRef, scaffolderApiMock]);
describe('TemplatePage', () => {
beforeEach(() => jest.resetAllMocks());
diff --git a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx
index e42bcbdbdc..60e9ee8840 100644
--- a/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx
+++ b/plugins/scaffolder/src/components/TemplatePage/TemplatePage.test.tsx
@@ -13,7 +13,11 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { renderInTestApp, renderWithEffects } from '@backstage/test-utils';
+import {
+ renderInTestApp,
+ renderWithEffects,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import { lightTheme } from '@backstage/theme';
import { ThemeProvider } from '@material-ui/core';
import { fireEvent, within } from '@testing-library/react';
@@ -24,7 +28,7 @@ import { ScaffolderApi, scaffolderApiRef } from '../../api';
import { rootRouteRef } from '../../routes';
import { TemplatePage } from './TemplatePage';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
jest.mock('react-router-dom', () => {
@@ -47,10 +51,10 @@ const scaffolderApiMock: jest.Mocked = {
const errorApiMock = { post: jest.fn(), error$: jest.fn() };
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[scaffolderApiRef, scaffolderApiMock],
[errorApiRef, errorApiMock],
-]);
+);
describe('TemplatePage', () => {
beforeEach(() => jest.resetAllMocks());
diff --git a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
index 3d0267ff1f..c70f251eac 100644
--- a/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
+++ b/plugins/scaffolder/src/components/TemplateTypePicker/TemplateTypePicker.test.tsx
@@ -26,8 +26,8 @@ import {
MockEntityListContextProvider,
} from '@backstage/plugin-catalog-react';
import { AlertApi, alertApiRef } from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
-import { renderWithEffects } from '@backstage/test-utils';
+import { ApiProvider } from '@backstage/core-app-api';
+import { renderWithEffects, TestApiRegistry } from '@backstage/test-utils';
const entities: Entity[] = [
{
@@ -62,7 +62,7 @@ const entities: Entity[] = [
},
];
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[
catalogApiRef,
{
@@ -77,7 +77,7 @@ const apis = ApiRegistry.from([
post: jest.fn(),
} as unknown as AlertApi,
],
-]);
+);
describe('', () => {
it('renders available entity types', async () => {
diff --git a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
index f293969dff..b7e72e3583 100644
--- a/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/EntityPicker/EntityPicker.test.tsx
@@ -16,12 +16,11 @@
import { Entity } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { FieldProps } from '@rjsf/core';
import userEvent from '@testing-library/user-event';
import React from 'react';
import { EntityPicker } from './EntityPicker';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
apiVersion: 'backstage.io/v1beta1',
@@ -53,14 +52,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
entities = [
makeEntity('Group', 'default', 'team-a'),
makeEntity('Group', 'default', 'squad-b'),
];
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
index 18d6fd7e5c..31992fbc8a 100644
--- a/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
+++ b/plugins/scaffolder/src/components/fields/OwnerPicker/OwnerPicker.test.tsx
@@ -16,11 +16,10 @@
import { Entity } from '@backstage/catalog-model';
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { FieldProps } from '@rjsf/core';
import React from 'react';
import { OwnerPicker } from './OwnerPicker';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
const makeEntity = (kind: string, namespace: string, name: string): Entity => ({
apiVersion: 'backstage.io/v1beta1',
@@ -50,14 +49,15 @@ describe('', () => {
let Wrapper: React.ComponentType;
beforeEach(() => {
- const apis = ApiRegistry.with(catalogApiRef, catalogApi);
entities = [
makeEntity('Group', 'default', 'team-a'),
makeEntity('Group', 'default', 'squad-b'),
];
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/search/src/components/SearchBar/SearchBar.test.tsx b/plugins/search/src/components/SearchBar/SearchBar.test.tsx
index 3a11daa9fa..81f90640c1 100644
--- a/plugins/search/src/components/SearchBar/SearchBar.test.tsx
+++ b/plugins/search/src/components/SearchBar/SearchBar.test.tsx
@@ -21,12 +21,9 @@ import { SearchContextProvider } from '../SearchContext';
import { SearchBar } from './SearchBar';
import { configApiRef } from '@backstage/core-plugin-api';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { searchApiRef } from '../../apis';
+import { TestApiRegistry } from '@backstage/test-utils';
jest.mock('@backstage/core-plugin-api', () => ({
...jest.requireActual('@backstage/core-plugin-api'),
@@ -42,10 +39,10 @@ describe('SearchBar', () => {
const query = jest.fn().mockResolvedValue({});
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[configApiRef, new ConfigReader({ app: { title: 'Mock title' } })],
[searchApiRef, { query }],
- ]);
+ );
const name = 'Search';
const term = 'term';
diff --git a/plugins/search/src/components/SearchModal/SearchModal.test.tsx b/plugins/search/src/components/SearchModal/SearchModal.test.tsx
index d7f873abf3..ff9622dfb9 100644
--- a/plugins/search/src/components/SearchModal/SearchModal.test.tsx
+++ b/plugins/search/src/components/SearchModal/SearchModal.test.tsx
@@ -15,14 +15,10 @@
*/
import React from 'react';
import { screen } from '@testing-library/react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { configApiRef } from '@backstage/core-plugin-api';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { rootRouteRef } from '../../plugin';
import { searchApiRef } from '../../apis';
@@ -38,10 +34,10 @@ jest.mock('../SearchContext', () => ({
describe('SearchModal', () => {
const query = jest.fn().mockResolvedValue({});
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[configApiRef, new ConfigReader({ app: { title: 'Mock app' } })],
[searchApiRef, { query }],
- ]);
+ );
const toggleModal = jest.fn();
diff --git a/plugins/shortcuts/src/Shortcuts.test.tsx b/plugins/shortcuts/src/Shortcuts.test.tsx
index 9182bcb47d..e0a70d6069 100644
--- a/plugins/shortcuts/src/Shortcuts.test.tsx
+++ b/plugins/shortcuts/src/Shortcuts.test.tsx
@@ -15,25 +15,31 @@
*/
import React from 'react';
-import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiProvider,
+} from '@backstage/test-utils';
import { screen, waitFor } from '@testing-library/react';
import { Shortcuts } from './Shortcuts';
import { LocalStoredShortcuts, shortcutsApiRef } from './api';
import { SidebarContext } from '@backstage/core-components';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
-
-const apis = ApiRegistry.from([
- [shortcutsApiRef, new LocalStoredShortcuts(MockStorageApi.create())],
-]);
describe('Shortcuts', () => {
it('displays an add button', async () => {
await renderInTestApp(
-
+
-
+
,
);
await waitFor(() => !screen.queryByTestId('progress'));
diff --git a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx
index 63d617aff2..eb5c99d4e7 100644
--- a/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx
+++ b/plugins/splunk-on-call/src/components/EntitySplunkOnCallCard.test.tsx
@@ -17,7 +17,7 @@ import React from 'react';
import { act, fireEvent, render, waitFor } from '@testing-library/react';
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import {
splunkOnCallApiRef,
SplunkOnCallClient,
@@ -37,13 +37,8 @@ import {
alertApiRef,
ConfigApi,
configApiRef,
- createApiRef,
} from '@backstage/core-plugin-api';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
const mockSplunkOnCallApi: Partial = {
getUsers: async () => [],
@@ -59,17 +54,11 @@ const configApi: ConfigApi = new ConfigReader({
},
});
-const apis = ApiRegistry.from([
+const apis = TestApiRegistry.from(
[splunkOnCallApiRef, mockSplunkOnCallApi],
[configApiRef, configApi],
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
-]);
+ [alertApiRef, {}],
+);
const mockEntity = {
apiVersion: 'backstage.io/v1alpha1',
diff --git a/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx b/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx
index 1319493742..046c6a37f9 100644
--- a/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx
+++ b/plugins/splunk-on-call/src/components/Escalation/Escalation.test.tsx
@@ -16,15 +16,15 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { EscalationPolicy } from './EscalationPolicy';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { splunkOnCallApiRef } from '../../api';
import { MOCKED_ON_CALL, MOCKED_USER } from '../../api/mocks';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const mockSplunkOnCallApi = {
- getOnCallUsers: () => [],
+ getOnCallUsers: jest.fn(),
};
-const apis = ApiRegistry.from([[splunkOnCallApiRef, mockSplunkOnCallApi]]);
+const apis = TestApiRegistry.from([splunkOnCallApiRef, mockSplunkOnCallApi]);
describe('Escalation', () => {
it('Handles an empty response', async () => {
diff --git a/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx
index df4254d3ad..181c2fda77 100644
--- a/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx
+++ b/plugins/splunk-on-call/src/components/Incident/Incidents.test.tsx
@@ -16,43 +16,39 @@
import React from 'react';
import { render, waitFor } from '@testing-library/react';
import { Incidents } from './Incidents';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { splunkOnCallApiRef } from '../../api';
import { MOCK_TEAM, MOCK_INCIDENT } from '../../api/mocks';
import {
alertApiRef,
- createApiRef,
IdentityApi,
identityApiRef,
} from '@backstage/core-plugin-api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
const mockIdentityApi: Partial = {
getUserId: () => 'test',
};
const mockSplunkOnCallApi = {
- getIncidents: () => [],
- getTeams: () => [],
+ getIncidents: jest.fn(),
+ getTeams: jest.fn(),
};
-const apis = ApiRegistry.from([
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
+const apis = TestApiRegistry.from(
+ [alertApiRef, {}],
[identityApiRef, mockIdentityApi],
[splunkOnCallApiRef, mockSplunkOnCallApi],
-]);
+);
describe('Incidents', () => {
+ afterEach(() => {
+ jest.resetAllMocks();
+ });
+
it('Renders an empty state when there are no incidents', async () => {
- mockSplunkOnCallApi.getTeams = jest
- .fn()
- .mockImplementationOnce(async () => [MOCK_TEAM]);
+ mockSplunkOnCallApi.getIncidents.mockResolvedValue([]);
+ mockSplunkOnCallApi.getTeams.mockResolvedValue([MOCK_TEAM]);
const { getByText, queryByTestId } = render(
wrapInTestApp(
@@ -69,13 +65,9 @@ describe('Incidents', () => {
});
it('Renders all incidents', async () => {
- mockSplunkOnCallApi.getIncidents = jest
- .fn()
- .mockImplementationOnce(async () => [MOCK_INCIDENT]);
+ mockSplunkOnCallApi.getIncidents.mockResolvedValue([MOCK_INCIDENT]);
+ mockSplunkOnCallApi.getTeams.mockResolvedValue([MOCK_TEAM]);
- mockSplunkOnCallApi.getTeams = jest
- .fn()
- .mockImplementationOnce(async () => [MOCK_TEAM]);
const {
getByText,
getByTitle,
@@ -108,9 +100,10 @@ describe('Incidents', () => {
});
it('Handle errors', async () => {
- mockSplunkOnCallApi.getIncidents = jest
- .fn()
- .mockRejectedValueOnce(new Error('Error occurred'));
+ mockSplunkOnCallApi.getIncidents.mockRejectedValueOnce(
+ new Error('Error occurred'),
+ );
+ mockSplunkOnCallApi.getTeams.mockResolvedValue([]);
const { getByText, queryByTestId } = render(
wrapInTestApp(
diff --git a/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx b/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx
index 94fd40bfe5..0b64a137af 100644
--- a/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx
+++ b/plugins/splunk-on-call/src/components/TriggerDialog/TriggerDialog.test.tsx
@@ -15,12 +15,12 @@
*/
import React from 'react';
import { render, fireEvent, act } from '@testing-library/react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { splunkOnCallApiRef } from '../../api';
import { TriggerDialog } from './TriggerDialog';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
-import { alertApiRef, createApiRef } from '@backstage/core-plugin-api';
+import { ApiProvider } from '@backstage/core-app-api';
+import { alertApiRef } from '@backstage/core-plugin-api';
describe('TriggerDialog', () => {
const mockTriggerAlarmFn = jest.fn();
@@ -28,16 +28,10 @@ describe('TriggerDialog', () => {
incidentAction: mockTriggerAlarmFn,
};
- const apis = ApiRegistry.from([
- [
- alertApiRef,
- createApiRef({
- id: 'core.alert',
- description: 'Used to report alerts and forward them to the app',
- }),
- ],
+ const apis = TestApiRegistry.from(
+ [alertApiRef, {}],
[splunkOnCallApiRef, mockSplunkOnCallApi],
- ]);
+ );
it('open the dialog and trigger an alarm', async () => {
const { getByText, getByRole, getByTestId } = render(
diff --git a/plugins/tech-radar/src/components/RadarComponent.test.tsx b/plugins/tech-radar/src/components/RadarComponent.test.tsx
index cce411e897..2b237c8d83 100644
--- a/plugins/tech-radar/src/components/RadarComponent.test.tsx
+++ b/plugins/tech-radar/src/components/RadarComponent.test.tsx
@@ -19,13 +19,12 @@ import { render, waitForElement } from '@testing-library/react';
import { ThemeProvider } from '@material-ui/core';
import { lightTheme } from '@backstage/theme';
import { act } from 'react-dom/test-utils';
-import { withLogCollector } from '@backstage/test-utils';
+import { TestApiProvider, withLogCollector } from '@backstage/test-utils';
import GetBBoxPolyfill from '../utils/polyfills/getBBox';
import { RadarComponent } from './RadarComponent';
import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
describe('RadarComponent', () => {
@@ -55,18 +54,18 @@ describe('RadarComponent', () => {
const errorApi = { post: () => {} };
const { getByTestId, queryByTestId } = render(
-
-
+
,
);
@@ -87,18 +86,18 @@ describe('RadarComponent', () => {
const { queryByTestId } = render(
-
-
+
,
);
@@ -115,13 +114,13 @@ describe('RadarComponent', () => {
expect(() => {
render(
-
+
-
+
,
);
}).toThrow();
diff --git a/plugins/tech-radar/src/components/RadarPage.test.tsx b/plugins/tech-radar/src/components/RadarPage.test.tsx
index 7215fc40df..7f4e69e388 100644
--- a/plugins/tech-radar/src/components/RadarPage.test.tsx
+++ b/plugins/tech-radar/src/components/RadarPage.test.tsx
@@ -17,6 +17,7 @@
import {
MockErrorApi,
renderInTestApp,
+ TestApiProvider,
wrapInTestApp,
} from '@backstage/test-utils';
import { lightTheme } from '@backstage/theme';
@@ -28,7 +29,6 @@ import GetBBoxPolyfill from '../utils/polyfills/getBBox';
import { RadarPage } from './RadarPage';
import { TechRadarLoaderResponse, techRadarApiRef, TechRadarApi } from '../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { errorApiRef } from '@backstage/core-plugin-api';
describe('RadarPage', () => {
@@ -63,9 +63,9 @@ describe('RadarPage', () => {
const { getByTestId, queryByTestId } = render(
wrapInTestApp(
-
+
-
+
,
),
);
@@ -89,9 +89,9 @@ describe('RadarPage', () => {
const { getByText, getByTestId } = await renderInTestApp(
-
+
-
+
,
);
@@ -115,9 +115,9 @@ describe('RadarPage', () => {
const { getByTestId } = await renderInTestApp(
-
+
-
+
,
);
@@ -142,14 +142,14 @@ describe('RadarPage', () => {
const { queryByTestId } = await renderInTestApp(
-
-
+
,
);
diff --git a/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx
index 1d8d7d2b04..8618c4432f 100644
--- a/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx
+++ b/plugins/techdocs/src/home/components/DefaultTechDocsHome.test.tsx
@@ -14,11 +14,7 @@
* limitations under the License.
*/
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import {
ConfigApi,
configApiRef,
@@ -30,7 +26,11 @@ import {
DefaultStarredEntitiesApi,
starredEntitiesApiRef,
} from '@backstage/plugin-catalog-react';
-import { MockStorageApi, renderInTestApp } from '@backstage/test-utils';
+import {
+ MockStorageApi,
+ renderInTestApp,
+ TestApiRegistry,
+} from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { rootDocsRouteRef } from '../../routes';
@@ -69,12 +69,12 @@ describe('TechDocs Home', () => {
const storageApi = MockStorageApi.create();
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[catalogApiRef, mockCatalogApi],
[configApiRef, configApi],
[storageApiRef, storageApi],
[starredEntitiesApiRef, new DefaultStarredEntitiesApi({ storageApi })],
- ]);
+ );
it('should render a TechDocs home page', async () => {
await renderInTestApp(
diff --git a/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx
index d9d2c6a345..a8fd472f7c 100644
--- a/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx
+++ b/plugins/techdocs/src/home/components/LegacyTechDocsHome.test.tsx
@@ -15,16 +15,12 @@
*/
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { LegacyTechDocsHome } from './LegacyTechDocsHome';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { ConfigApi, configApiRef } from '@backstage/core-plugin-api';
import { rootDocsRouteRef } from '../../routes';
@@ -59,10 +55,10 @@ describe('Legacy TechDocs Home', () => {
},
});
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[catalogApiRef, mockCatalogApi],
[configApiRef, configApi],
- ]);
+ );
it('should render a TechDocs home page', async () => {
await renderInTestApp(
diff --git a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx
index bcbfdeed86..e9c3a55b15 100644
--- a/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx
+++ b/plugins/techdocs/src/home/components/TechDocsCustomHome.test.tsx
@@ -15,11 +15,11 @@
*/
import { CatalogApi, catalogApiRef } from '@backstage/plugin-catalog-react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiRegistry } from '@backstage/test-utils';
import { screen } from '@testing-library/react';
import React from 'react';
import { TechDocsCustomHome, PanelType } from './TechDocsCustomHome';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { rootDocsRouteRef } from '../../routes';
jest.mock('@backstage/plugin-catalog-react', () => {
@@ -47,7 +47,7 @@ const mockCatalogApi = {
} as Partial;
describe('TechDocsCustomHome', () => {
- const apiRegistry = ApiRegistry.with(catalogApiRef, mockCatalogApi);
+ const apiRegistry = TestApiRegistry.from([catalogApiRef, mockCatalogApi]);
it('should render a TechDocs home page', async () => {
const tabsConfig = [
diff --git a/plugins/techdocs/src/reader/components/Reader.test.tsx b/plugins/techdocs/src/reader/components/Reader.test.tsx
index 8b5dbced3f..6e8609694d 100644
--- a/plugins/techdocs/src/reader/components/Reader.test.tsx
+++ b/plugins/techdocs/src/reader/components/Reader.test.tsx
@@ -19,12 +19,12 @@ import {
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { act, render } from '@testing-library/react';
import React from 'react';
import { TechDocsStorageApi, techdocsStorageApiRef } from '../../api';
import { Reader } from './Reader';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { searchApiRef } from '@backstage/plugin-search';
jest.mock('react-router-dom', () => {
@@ -57,11 +57,11 @@ describe('', () => {
results: [],
}),
};
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[scmIntegrationsApiRef, scmIntegrationsApi],
[techdocsStorageApiRef, techdocsStorageApi],
[searchApiRef, searchApi],
- ]);
+ );
await act(async () => {
const rendered = render(
diff --git a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx
index 6a5af982fd..b297466a62 100644
--- a/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsPage.test.tsx
@@ -21,7 +21,7 @@ import {
ScmIntegrationsApi,
scmIntegrationsApiRef,
} from '@backstage/integration-react';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import { Header } from '@backstage/core-components';
import {
techdocsApiRef,
@@ -29,7 +29,7 @@ import {
techdocsStorageApiRef,
TechDocsStorageApi,
} from '../../api';
-import { ApiRegistry, ApiProvider } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { searchApiRef } from '@backstage/plugin-search';
jest.mock('react-router-dom', () => {
@@ -90,12 +90,12 @@ describe('', () => {
results: [],
}),
};
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[scmIntegrationsApiRef, scmIntegrationsApi],
[techdocsApiRef, techdocsApi],
[techdocsStorageApiRef, techdocsStorageApi],
[searchApiRef, searchApi],
- ]);
+ );
await act(async () => {
const rendered = render(
@@ -147,12 +147,12 @@ describe('', () => {
results: [],
}),
};
- const apiRegistry = ApiRegistry.from([
+ const apiRegistry = TestApiRegistry.from(
[scmIntegrationsApiRef, scmIntegrationsApi],
[techdocsApiRef, techdocsApi],
[techdocsStorageApiRef, techdocsStorageApi],
[searchApiRef, searchApi],
- ]);
+ );
await act(async () => {
const rendered = render(
diff --git a/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx b/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx
index 602a94e758..5b3d714a04 100644
--- a/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx
+++ b/plugins/techdocs/src/reader/components/TechDocsSearch.test.tsx
@@ -13,9 +13,9 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { ApiProvider } from '@backstage/core-app-api';
import { searchApiRef } from '@backstage/plugin-search';
-import { wrapInTestApp } from '@backstage/test-utils';
+import { TestApiRegistry, wrapInTestApp } from '@backstage/test-utils';
import {
act,
fireEvent,
@@ -54,7 +54,7 @@ describe('', () => {
const querySpy = jest.fn(query);
const searchApi = { query: querySpy };
- const apiRegistry = ApiRegistry.from([[searchApiRef, searchApi]]);
+ const apiRegistry = TestApiRegistry.from([searchApiRef, searchApi]);
await act(async () => {
const rendered = render(
@@ -75,7 +75,7 @@ describe('', () => {
const querySpy = jest.fn(query);
const searchApi = { query: querySpy };
- const apiRegistry = ApiRegistry.from([[searchApiRef, searchApi]]);
+ const apiRegistry = TestApiRegistry.from([searchApiRef, searchApi]);
await act(async () => {
const rendered = render(
diff --git a/plugins/techdocs/src/reader/components/useReaderState.test.tsx b/plugins/techdocs/src/reader/components/useReaderState.test.tsx
index cce8282b3d..e41a114dbe 100644
--- a/plugins/techdocs/src/reader/components/useReaderState.test.tsx
+++ b/plugins/techdocs/src/reader/components/useReaderState.test.tsx
@@ -14,8 +14,8 @@
* limitations under the License.
*/
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { NotFoundError } from '@backstage/errors';
+import { TestApiProvider } from '@backstage/test-utils';
import { act, renderHook } from '@testing-library/react-hooks';
import React from 'react';
import { techdocsStorageApiRef } from '../../api';
@@ -38,10 +38,10 @@ describe('useReaderState', () => {
};
beforeEach(() => {
- const apis = ApiRegistry.with(techdocsStorageApiRef, techdocsStorageApi);
-
Wrapper = ({ children }: { children?: React.ReactNode }) => (
- {children}
+
+ {children}
+
);
});
diff --git a/plugins/todo/dev/index.tsx b/plugins/todo/dev/index.tsx
index a4221dc710..fe547812ec 100644
--- a/plugins/todo/dev/index.tsx
+++ b/plugins/todo/dev/index.tsx
@@ -22,8 +22,8 @@ import OfflineIcon from '@material-ui/icons/Storage';
import React from 'react';
import { EntityTodoContent, todoApiRef, todoPlugin } from '../src';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { Content, Header, HeaderLabel, Page } from '@backstage/core-components';
+import { TestApiProvider } from '@backstage/test-utils';
const entity: Entity = {
apiVersion: 'backstage.io/v1alpha1',
@@ -60,7 +60,7 @@ createDevApp()
.registerPlugin(todoPlugin)
.addPage({
element: (
-
+
@@ -71,7 +71,7 @@ createDevApp()
-
+
),
title: 'Entity Todo Content',
icon: OfflineIcon,
diff --git a/plugins/todo/src/components/TodoList/TodoList.test.tsx b/plugins/todo/src/components/TodoList/TodoList.test.tsx
index 0e4d64172d..2759fe40f8 100644
--- a/plugins/todo/src/components/TodoList/TodoList.test.tsx
+++ b/plugins/todo/src/components/TodoList/TodoList.test.tsx
@@ -16,11 +16,10 @@
import { Entity } from '@backstage/catalog-model';
import { EntityProvider } from '@backstage/plugin-catalog-react';
-import { renderWithEffects } from '@backstage/test-utils';
+import { renderWithEffects, TestApiProvider } from '@backstage/test-utils';
import React from 'react';
import { TodoApi, todoApiRef } from '../../api';
import { TodoList } from './TodoList';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
describe('TodoList', () => {
it('should render', async () => {
@@ -42,11 +41,11 @@ describe('TodoList', () => {
const mockEntity = { metadata: { name: 'mock' } } as Entity;
const rendered = await renderWithEffects(
-
+
- ,
+ ,
);
await expect(rendered.findByText('FIXME')).resolves.toBeInTheDocument();
diff --git a/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx b/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx
index 085a884f2e..4e14dec2ef 100644
--- a/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx
+++ b/plugins/user-settings/src/components/AuthProviders/UserSettingsAuthProviders.test.tsx
@@ -14,22 +14,24 @@
* limitations under the License.
*/
-import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import {
+ renderWithEffects,
+ TestApiRegistry,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import { fireEvent } from '@testing-library/react';
import React from 'react';
import { UserSettingsAuthProviders } from './UserSettingsAuthProviders';
-import {
- ApiProvider,
- ApiRegistry,
- ConfigReader,
-} from '@backstage/core-app-api';
+import { ApiProvider, ConfigReader } from '@backstage/core-app-api';
import { configApiRef, googleAuthApiRef } from '@backstage/core-plugin-api';
const mockSignInHandler = jest.fn().mockReturnValue('');
const mockGoogleAuth = {
sessionState$: () => ({
+ [Symbol.observable]: jest.fn(),
subscribe: () => ({
+ closed: false,
unsubscribe: () => null,
}),
}),
@@ -47,10 +49,10 @@ const createConfig = () =>
const config = createConfig();
-const apiRegistry = ApiRegistry.from([
+const apiRegistry = TestApiRegistry.from(
[configApiRef, config],
[googleAuthApiRef, mockGoogleAuth],
-]);
+);
describe('', () => {
it('displays a provider and calls its sign-in handler on click', async () => {
diff --git a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx
index caae4d69bc..83d9e23f83 100644
--- a/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx
+++ b/plugins/user-settings/src/components/General/UserSettingsThemeToggle.test.tsx
@@ -15,16 +15,16 @@
*/
import { AppTheme, appThemeApiRef } from '@backstage/core-plugin-api';
-import { renderWithEffects, wrapInTestApp } from '@backstage/test-utils';
+import {
+ renderWithEffects,
+ TestApiRegistry,
+ wrapInTestApp,
+} from '@backstage/test-utils';
import { lightTheme } from '@backstage/theme';
import { fireEvent } from '@testing-library/react';
import React from 'react';
import { UserSettingsThemeToggle } from './UserSettingsThemeToggle';
-import {
- ApiProvider,
- ApiRegistry,
- AppThemeSelector,
-} from '@backstage/core-app-api';
+import { ApiProvider, AppThemeSelector } from '@backstage/core-app-api';
const mockTheme: AppTheme = {
id: 'light-theme',
@@ -33,8 +33,9 @@ const mockTheme: AppTheme = {
theme: lightTheme,
};
-const apiRegistry = ApiRegistry.from([
- [appThemeApiRef, AppThemeSelector.createWithStorage([mockTheme])],
+const apiRegistry = TestApiRegistry.from([
+ appThemeApiRef,
+ AppThemeSelector.createWithStorage([mockTheme]),
]);
describe('', () => {
diff --git a/plugins/xcmetrics/src/components/BuildDetails/BuildDetails.test.tsx b/plugins/xcmetrics/src/components/BuildDetails/BuildDetails.test.tsx
index 2d949d7abc..7083d890d6 100644
--- a/plugins/xcmetrics/src/components/BuildDetails/BuildDetails.test.tsx
+++ b/plugins/xcmetrics/src/components/BuildDetails/BuildDetails.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { BuildDetails, withRequest } from './BuildDetails';
import { xcmetricsApiRef } from '../../api';
@@ -35,11 +34,9 @@ jest.mock('../BuildTimeline', () => ({
describe('BuildDetails', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('accordion-Host')).toBeInTheDocument();
@@ -61,11 +58,9 @@ describe('BuildDetails with request', () => {
it('should fetch the build and render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText(client.mockBuild.id)).toBeInTheDocument();
@@ -78,11 +73,9 @@ describe('BuildDetails with request', () => {
.mockRejectedValue({ message: errorMessage });
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText(errorMessage)).toBeInTheDocument();
@@ -92,11 +85,9 @@ describe('BuildDetails with request', () => {
client.XcmetricsClient.getBuild = jest.fn().mockReturnValue(undefined);
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(
diff --git a/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx b/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx
index eb141209e7..53471f5e9f 100644
--- a/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx
+++ b/plugins/xcmetrics/src/components/BuildList/BuildList.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { BuildList } from './BuildList';
import { xcmetricsApiRef } from '../../api';
import userEvent from '@testing-library/user-event';
@@ -35,11 +34,9 @@ jest.mock('../BuildDetails', () => ({
describe('BuildList', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('Builds')).toBeInTheDocument();
@@ -50,11 +47,9 @@ describe('BuildList', () => {
it('should show build details', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
userEvent.click(
@@ -70,11 +65,9 @@ describe('BuildList', () => {
.mockRejectedValue({ message });
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText(message)).toBeInTheDocument();
diff --git a/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx b/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
index b997de68e0..06f4aed1b7 100644
--- a/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
+++ b/plugins/xcmetrics/src/components/BuildListFilter/BuildListFilter.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { BuildListFilter } from './BuildListFilter';
import { BuildFilters, xcmetricsApiRef } from '../../api';
@@ -37,14 +36,12 @@ const renderWithFiltersVisible = async (
callback?: (filters: BuildFilters) => void,
) => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
userEvent.click(rendered.getByLabelText('show filters'));
@@ -67,14 +64,12 @@ const setProjectFilter = async (rendered: RenderResult, option: string) => {
describe('BuildListFilter', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('Filters (0)')).toBeInTheDocument();
diff --git a/plugins/xcmetrics/src/components/Overview/Overview.test.tsx b/plugins/xcmetrics/src/components/Overview/Overview.test.tsx
index 3d24d2a422..42e3772d04 100644
--- a/plugins/xcmetrics/src/components/Overview/Overview.test.tsx
+++ b/plugins/xcmetrics/src/components/Overview/Overview.test.tsx
@@ -14,9 +14,8 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { xcmetricsApiRef } from '../../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import { Overview } from './Overview';
jest.mock('../../api/XcmetricsClient');
@@ -33,11 +32,9 @@ jest.mock('../StatusMatrix', () => ({
describe('Overview', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('XCMetrics Dashboard')).toBeInTheDocument();
@@ -49,9 +46,9 @@ describe('Overview', () => {
api.getBuilds = jest.fn().mockResolvedValue([]);
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('No builds to show')).toBeInTheDocument();
@@ -64,9 +61,9 @@ describe('Overview', () => {
api.getBuilds = jest.fn().mockRejectedValue({ message: errorMessage });
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText(errorMessage)).toBeInTheDocument();
diff --git a/plugins/xcmetrics/src/components/OverviewTrends/OverviewTrends.test.tsx b/plugins/xcmetrics/src/components/OverviewTrends/OverviewTrends.test.tsx
index 76b47f4943..db7904a8d1 100644
--- a/plugins/xcmetrics/src/components/OverviewTrends/OverviewTrends.test.tsx
+++ b/plugins/xcmetrics/src/components/OverviewTrends/OverviewTrends.test.tsx
@@ -15,9 +15,8 @@
*/
import React from 'react';
import { OverviewTrends } from './OverviewTrends';
-import { renderInTestApp } from '@backstage/test-utils';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { xcmetricsApiRef } from '../../api';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
import userEvent from '@testing-library/user-event';
jest.mock('../../api/XcmetricsClient');
@@ -26,11 +25,9 @@ const client = require('../../api/XcmetricsClient');
describe('OverviewTrends', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('Trends for')).toBeInTheDocument();
expect(rendered.getAllByText('Build Count').length).toEqual(3);
@@ -42,20 +39,18 @@ describe('OverviewTrends', () => {
api.getBuildCounts = jest.fn().mockResolvedValue([]);
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('--')).toBeInTheDocument();
});
it('should change number of days when select is changed', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
userEvent.click(rendered.getByText('14 days'));
@@ -76,9 +71,9 @@ describe('OverviewTrends', () => {
.mockRejectedValue({ message: buildTimesError });
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText(buildCountError)).toBeInTheDocument();
expect(rendered.getByText(buildTimesError)).toBeInTheDocument();
diff --git a/plugins/xcmetrics/src/components/StatusCell/StatusCell.test.tsx b/plugins/xcmetrics/src/components/StatusCell/StatusCell.test.tsx
index cac111d13e..6e5b7f73b9 100644
--- a/plugins/xcmetrics/src/components/StatusCell/StatusCell.test.tsx
+++ b/plugins/xcmetrics/src/components/StatusCell/StatusCell.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import userEvent from '@testing-library/user-event';
import { StatusCell } from './StatusCell';
import { xcmetricsApiRef } from '../../api';
@@ -27,9 +26,7 @@ const client = require('../../api/XcmetricsClient');
describe('StatusCell', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
{
size={10}
spacing={10}
/>
- ,
+ ,
);
userEvent.hover(rendered.getByTestId(client.mockBuild.id));
diff --git a/plugins/xcmetrics/src/components/StatusMatrix/StatusMatrix.test.tsx b/plugins/xcmetrics/src/components/StatusMatrix/StatusMatrix.test.tsx
index 7d3854bbda..f18d322b43 100644
--- a/plugins/xcmetrics/src/components/StatusMatrix/StatusMatrix.test.tsx
+++ b/plugins/xcmetrics/src/components/StatusMatrix/StatusMatrix.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { StatusMatrix } from './StatusMatrix';
import { xcmetricsApiRef } from '../../api';
@@ -25,11 +24,9 @@ const client = require('../../api/XcmetricsClient');
describe('StatusMatrix', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
const cell = rendered.getByTestId(client.mockBuild.id);
diff --git a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx
index 84bd25624c..29d6407b03 100644
--- a/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx
+++ b/plugins/xcmetrics/src/components/XcmetricsLayout/XcmetricsLayout.test.tsx
@@ -14,8 +14,7 @@
* limitations under the License.
*/
import React from 'react';
-import { renderInTestApp } from '@backstage/test-utils';
-import { ApiProvider, ApiRegistry } from '@backstage/core-app-api';
+import { renderInTestApp, TestApiProvider } from '@backstage/test-utils';
import { XcmetricsLayout } from './XcmetricsLayout';
import { xcmetricsApiRef } from '../../api';
import userEvent from '@testing-library/user-event';
@@ -34,11 +33,9 @@ jest.mock('../BuildList', () => ({
describe('XcmetricsLayout', () => {
it('should render', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
expect(rendered.getByText('Overview')).toBeInTheDocument();
@@ -49,11 +46,9 @@ describe('XcmetricsLayout', () => {
it('should show a list of builds when the Builds tab is selected', async () => {
const rendered = await renderInTestApp(
-
+
- ,
+ ,
);
userEvent.click(rendered.getByText('Builds'));
diff --git a/yarn.lock b/yarn.lock
index 07a5febcb8..1e4d402072 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -2078,10 +2078,10 @@
core-js-pure "^3.16.0"
regenerator-runtime "^0.13.4"
-"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
- version "7.15.4"
- resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.15.4.tgz#fd17d16bfdf878e6dd02d19753a39fa8a8d9c84a"
- integrity sha512-99catp6bHCaxr4sJ/DbTGgHS4+Rs2RVd2g7iOap6SLGPDknRK9ztKNsE/Fg6QhSeh1FGE5f6gHGQmvvn3I3xhw==
+"@babel/runtime@^7.0.0", "@babel/runtime@^7.1.2", "@babel/runtime@^7.10.0", "@babel/runtime@^7.10.1", "@babel/runtime@^7.10.2", "@babel/runtime@^7.10.4", "@babel/runtime@^7.10.5", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.14.0", "@babel/runtime@^7.14.6", "@babel/runtime@^7.14.8", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.0", "@babel/runtime@^7.5.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.0", "@babel/runtime@^7.7.2", "@babel/runtime@^7.7.6", "@babel/runtime@^7.8.3", "@babel/runtime@^7.8.4", "@babel/runtime@^7.8.7", "@babel/runtime@^7.9.2":
+ version "7.16.3"
+ resolved "https://registry.npmjs.org/@babel/runtime/-/runtime-7.16.3.tgz#b86f0db02a04187a3c17caa77de69840165d42d5"
+ integrity sha512-WBwekcqacdY2e9AF/Q7WLFUWmdJGJTkbjqTjoMDgXkVZ3ZRUvOPsLb5KdwISoQVsbP+DQzVZW4Zhci0DvpbNTQ==
dependencies:
regenerator-runtime "^0.13.4"
@@ -2879,35 +2879,36 @@
resolved "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.2.tgz#30aa825f11d438671d585bd44e7fd564535fc210"
integrity sha512-82cpyJyKRoQoRi+14ibCeGPu0CwypgtBAdBhq1WfvagpCZNKqwXbKwXllYSMG91DhmG4jt9gN8eP6lGOtozuaw==
-"@gitbeaker/core@^30.2.0", "@gitbeaker/core@^30.3.0":
- version "30.3.0"
- resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-30.3.0.tgz#d005891d47cfacb41d4a3cc8bf3ee8c68c53d378"
- integrity sha512-j7GHsFo6AOuRmLaK4F4Kx967jTy6LFZh5vjmRxjlRvX0qlfl3NJKiQIl30fZz1rPXWdQLAq+kwl1i0BKrWhOpA==
+"@gitbeaker/core@^34.6.0":
+ version "34.6.0"
+ resolved "https://registry.npmjs.org/@gitbeaker/core/-/core-34.6.0.tgz#f774ea98ac079ba2edf495fdef738ac3f741178b"
+ integrity sha512-yKF+oxffPyzOnyuHCqLGJrBHhcFHuGHtcmqKhGKtnYPfqcNYA8rt4INAHaE5wMz4ILua9b4sB8p42fki+xn6WA==
dependencies:
- "@gitbeaker/requester-utils" "^30.3.0"
+ "@gitbeaker/requester-utils" "^34.6.0"
form-data "^4.0.0"
li "^1.3.0"
+ mime "^3.0.0"
query-string "^7.0.0"
xcase "^2.0.1"
-"@gitbeaker/node@^30.2.0":
- version "30.3.0"
- resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-30.3.0.tgz#ceebde08a13d3f655fa614a4ced56eb1f0a71ec1"
- integrity sha512-Ythbadb1+yMO2hSgvp3nvaroHTkI4+mdLg1rdV3YNIF1n+kUYXQD2GY7wpAjH0KjAnBAmgxw/JsFmlfnRu3KAg==
+"@gitbeaker/node@^34.6.0":
+ version "34.6.0"
+ resolved "https://registry.npmjs.org/@gitbeaker/node/-/node-34.6.0.tgz#104f122433b65ceb45b0e645001d15cbcc9b1280"
+ integrity sha512-gVV4Wuev43Jbyoy1fszC885+bkvWH4zWiUhtIu0PSAm628j/OxO7idLIqUEMV0hDf6wm/PE/vOSP6PhjE0N+fA==
dependencies:
- "@gitbeaker/core" "^30.3.0"
- "@gitbeaker/requester-utils" "^30.3.0"
+ "@gitbeaker/core" "^34.6.0"
+ "@gitbeaker/requester-utils" "^34.6.0"
delay "^5.0.0"
got "^11.8.2"
xcase "^2.0.1"
-"@gitbeaker/requester-utils@^30.3.0":
- version "30.3.0"
- resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-30.3.0.tgz#fbbaae20263ff90952da116d8782f74f47b29c81"
- integrity sha512-b5vCaUqP2Jrqdpt5CkmFET4VFHwJYjnIovwZGYd5H0aKmiPw4NmeW1JtP+65J4xf0c4AOQQj6XSf7TZLuPNAqw==
+"@gitbeaker/requester-utils@^34.6.0":
+ version "34.6.0"
+ resolved "https://registry.npmjs.org/@gitbeaker/requester-utils/-/requester-utils-34.6.0.tgz#4489009b759ca6f9a83f244453f4f610f1ac7349"
+ integrity sha512-H8utxbSP1kEdX0KcyVYrTDTT0A3UcPwrIV1ahyufX9ZLybYSUsA56B8Wx5kJSbWGFT1ffu2f8H2YDMwNCKKsBg==
dependencies:
form-data "^4.0.0"
- query-string "^7.0.0"
+ qs "^6.10.1"
xcase "^2.0.1"
"@google-cloud/common@^3.7.0":
@@ -3654,6 +3655,17 @@
"@types/yargs" "^15.0.0"
chalk "^4.0.0"
+"@jest/types@^27.2.5":
+ version "27.2.5"
+ resolved "https://registry.npmjs.org/@jest/types/-/types-27.2.5.tgz#420765c052605e75686982d24b061b4cbba22132"
+ integrity sha512-nmuM4VuDtCZcY+eTpw+0nvstwReMsjPoj7ZR80/BbixulhLaiX+fbv8oeLW8WZlJMcsGQsTmMKT/iTZu1Uy/lQ==
+ dependencies:
+ "@types/istanbul-lib-coverage" "^2.0.0"
+ "@types/istanbul-reports" "^3.0.0"
+ "@types/node" "*"
+ "@types/yargs" "^16.0.0"
+ chalk "^4.0.0"
+
"@josephg/resolvable@^1.0.0":
version "1.0.1"
resolved "https://registry.npmjs.org/@josephg/resolvable/-/resolvable-1.0.1.tgz#69bc4db754d79e1a2f17a650d3466e038d94a5eb"
@@ -5556,16 +5568,16 @@
util-deprecate "^1.0.2"
"@storybook/addon-actions@^6.1.11":
- version "6.3.7"
- resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.3.7.tgz#b25434972bef351aceb3f7ec6fd66e210f256aac"
- integrity sha512-CEAmztbVt47Gw1o6Iw0VP20tuvISCEKk9CS/rCjHtb4ubby6+j/bkp3pkEUQIbyLdHiLWFMz0ZJdyA/U6T6jCw==
+ version "6.3.12"
+ resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.3.12.tgz#69eb5f8f780f1b00456051da6290d4b959ba24a0"
+ integrity sha512-mzuN4Ano4eyicwycM2PueGzzUCAEzt9/6vyptWEIVJu0sjK0J9KtBRlqFi1xGQxmCfimDR/n/vWBBkc7fp2uJA==
dependencies:
- "@storybook/addons" "6.3.7"
- "@storybook/api" "6.3.7"
- "@storybook/client-api" "6.3.7"
- "@storybook/components" "6.3.7"
- "@storybook/core-events" "6.3.7"
- "@storybook/theming" "6.3.7"
+ "@storybook/addons" "6.3.12"
+ "@storybook/api" "6.3.12"
+ "@storybook/client-api" "6.3.12"
+ "@storybook/components" "6.3.12"
+ "@storybook/core-events" "6.3.12"
+ "@storybook/theming" "6.3.12"
core-js "^3.8.2"
fast-deep-equal "^3.1.3"
global "^4.4.0"
@@ -5841,19 +5853,6 @@
qs "^6.10.0"
telejson "^5.3.2"
-"@storybook/channel-postmessage@6.3.7":
- version "6.3.7"
- resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.7.tgz#bd4edf84a29aa2cd4a22d26115c60194d289a840"
- integrity sha512-Cmw8HRkeSF1yUFLfEIUIkUICyCXX8x5Ol/5QPbiW9HPE2hbZtYROCcg4bmWqdq59N0Tp9FQNSn+9ZygPgqQtNw==
- dependencies:
- "@storybook/channels" "6.3.7"
- "@storybook/client-logger" "6.3.7"
- "@storybook/core-events" "6.3.7"
- core-js "^3.8.2"
- global "^4.4.0"
- qs "^6.10.0"
- telejson "^5.3.2"
-
"@storybook/channels@6.3.11":
version "6.3.11"
resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.11.tgz#a14ce233367a9072bd1cffef3825f125c27bb0ae"
@@ -5929,30 +5928,6 @@
ts-dedent "^2.0.0"
util-deprecate "^1.0.2"
-"@storybook/client-api@6.3.7":
- version "6.3.7"
- resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.7.tgz#cb1dca05467d777bd09aadbbdd1dd22ca537ce14"
- integrity sha512-8wOH19cMIwIIYhZy5O5Wl8JT1QOL5kNuamp9GPmg5ff4DtnG+/uUslskRvsnKyjPvl+WbIlZtBVWBiawVdd/yQ==
- dependencies:
- "@storybook/addons" "6.3.7"
- "@storybook/channel-postmessage" "6.3.7"
- "@storybook/channels" "6.3.7"
- "@storybook/client-logger" "6.3.7"
- "@storybook/core-events" "6.3.7"
- "@storybook/csf" "0.0.1"
- "@types/qs" "^6.9.5"
- "@types/webpack-env" "^1.16.0"
- core-js "^3.8.2"
- global "^4.4.0"
- lodash "^4.17.20"
- memoizerific "^1.11.3"
- qs "^6.10.0"
- regenerator-runtime "^0.13.7"
- stable "^0.1.8"
- store2 "^2.12.0"
- ts-dedent "^2.0.0"
- util-deprecate "^1.0.2"
-
"@storybook/client-logger@6.3.11":
version "6.3.11"
resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.11.tgz#d2e0e17f35ed4c5ee282d818b6db3a015ce6b833"
@@ -6651,13 +6626,13 @@
dependencies:
defer-to-connect "^2.0.0"
-"@testing-library/cypress@^7.0.1":
- version "7.0.6"
- resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-7.0.6.tgz#5445dac4f4852c26901c356e9d3a69371bd20ccf"
- integrity sha512-atnjqlkEt6spU4Mv7evvpA8fMXeRw7AN2uTKOR1dP6WBvBixVwAYMZY+1fMOaZULWAj9vGLCXXvmw++u3TxuCQ==
+"@testing-library/cypress@^8.0.2":
+ version "8.0.2"
+ resolved "https://registry.npmjs.org/@testing-library/cypress/-/cypress-8.0.2.tgz#b13f0ff2424dec4368b6670dfbfb7e43af8eefc9"
+ integrity sha512-KVdm7n37sg/A4e3wKMD4zUl0NpzzVhx06V9Tf0hZHZ7nrZ4yFva6Zwg2EFF1VzHkEfN/ahUzRtT1qiW+vuWnJw==
dependencies:
- "@babel/runtime" "^7.12.5"
- "@testing-library/dom" "^7.28.1"
+ "@babel/runtime" "^7.14.6"
+ "@testing-library/dom" "^8.1.0"
"@testing-library/dom@^7.28.1":
version "7.29.6"
@@ -6673,6 +6648,20 @@
lz-string "^1.4.4"
pretty-format "^26.6.2"
+"@testing-library/dom@^8.1.0":
+ version "8.11.1"
+ resolved "https://registry.npmjs.org/@testing-library/dom/-/dom-8.11.1.tgz#03fa2684aa09ade589b460db46b4c7be9fc69753"
+ integrity sha512-3KQDyx9r0RKYailW2MiYrSSKEfH0GTkI51UGEvJenvcoDoeRYs0PZpi2SXqtnMClQvCqdtTTpOfFETDTVADpAg==
+ dependencies:
+ "@babel/code-frame" "^7.10.4"
+ "@babel/runtime" "^7.12.5"
+ "@types/aria-query" "^4.2.0"
+ aria-query "^5.0.0"
+ chalk "^4.1.0"
+ dom-accessibility-api "^0.5.9"
+ lz-string "^1.4.4"
+ pretty-format "^27.0.2"
+
"@testing-library/jest-dom@^5.10.1":
version "5.14.1"
resolved "https://registry.npmjs.org/@testing-library/jest-dom/-/jest-dom-5.14.1.tgz#8501e16f1e55a55d675fe73eecee32cdaddb9766"
@@ -6742,6 +6731,11 @@
axios-cached-dns-resolve "0.5.2"
file-type "16.5.3"
+"@trysound/sax@0.2.0":
+ version "0.2.0"
+ resolved "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz#cccaab758af56761eb7bf37af6f03f326dd798ad"
+ integrity sha512-L7z9BgrNEcYyUYtF+HaEfiS5ebkh9jXqbszz7pC0hRBPaatV0XjSD3+eHrpqFemQfgwiFF0QPIarnIihIDn7OA==
+
"@tsconfig/node10@^1.0.7":
version "1.0.8"
resolved "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.8.tgz#c1e4e80d6f964fbecb3359c43bd48b40f7cadad9"
@@ -8317,6 +8311,13 @@
dependencies:
"@types/yargs-parser" "*"
+"@types/yargs@^16.0.0":
+ version "16.0.4"
+ resolved "https://registry.npmjs.org/@types/yargs/-/yargs-16.0.4.tgz#26aad98dd2c2a38e421086ea9ad42b9e51642977"
+ integrity sha512-T8Yc9wt/5LbJyCaLiHPReJa0kApcIgJ7Bn735GjItUfh08Z1pJvu8QZqb9s+mMvKV6WUQRV7K2R46YbjMXTTJw==
+ dependencies:
+ "@types/yargs-parser" "*"
+
"@types/yarnpkg__lockfile@^1.1.4":
version "1.1.4"
resolved "https://registry.npmjs.org/@types/yarnpkg__lockfile/-/yarnpkg__lockfile-1.1.4.tgz#445251eb00bd9c1e751f82c7c6bf4f714edfd464"
@@ -8938,7 +8939,7 @@ ajv@^8.0.1:
require-from-string "^2.0.2"
uri-js "^4.2.2"
-alphanum-sort@^1.0.0:
+alphanum-sort@^1.0.2:
version "1.0.2"
resolved "https://registry.npmjs.org/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
@@ -9011,7 +9012,7 @@ ansi-regex@^4.0.0, ansi-regex@^4.1.0:
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
integrity sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==
-ansi-regex@^5.0.0:
+ansi-regex@^5.0.0, ansi-regex@^5.0.1:
version "5.0.1"
resolved "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==
@@ -9041,6 +9042,11 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0:
"@types/color-name" "^1.1.1"
color-convert "^2.0.1"
+ansi-styles@^5.0.0:
+ version "5.2.0"
+ resolved "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz#07449690ad45777d1924ac2abb2fc8895dba836b"
+ integrity sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==
+
ansi-to-html@^0.6.11:
version "0.6.14"
resolved "https://registry.npmjs.org/ansi-to-html/-/ansi-to-html-0.6.14.tgz#65fe6d08bba5dd9db33f44a20aec331e0010dad8"
@@ -9362,6 +9368,11 @@ aria-query@^4.2.2:
"@babel/runtime" "^7.10.2"
"@babel/runtime-corejs3" "^7.10.2"
+aria-query@^5.0.0:
+ version "5.0.0"
+ resolved "https://registry.npmjs.org/aria-query/-/aria-query-5.0.0.tgz#210c21aaf469613ee8c9a62c7f86525e058db52c"
+ integrity sha512-V+SM7AbUwJ+EBnB8+DXs0hPZHO0W6pqBcc0dW90OwtVG02PswOu/teuARoLQjdDOH+t9pJgGnW5/Qmouf3gPJg==
+
arr-diff@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520"
@@ -10388,7 +10399,7 @@ browserslist@4.14.2:
escalade "^3.0.2"
node-releases "^1.1.61"
-browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.6:
+browserslist@^4.0.0, browserslist@^4.12.0, browserslist@^4.14.5, browserslist@^4.16.0, browserslist@^4.16.6:
version "4.18.1"
resolved "https://registry.npmjs.org/browserslist/-/browserslist-4.18.1.tgz#60d3920f25b6860eb917c6c7b185576f4d8b017f"
integrity sha512-8ScCzdpPwR2wQh8IT82CA2VgDwjHyqMovPBZSNH54+tm4Jk2pCuv90gmAdH6J84OCRWi0b4gMe6O6XPXuJnjgQ==
@@ -10670,25 +10681,6 @@ call-me-maybe@^1.0.1:
resolved "https://registry.npmjs.org/call-me-maybe/-/call-me-maybe-1.0.1.tgz#26d208ea89e37b5cbde60250a15f031c16a4d66b"
integrity sha1-JtII6onje1y95gJQoV8DHBak1ms=
-caller-callsite@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/caller-callsite/-/caller-callsite-2.0.0.tgz#847e0fce0a223750a9a027c54b33731ad3154134"
- integrity sha1-hH4PzgoiN1CpoCfFSzNzGtMVQTQ=
- dependencies:
- callsites "^2.0.0"
-
-caller-path@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/caller-path/-/caller-path-2.0.0.tgz#468f83044e369ab2010fac5f06ceee15bb2cb1f4"
- integrity sha1-Ro+DBE42mrIBD6xfBs7uFbsssfQ=
- dependencies:
- caller-callsite "^2.0.0"
-
-callsites@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/callsites/-/callsites-2.0.0.tgz#06eb84f00eea413da86affefacbffb36093b3c50"
- integrity sha1-BuuE8A7qQT2oav/vrL/7Ngk7PFA=
-
callsites@^3.0.0:
version "3.1.0"
resolved "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz#b3630abd8943432f54b3f0519238e33cd7df2f73"
@@ -11332,7 +11324,7 @@ color-name@^1.0.0, color-name@^1.1.4, color-name@~1.1.4:
resolved "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
-color-string@^1.5.2, color-string@^1.5.4, color-string@^1.6.0:
+color-string@^1.5.2, color-string@^1.6.0:
version "1.6.0"
resolved "https://registry.npmjs.org/color-string/-/color-string-1.6.0.tgz#c3915f61fe267672cb7e1e064c9d692219f6c312"
integrity sha512-c/hGS+kRWJutUBEngKKmk4iH3sD59MBkoxVapS/0wgpCz2u7XsNloxknyvBhzwEs1IbV36D9PwqLPJ2DTu3vMA==
@@ -11348,14 +11340,6 @@ color@3.0.x:
color-convert "^1.9.1"
color-string "^1.5.2"
-color@^3.0.0:
- version "3.1.3"
- resolved "https://registry.npmjs.org/color/-/color-3.1.3.tgz#ca67fb4e7b97d611dcde39eceed422067d91596e"
- integrity sha512-xgXAcTHa2HeFCGLE9Xs/R82hujGtu9Jd9x4NW3T34+OMs7VoPsjwzRczKHvTAHeJwWFwX5j15+MgAppE8ztObQ==
- dependencies:
- color-convert "^1.9.1"
- color-string "^1.5.4"
-
color@^4.0.1:
version "4.0.1"
resolved "https://registry.npmjs.org/color/-/color-4.0.1.tgz#21df44cd10245a91b1ccf5ba031609b0e10e7d67"
@@ -11364,6 +11348,11 @@ color@^4.0.1:
color-convert "^2.0.1"
color-string "^1.6.0"
+colord@^2.9.1:
+ version "2.9.1"
+ resolved "https://registry.npmjs.org/colord/-/colord-2.9.1.tgz#c961ea0efeb57c9f0f4834458f26cb9cc4a3f90e"
+ integrity sha512-4LBMSt09vR0uLnPVkOUBnmxgoaeN4ewRbx801wY/bXcltXfpR/G46OdWn96XpYmCWuYvO46aBZP4NgX8HpNAcw==
+
colorette@1.2.1:
version "1.2.1"
resolved "https://registry.npmjs.org/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
@@ -11878,16 +11867,6 @@ cosmiconfig@7.0.0, cosmiconfig@^7.0.0:
path-type "^4.0.0"
yaml "^1.10.0"
-cosmiconfig@^5.0.0:
- version "5.2.1"
- resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-5.2.1.tgz#040f726809c591e77a17c0a3626ca45b4f168b1a"
- integrity sha512-H65gsXo1SKjf8zmrJ67eJk8aIRKV5ff2D4uKZIBZShbhGSpEmsQOPW/SKMKYhSTrqR7ufy6RP69rPogdaPh/kA==
- dependencies:
- import-fresh "^2.0.0"
- is-directory "^0.3.1"
- js-yaml "^3.13.1"
- parse-json "^4.0.0"
-
cosmiconfig@^6.0.0:
version "6.0.0"
resolved "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-6.0.0.tgz#da4fee853c52f6b1e6935f41c1a2fc50bd4a9982"
@@ -12084,17 +12063,11 @@ css-color-converter@^2.0.0:
color-name "^1.1.4"
css-unit-converter "^1.1.2"
-css-color-names@0.0.4, css-color-names@^0.0.4:
- version "0.0.4"
- resolved "https://registry.npmjs.org/css-color-names/-/css-color-names-0.0.4.tgz#808adc2e79cf84738069b646cb20ec27beb629e0"
- integrity sha1-gIrcLnnPhHOAabZGyyDsJ762KeA=
-
-css-declaration-sorter@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-4.0.1.tgz#c198940f63a76d7e36c1e71018b001721054cb22"
- integrity sha512-BcxQSKTSEEQUftYpBVnsH4SF05NTuBokb19/sBt6asXGKZ/6VP7PLG1CBCkFDYOnhXhPh0jMhO6xZ71oYHXHBA==
+css-declaration-sorter@^6.0.3:
+ version "6.1.3"
+ resolved "https://registry.npmjs.org/css-declaration-sorter/-/css-declaration-sorter-6.1.3.tgz#e9852e4cf940ba79f509d9425b137d1f94438dc2"
+ integrity sha512-SvjQjNRZgh4ULK1LDJ2AduPKUKxIqmtU7ZAyi47BTV+M90Qvxr9AB6lKlLbDUfXqI9IQeYA8LbAsCZPpJEV3aA==
dependencies:
- postcss "^7.0.1"
timsort "^0.3.0"
css-in-js-utils@^2.0.0:
@@ -12182,6 +12155,14 @@ css-tree@^1.1.2:
mdn-data "2.0.14"
source-map "^0.6.1"
+css-tree@^1.1.3:
+ version "1.1.3"
+ resolved "https://registry.npmjs.org/css-tree/-/css-tree-1.1.3.tgz#eb4870fb6fd7707327ec95c2ff2ab09b5e8db91d"
+ integrity sha512-tRpdppF7TRazZrjJ6v3stzv93qxRcSsFmW6cX0Zm2NVKpxE1WV1HblnghVv9TreireHkqI/VDEsfolRF1p6y7Q==
+ dependencies:
+ mdn-data "2.0.14"
+ source-map "^0.6.1"
+
css-unit-converter@^1.1.2:
version "1.1.2"
resolved "https://registry.npmjs.org/css-unit-converter/-/css-unit-converter-1.1.2.tgz#4c77f5a1954e6dbff60695ecb214e3270436ab21"
@@ -12229,73 +12210,55 @@ cssfilter@0.0.10:
resolved "https://registry.npmjs.org/cssfilter/-/cssfilter-0.0.10.tgz#c6d2672632a2e5c83e013e6864a42ce8defd20ae"
integrity sha1-xtJnJjKi5cg+AT5oZKQs6N79IK4=
-cssnano-preset-default@^4.0.7:
- version "4.0.7"
- resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-4.0.7.tgz#51ec662ccfca0f88b396dcd9679cdb931be17f76"
- integrity sha512-x0YHHx2h6p0fCl1zY9L9roD7rnlltugGu7zXSKQx6k2rYw0Hi3IqxcoAGF7u9Q5w1nt7vK0ulxV8Lo+EvllGsA==
+cssnano-preset-default@^5.1.7:
+ version "5.1.7"
+ resolved "https://registry.npmjs.org/cssnano-preset-default/-/cssnano-preset-default-5.1.7.tgz#68c3ad1ec6a810482ec7d06b2d70fc34b6b0d70c"
+ integrity sha512-bWDjtTY+BOqrqBtsSQIbN0RLGD2Yr2CnecpP0ydHNafh9ZUEre8c8VYTaH9FEbyOt0eIfEUAYYk5zj92ioO8LA==
dependencies:
- css-declaration-sorter "^4.0.1"
- cssnano-util-raw-cache "^4.0.1"
- postcss "^7.0.0"
- postcss-calc "^7.0.1"
- postcss-colormin "^4.0.3"
- postcss-convert-values "^4.0.1"
- postcss-discard-comments "^4.0.2"
- postcss-discard-duplicates "^4.0.2"
- postcss-discard-empty "^4.0.1"
- postcss-discard-overridden "^4.0.1"
- postcss-merge-longhand "^4.0.11"
- postcss-merge-rules "^4.0.3"
- postcss-minify-font-values "^4.0.2"
- postcss-minify-gradients "^4.0.2"
- postcss-minify-params "^4.0.2"
- postcss-minify-selectors "^4.0.2"
- postcss-normalize-charset "^4.0.1"
- postcss-normalize-display-values "^4.0.2"
- postcss-normalize-positions "^4.0.2"
- postcss-normalize-repeat-style "^4.0.2"
- postcss-normalize-string "^4.0.2"
- postcss-normalize-timing-functions "^4.0.2"
- postcss-normalize-unicode "^4.0.1"
- postcss-normalize-url "^4.0.1"
- postcss-normalize-whitespace "^4.0.2"
- postcss-ordered-values "^4.1.2"
- postcss-reduce-initial "^4.0.3"
- postcss-reduce-transforms "^4.0.2"
- postcss-svgo "^4.0.2"
- postcss-unique-selectors "^4.0.1"
+ css-declaration-sorter "^6.0.3"
+ cssnano-utils "^2.0.1"
+ postcss-calc "^8.0.0"
+ postcss-colormin "^5.2.1"
+ postcss-convert-values "^5.0.2"
+ postcss-discard-comments "^5.0.1"
+ postcss-discard-duplicates "^5.0.1"
+ postcss-discard-empty "^5.0.1"
+ postcss-discard-overridden "^5.0.1"
+ postcss-merge-longhand "^5.0.4"
+ postcss-merge-rules "^5.0.3"
+ postcss-minify-font-values "^5.0.1"
+ postcss-minify-gradients "^5.0.3"
+ postcss-minify-params "^5.0.2"
+ postcss-minify-selectors "^5.1.0"
+ postcss-normalize-charset "^5.0.1"
+ postcss-normalize-display-values "^5.0.1"
+ postcss-normalize-positions "^5.0.1"
+ postcss-normalize-repeat-style "^5.0.1"
+ postcss-normalize-string "^5.0.1"
+ postcss-normalize-timing-functions "^5.0.1"
+ postcss-normalize-unicode "^5.0.1"
+ postcss-normalize-url "^5.0.3"
+ postcss-normalize-whitespace "^5.0.1"
+ postcss-ordered-values "^5.0.2"
+ postcss-reduce-initial "^5.0.1"
+ postcss-reduce-transforms "^5.0.1"
+ postcss-svgo "^5.0.3"
+ postcss-unique-selectors "^5.0.2"
-cssnano-util-get-arguments@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/cssnano-util-get-arguments/-/cssnano-util-get-arguments-4.0.0.tgz#ed3a08299f21d75741b20f3b81f194ed49cc150f"
- integrity sha1-7ToIKZ8h11dBsg87gfGU7UnMFQ8=
+cssnano-utils@^2.0.1:
+ version "2.0.1"
+ resolved "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz#8660aa2b37ed869d2e2f22918196a9a8b6498ce2"
+ integrity sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==
-cssnano-util-get-match@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/cssnano-util-get-match/-/cssnano-util-get-match-4.0.0.tgz#c0e4ca07f5386bb17ec5e52250b4f5961365156d"
- integrity sha1-wOTKB/U4a7F+xeUiULT1lhNlFW0=
-
-cssnano-util-raw-cache@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/cssnano-util-raw-cache/-/cssnano-util-raw-cache-4.0.1.tgz#b26d5fd5f72a11dfe7a7846fb4c67260f96bf282"
- integrity sha512-qLuYtWK2b2Dy55I8ZX3ky1Z16WYsx544Q0UWViebptpwn/xDBmog2TLg4f+DBMg1rJ6JDWtn96WHbOKDWt1WQA==
+cssnano@^5.0.1:
+ version "5.0.11"
+ resolved "https://registry.npmjs.org/cssnano/-/cssnano-5.0.11.tgz#743397a05e04cb87e9df44b7659850adfafc3646"
+ integrity sha512-5SHM31NAAe29jvy0MJqK40zZ/8dGlnlzcfHKw00bWMVFp8LWqtuyPSFwbaoIoxvt71KWJOfg8HMRGrBR3PExCg==
dependencies:
- postcss "^7.0.0"
-
-cssnano-util-same-parent@^4.0.0:
- version "4.0.1"
- resolved "https://registry.npmjs.org/cssnano-util-same-parent/-/cssnano-util-same-parent-4.0.1.tgz#574082fb2859d2db433855835d9a8456ea18bbf3"
- integrity sha512-WcKx5OY+KoSIAxBW6UBBRay1U6vkYheCdjyVNDm85zt5K9mHoGOfsOsqIszfAqrQQFIIKgjh2+FDgIj/zsl21Q==
-
-cssnano@^4.1.10:
- version "4.1.10"
- resolved "https://registry.npmjs.org/cssnano/-/cssnano-4.1.10.tgz#0ac41f0b13d13d465487e111b778d42da631b8b2"
- integrity sha512-5wny+F6H4/8RgNlaqab4ktc3e0/blKutmq8yNlBFXA//nSFFAqAngjNVRzUvCgYROULmZZUoosL/KSoZo5aUaQ==
- dependencies:
- cosmiconfig "^5.0.0"
- cssnano-preset-default "^4.0.7"
- is-resolvable "^1.0.0"
- postcss "^7.0.0"
+ cssnano-preset-default "^5.1.7"
+ is-resolvable "^1.1.0"
+ lilconfig "^2.0.3"
+ yaml "^1.10.2"
csso@^4.0.2:
version "4.0.2"
@@ -12304,6 +12267,13 @@ csso@^4.0.2:
dependencies:
css-tree "1.0.0-alpha.37"
+csso@^4.2.0:
+ version "4.2.0"
+ resolved "https://registry.npmjs.org/csso/-/csso-4.2.0.tgz#ea3a561346e8dc9f546d6febedd50187cf389529"
+ integrity sha512-wvlcdIbf6pwKEk7vHj8/Bkc0B4ylXZruLvOgs9doS5eOsOpuodOV2zJChSpkp+pRpYQLQMeF04nr3Z68Sta9jA==
+ dependencies:
+ css-tree "^1.1.2"
+
cssom@^0.4.4:
version "0.4.4"
resolved "https://registry.npmjs.org/cssom/-/cssom-0.4.4.tgz#5a66cf93d2d0b661d80bf6a44fb65f5c2e4e0a10"
@@ -13162,6 +13132,11 @@ dom-accessibility-api@^0.5.4, dom-accessibility-api@^0.5.6:
resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.6.tgz#3f5d43b52c7a3bd68b5fb63fa47b4e4c1fdf65a9"
integrity sha512-DplGLZd8L1lN64jlT27N9TVSESFR5STaEJvX+thCby7fuCHonfPpAlodYc3vuUYbDuDec5w8AMP7oCM5TWFsqw==
+dom-accessibility-api@^0.5.9:
+ version "0.5.10"
+ resolved "https://registry.npmjs.org/dom-accessibility-api/-/dom-accessibility-api-0.5.10.tgz#caa6d08f60388d0bb4539dd75fe458a9a1d0014c"
+ integrity sha512-Xu9mD0UjrJisTmv7lmVSDMagQcU9R5hwAbxsaAE/35XPnPLJobbuREfV/rraiSaEj/UOvgrzQs66zyTWTlyd+g==
+
dom-converter@^0.2.0:
version "0.2.0"
resolved "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz#6721a9daee2e293682955b6afe416771627bb768"
@@ -16055,7 +16030,7 @@ has-yarn@^2.1.0:
resolved "https://registry.npmjs.org/has-yarn/-/has-yarn-2.1.0.tgz#137e11354a7b5bf11aa5cb649cf0c6f3ff2b2e77"
integrity sha512-UqBRqi4ju7T+TqGNdqAO0PaSVGsDGJUBQvk9eUWNGRY1CFGDzYhLWoM7JQEemnlvVcv/YEmc2wNW8BC24EnUsw==
-has@^1.0.0, has@^1.0.3:
+has@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
integrity sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==
@@ -16179,11 +16154,6 @@ helmet@^4.0.0:
resolved "https://registry.npmjs.org/helmet/-/helmet-4.4.1.tgz#a17e1444d81d7a83ddc6e6f9bc6e2055b994efe7"
integrity sha512-G8tp0wUMI7i8wkMk2xLcEvESg5PiCitFMYgGRc/PwULB0RVhTP5GFdxOwvJwp9XVha8CuS8mnhmE8I/8dx/pbw==
-hex-color-regex@^1.1.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/hex-color-regex/-/hex-color-regex-1.1.0.tgz#4c06fccb4602fe2602b3c93df82d7e7dbf1a8a8e"
- integrity sha512-l9sfDFsuqtOqKDsQdqrMRk0U85RZc0RtOR9yPI7mRVOa4FsR/BVnZ0shmQRM96Ji99kYZP/7hn1cedc1+ApsTQ==
-
highlight.js@^10.1.0, highlight.js@^10.1.1, highlight.js@^10.4.1, highlight.js@^10.6.0:
version "10.7.2"
resolved "https://registry.npmjs.org/highlight.js/-/highlight.js-10.7.2.tgz#89319b861edc66c48854ed1e6da21ea89f847360"
@@ -16256,16 +16226,6 @@ hpagent@^0.1.1:
resolved "https://registry.npmjs.org/hpagent/-/hpagent-0.1.2.tgz#cab39c66d4df2d4377dbd212295d878deb9bdaa9"
integrity sha512-ePqFXHtSQWAFXYmj+JtOTHr84iNrII4/QRlAAPPE+zqnKy4xJo7Ie1Y4kC7AdB+LxLxSTTzBMASsEcy0q8YyvQ==
-hsl-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/hsl-regex/-/hsl-regex-1.0.0.tgz#d49330c789ed819e276a4c0d272dffa30b18fe6e"
- integrity sha1-1JMwx4ntgZ4nakwNJy3/owsY/m4=
-
-hsla-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/hsla-regex/-/hsla-regex-1.0.0.tgz#c1ce7a3168c8c6614033a4b5f7877f3b225f9c38"
- integrity sha1-wc56MWjIxmFAM6S194d/OyJfnDg=
-
html-encoding-sniffer@^2.0.1:
version "2.0.1"
resolved "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-2.0.1.tgz#42a6dc4fd33f00281176e8b23759ca4e4fa185f3"
@@ -16639,14 +16599,6 @@ import-cwd@^3.0.0:
dependencies:
import-from "^3.0.0"
-import-fresh@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546"
- integrity sha1-2BNVwVYS04bGH53dOSLUMEgipUY=
- dependencies:
- caller-path "^2.0.0"
- resolve-from "^3.0.0"
-
import-fresh@^3.0.0, import-fresh@^3.1.0, import-fresh@^3.2.1:
version "3.2.1"
resolved "https://registry.npmjs.org/import-fresh/-/import-fresh-3.2.1.tgz#633ff618506e793af5ac91bf48b72677e15cbe66"
@@ -16916,10 +16868,10 @@ ipaddr.js@^2.0.1:
resolved "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.0.1.tgz#eca256a7a877e917aeb368b0a7497ddf42ef81c0"
integrity sha512-1qTgH9NG+IIJ4yfKs2e6Pp1bZg8wbDbKHT21HrLIeYBTRLgMYKnMTPAuI3Lcs61nfx5h1xlXnbJtH1kX5/d/ng==
-is-absolute-url@^2.0.0:
- version "2.1.0"
- resolved "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-2.1.0.tgz#50530dfb84fcc9aa7dbe7852e83a37b93b9f2aa6"
- integrity sha1-UFMN+4T8yap9vnhS6Do3uTufKqY=
+is-absolute-url@^3.0.3:
+ version "3.0.3"
+ resolved "https://registry.npmjs.org/is-absolute-url/-/is-absolute-url-3.0.3.tgz#96c6a22b6a23929b11ea0afb1836c36ad4a5d698"
+ integrity sha512-opmNIX7uFnS96NtPmhWQgQx6/NYFgsUXYMllcfzwWKUMwfo8kku1TvE6hkNcH+Q1ts5cMVrsY7j0bxXQDciu9Q==
is-absolute@^1.0.0:
version "1.0.0"
@@ -17039,18 +16991,6 @@ is-ci@^3.0.0:
dependencies:
ci-info "^3.1.1"
-is-color-stop@^1.0.0:
- version "1.1.0"
- resolved "https://registry.npmjs.org/is-color-stop/-/is-color-stop-1.1.0.tgz#cfff471aee4dd5c9e158598fbe12967b5cdad345"
- integrity sha1-z/9HGu5N1cnhWFmPvhKWe1za00U=
- dependencies:
- css-color-names "^0.0.4"
- hex-color-regex "^1.1.0"
- hsl-regex "^1.0.0"
- hsla-regex "^1.0.0"
- rgb-regex "^1.0.1"
- rgba-regex "^1.0.0"
-
is-core-module@^2.1.0, is-core-module@^2.2.0:
version "2.4.0"
resolved "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz#8e9fc8e15027b011418026e98f0e6f4d86305cc1"
@@ -17105,11 +17045,6 @@ is-descriptor@^1.0.0, is-descriptor@^1.0.2:
is-data-descriptor "^1.0.0"
kind-of "^6.0.2"
-is-directory@^0.3.1:
- version "0.3.1"
- resolved "https://registry.npmjs.org/is-directory/-/is-directory-0.3.1.tgz#61339b6f2475fc772fd9c9d83f5c8575dc154ae1"
- integrity sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=
-
is-docker@^2.0.0, is-docker@^2.1.1:
version "2.2.1"
resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa"
@@ -17423,7 +17358,7 @@ is-relative@^1.0.0:
dependencies:
is-unc-path "^1.0.0"
-is-resolvable@^1.0.0:
+is-resolvable@^1.1.0:
version "1.1.0"
resolved "https://registry.npmjs.org/is-resolvable/-/is-resolvable-1.1.0.tgz#fb18f87ce1feb925169c9a407c19318a3206ed88"
integrity sha512-qgDYXFSR5WvEfuS5dMj6oTMEbrrSaM0CrFk2Yiq/gXnBvD9pMa2jGXxyhGLfvhZpuMZe18CJpFxAt3CRs42NMg==
@@ -19001,6 +18936,11 @@ libnpmpublish@^4.0.0:
semver "^7.1.3"
ssri "^8.0.0"
+lilconfig@^2.0.3:
+ version "2.0.4"
+ resolved "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.4.tgz#f4507d043d7058b380b6a8f5cb7bcd4b34cee082"
+ integrity sha512-bfTIN7lEsiooCocSISTWXkiWJkRqtL9wYtYy+8EK3Y41qh3mpwPU0ycTOgjdY9ErwXCc8QyrQp82bdL0Xkm9yA==
+
lines-and-columns@^1.1.6:
version "1.1.6"
resolved "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.1.6.tgz#1c00c743b433cd0a4e80758f7b64a57440d9ff00"
@@ -20547,6 +20487,11 @@ mime@^2.2.0, mime@^2.4.4, mime@^2.4.6:
resolved "https://registry.npmjs.org/mime/-/mime-2.5.2.tgz#6e3dc6cc2b9510643830e5f19d5cb753da5eeabe"
integrity sha512-tqkh47FzKeCPD2PUiPB6pkbMzsCasjxAfC62/Wap5qrUWcb+sFasXUC5I3gYM5iBM8v/Qpn4UK0x+j0iHyFPDg==
+mime@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.npmjs.org/mime/-/mime-3.0.0.tgz#b374550dca3a0c18443b0c950a6a58f1931cf7a7"
+ integrity sha512-jSCU7/VB1loIWBZe14aEYHU/+1UMEHoaO7qxCOVJOw9GgH72VAWppxNcjU+x9a2k3GSIBXNKxXQFqRvvZ7vr3A==
+
mimic-fn@^1.0.0:
version "1.2.0"
resolved "https://registry.npmjs.org/mimic-fn/-/mimic-fn-1.2.0.tgz#820c86a39334640e99516928bd03fca88057d022"
@@ -21361,7 +21306,7 @@ normalize-range@^0.1.2:
resolved "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz#2d10c06bdfd312ea9777695a4d28439456b75942"
integrity sha1-LRDAa9/TEuqXd2laTShDlFa3WUI=
-normalize-url@^3.0.0, normalize-url@^3.3.0:
+normalize-url@^3.3.0:
version "3.3.0"
resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-3.3.0.tgz#b2e1c4dc4f7c6d57743df733a4f5978d18650559"
integrity sha512-U+JJi7duF1o+u2pynbp2zXDW2/PADgC30f0GsHZtRh+HOcXHnw137TrNlyxxRvWW5fjKd3bcLHPxofWuCjaeZg==
@@ -21371,6 +21316,11 @@ normalize-url@^4.1.0:
resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-4.5.1.tgz#0dd90cf1288ee1d1313b87081c9a5932ee48518a"
integrity sha512-9UZCFRHQdNrfTpGg8+1INIg93B6zE0aXMVFkw1WFwvO4SlZywU6aLg5Of0Ap/PgcbSw4LNxvMWXMeugwMCX0AA==
+normalize-url@^6.0.1:
+ version "6.1.0"
+ resolved "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz#40d0885b535deffe3f3147bec877d05fe4c5668a"
+ integrity sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==
+
npm-bundled@^1.0.1, npm-bundled@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.1.tgz#1edd570865a94cdb1bc8220775e29466c9fb234b"
@@ -22820,61 +22770,50 @@ posix-character-classes@^0.1.0:
resolved "https://registry.npmjs.org/posix-character-classes/-/posix-character-classes-0.1.1.tgz#01eac0fe3b5af71a2a6c02feabb8c1fef7e00eab"
integrity sha1-AerA/jta9xoqbAL+q7jB/vfgDqs=
-postcss-calc@^7.0.1:
- version "7.0.2"
- resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-7.0.2.tgz#504efcd008ca0273120568b0792b16cdcde8aac1"
- integrity sha512-rofZFHUg6ZIrvRwPeFktv06GdbDYLcGqh9EwiMutZg+a0oePCCw1zHOEiji6LCpyRcjTREtPASuUqeAvYlEVvQ==
+postcss-calc@^8.0.0:
+ version "8.0.0"
+ resolved "https://registry.npmjs.org/postcss-calc/-/postcss-calc-8.0.0.tgz#a05b87aacd132740a5db09462a3612453e5df90a"
+ integrity sha512-5NglwDrcbiy8XXfPM11F3HeC6hoT9W7GUH/Zi5U/p7u3Irv4rHhdDcIZwG0llHXV4ftsBjpfWMXAnXNl4lnt8g==
dependencies:
- postcss "^7.0.27"
postcss-selector-parser "^6.0.2"
postcss-value-parser "^4.0.2"
-postcss-colormin@^4.0.3:
- version "4.0.3"
- resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-4.0.3.tgz#ae060bce93ed794ac71264f08132d550956bd381"
- integrity sha512-WyQFAdDZpExQh32j0U0feWisZ0dmOtPl44qYmJKkq9xFWY3p+4qnRzCHeNrkeRhwPHz9bQ3mo0/yVkaply0MNw==
+postcss-colormin@^5.2.1:
+ version "5.2.1"
+ resolved "https://registry.npmjs.org/postcss-colormin/-/postcss-colormin-5.2.1.tgz#6e444a806fd3c578827dbad022762df19334414d"
+ integrity sha512-VVwMrEYLcHYePUYV99Ymuoi7WhKrMGy/V9/kTS0DkCoJYmmjdOMneyhzYUxcNgteKDVbrewOkSM7Wje/MFwxzA==
dependencies:
- browserslist "^4.0.0"
- color "^3.0.0"
- has "^1.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ browserslist "^4.16.6"
+ caniuse-api "^3.0.0"
+ colord "^2.9.1"
+ postcss-value-parser "^4.1.0"
-postcss-convert-values@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-4.0.1.tgz#ca3813ed4da0f812f9d43703584e449ebe189a7f"
- integrity sha512-Kisdo1y77KUC0Jmn0OXU/COOJbzM8cImvw1ZFsBgBgMgb1iL23Zs/LXRe3r+EZqM3vGYKdQ2YJVQ5VkJI+zEJQ==
+postcss-convert-values@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/postcss-convert-values/-/postcss-convert-values-5.0.2.tgz#879b849dc3677c7d6bc94b6a2c1a3f0808798059"
+ integrity sha512-KQ04E2yadmfa1LqXm7UIDwW1ftxU/QWZmz6NKnHnUvJ3LEYbbcX6i329f/ig+WnEByHegulocXrECaZGLpL8Zg==
dependencies:
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ postcss-value-parser "^4.1.0"
-postcss-discard-comments@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-4.0.2.tgz#1fbabd2c246bff6aaad7997b2b0918f4d7af4033"
- integrity sha512-RJutN259iuRf3IW7GZyLM5Sw4GLTOH8FmsXBnv8Ab/Tc2k4SR4qbV4DNbyyY4+Sjo362SyDmW2DQ7lBSChrpkg==
- dependencies:
- postcss "^7.0.0"
+postcss-discard-comments@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz#9eae4b747cf760d31f2447c27f0619d5718901fe"
+ integrity sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==
-postcss-discard-duplicates@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-4.0.2.tgz#3fe133cd3c82282e550fc9b239176a9207b784eb"
- integrity sha512-ZNQfR1gPNAiXZhgENFfEglF93pciw0WxMkJeVmw8eF+JZBbMD7jp6C67GqJAXVZP2BWbOztKfbsdmMp/k8c6oQ==
- dependencies:
- postcss "^7.0.0"
+postcss-discard-duplicates@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz#68f7cc6458fe6bab2e46c9f55ae52869f680e66d"
+ integrity sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==
-postcss-discard-empty@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-4.0.1.tgz#c8c951e9f73ed9428019458444a02ad90bb9f765"
- integrity sha512-B9miTzbznhDjTfjvipfHoqbWKwd0Mj+/fL5s1QOz06wufguil+Xheo4XpOnc4NqKYBCNqqEzgPv2aPBIJLox0w==
- dependencies:
- postcss "^7.0.0"
+postcss-discard-empty@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz#ee136c39e27d5d2ed4da0ee5ed02bc8a9f8bf6d8"
+ integrity sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==
-postcss-discard-overridden@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-4.0.1.tgz#652aef8a96726f029f5e3e00146ee7a4e755ff57"
- integrity sha512-IYY2bEDD7g1XM1IDEsUT4//iEYCxAmP5oDSFMVU/JVvT7gh+l4fmjciLqGgwjdWpQIdb0Che2VX00QObS5+cTg==
- dependencies:
- postcss "^7.0.0"
+postcss-discard-overridden@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz#454b41f707300b98109a75005ca4ab0ff2743ac6"
+ integrity sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==
postcss-flexbugs-fixes@^4.2.1:
version "4.2.1"
@@ -22902,67 +22841,57 @@ postcss-loader@^4.2.0:
schema-utils "^3.0.0"
semver "^7.3.4"
-postcss-merge-longhand@^4.0.11:
- version "4.0.11"
- resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-4.0.11.tgz#62f49a13e4a0ee04e7b98f42bb16062ca2549e24"
- integrity sha512-alx/zmoeXvJjp7L4mxEMjh8lxVlDFX1gqWHzaaQewwMZiVhLo42TEClKaeHbRf6J7j82ZOdTJ808RtN0ZOZwvw==
+postcss-merge-longhand@^5.0.4:
+ version "5.0.4"
+ resolved "https://registry.npmjs.org/postcss-merge-longhand/-/postcss-merge-longhand-5.0.4.tgz#41f4f3270282ea1a145ece078b7679f0cef21c32"
+ integrity sha512-2lZrOVD+d81aoYkZDpWu6+3dTAAGkCKbV5DoRhnIR7KOULVrI/R7bcMjhrH9KTRy6iiHKqmtG+n/MMj1WmqHFw==
dependencies:
- css-color-names "0.0.4"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
- stylehacks "^4.0.0"
+ postcss-value-parser "^4.1.0"
+ stylehacks "^5.0.1"
-postcss-merge-rules@^4.0.3:
- version "4.0.3"
- resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-4.0.3.tgz#362bea4ff5a1f98e4075a713c6cb25aefef9a650"
- integrity sha512-U7e3r1SbvYzO0Jr3UT/zKBVgYYyhAz0aitvGIYOYK5CPmkNih+WDSsS5tvPrJ8YMQYlEMvsZIiqmn7HdFUaeEQ==
+postcss-merge-rules@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/postcss-merge-rules/-/postcss-merge-rules-5.0.3.tgz#b5cae31f53129812a77e3eb1eeee448f8cf1a1db"
+ integrity sha512-cEKTMEbWazVa5NXd8deLdCnXl+6cYG7m2am+1HzqH0EnTdy8fRysatkaXb2dEnR+fdaDxTvuZ5zoBdv6efF6hg==
dependencies:
- browserslist "^4.0.0"
+ browserslist "^4.16.6"
caniuse-api "^3.0.0"
- cssnano-util-same-parent "^4.0.0"
- postcss "^7.0.0"
- postcss-selector-parser "^3.0.0"
- vendors "^1.0.0"
+ cssnano-utils "^2.0.1"
+ postcss-selector-parser "^6.0.5"
-postcss-minify-font-values@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-4.0.2.tgz#cd4c344cce474343fac5d82206ab2cbcb8afd5a6"
- integrity sha512-j85oO6OnRU9zPf04+PZv1LYIYOprWm6IA6zkXkrJXyRveDEuQggG6tvoy8ir8ZwjLxLuGfNkCZEQG7zan+Hbtg==
+postcss-minify-font-values@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-minify-font-values/-/postcss-minify-font-values-5.0.1.tgz#a90cefbfdaa075bd3dbaa1b33588bb4dc268addf"
+ integrity sha512-7JS4qIsnqaxk+FXY1E8dHBDmraYFWmuL6cgt0T1SWGRO5bzJf8sUoelwa4P88LEWJZweHevAiDKxHlofuvtIoA==
dependencies:
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ postcss-value-parser "^4.1.0"
-postcss-minify-gradients@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-4.0.2.tgz#93b29c2ff5099c535eecda56c4aa6e665a663471"
- integrity sha512-qKPfwlONdcf/AndP1U8SJ/uzIJtowHlMaSioKzebAXSG4iJthlWC9iSWznQcX4f66gIWX44RSA841HTHj3wK+Q==
+postcss-minify-gradients@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/postcss-minify-gradients/-/postcss-minify-gradients-5.0.3.tgz#f970a11cc71e08e9095e78ec3a6b34b91c19550e"
+ integrity sha512-Z91Ol22nB6XJW+5oe31+YxRsYooxOdFKcbOqY/V8Fxse1Y3vqlNRpi1cxCqoACZTQEhl+xvt4hsbWiV5R+XI9Q==
dependencies:
- cssnano-util-get-arguments "^4.0.0"
- is-color-stop "^1.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ colord "^2.9.1"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-minify-params@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-4.0.2.tgz#6b9cef030c11e35261f95f618c90036d680db874"
- integrity sha512-G7eWyzEx0xL4/wiBBJxJOz48zAKV2WG3iZOqVhPet/9geefm/Px5uo1fzlHu+DOjT+m0Mmiz3jkQzVHe6wxAWg==
+postcss-minify-params@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/postcss-minify-params/-/postcss-minify-params-5.0.2.tgz#1b644da903473fbbb18fbe07b8e239883684b85c"
+ integrity sha512-qJAPuBzxO1yhLad7h2Dzk/F7n1vPyfHfCCh5grjGfjhi1ttCnq4ZXGIW77GSrEbh9Hus9Lc/e/+tB4vh3/GpDg==
dependencies:
- alphanum-sort "^1.0.0"
- browserslist "^4.0.0"
- cssnano-util-get-arguments "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
- uniqs "^2.0.0"
+ alphanum-sort "^1.0.2"
+ browserslist "^4.16.6"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-minify-selectors@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-4.0.2.tgz#e2e5eb40bfee500d0cd9243500f5f8ea4262fbd8"
- integrity sha512-D5S1iViljXBj9kflQo4YutWnJmwm8VvIsU1GeXJGiG9j8CIg9zs4voPMdQDUmIxetUOh60VilsNzCiAFTOqu3g==
+postcss-minify-selectors@^5.1.0:
+ version "5.1.0"
+ resolved "https://registry.npmjs.org/postcss-minify-selectors/-/postcss-minify-selectors-5.1.0.tgz#4385c845d3979ff160291774523ffa54eafd5a54"
+ integrity sha512-NzGBXDa7aPsAcijXZeagnJBKBPMYLaJJzB8CQh6ncvyl2sIndLVWfbcDi0SBjRWk5VqEjXvf8tYwzoKf4Z07og==
dependencies:
- alphanum-sort "^1.0.0"
- has "^1.0.0"
- postcss "^7.0.0"
- postcss-selector-parser "^3.0.0"
+ alphanum-sort "^1.0.2"
+ postcss-selector-parser "^6.0.5"
postcss-modules-extract-imports@^2.0.0:
version "2.0.0"
@@ -23039,124 +22968,96 @@ postcss-modules@^4.0.0:
postcss-modules-values "^4.0.0"
string-hash "^1.1.1"
-postcss-normalize-charset@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-4.0.1.tgz#8b35add3aee83a136b0471e0d59be58a50285dd4"
- integrity sha512-gMXCrrlWh6G27U0hF3vNvR3w8I1s2wOBILvA87iNXaPvSNo5uZAMYsZG7XjCUf1eVxuPfyL4TJ7++SGZLc9A3g==
- dependencies:
- postcss "^7.0.0"
+postcss-normalize-charset@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz#121559d1bebc55ac8d24af37f67bd4da9efd91d0"
+ integrity sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==
-postcss-normalize-display-values@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-4.0.2.tgz#0dbe04a4ce9063d4667ed2be476bb830c825935a"
- integrity sha512-3F2jcsaMW7+VtRMAqf/3m4cPFhPD3EFRgNs18u+k3lTJJlVe7d0YPO+bnwqo2xg8YiRpDXJI2u8A0wqJxMsQuQ==
+postcss-normalize-display-values@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-display-values/-/postcss-normalize-display-values-5.0.1.tgz#62650b965981a955dffee83363453db82f6ad1fd"
+ integrity sha512-uupdvWk88kLDXi5HEyI9IaAJTE3/Djbcrqq8YgjvAVuzgVuqIk3SuJWUisT2gaJbZm1H9g5k2w1xXilM3x8DjQ==
dependencies:
- cssnano-util-get-match "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-positions@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-4.0.2.tgz#05f757f84f260437378368a91f8932d4b102917f"
- integrity sha512-Dlf3/9AxpxE+NF1fJxYDeggi5WwV35MXGFnnoccP/9qDtFrTArZ0D0R+iKcg5WsUd8nUYMIl8yXDCtcrT8JrdA==
+postcss-normalize-positions@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-positions/-/postcss-normalize-positions-5.0.1.tgz#868f6af1795fdfa86fbbe960dceb47e5f9492fe5"
+ integrity sha512-rvzWAJai5xej9yWqlCb1OWLd9JjW2Ex2BCPzUJrbaXmtKtgfL8dBMOOMTX6TnvQMtjk3ei1Lswcs78qKO1Skrg==
dependencies:
- cssnano-util-get-arguments "^4.0.0"
- has "^1.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-repeat-style@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-4.0.2.tgz#c4ebbc289f3991a028d44751cbdd11918b17910c"
- integrity sha512-qvigdYYMpSuoFs3Is/f5nHdRLJN/ITA7huIoCyqqENJe9PvPmLhNLMu7QTjPdtnVf6OcYYO5SHonx4+fbJE1+Q==
+postcss-normalize-repeat-style@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-repeat-style/-/postcss-normalize-repeat-style-5.0.1.tgz#cbc0de1383b57f5bb61ddd6a84653b5e8665b2b5"
+ integrity sha512-syZ2itq0HTQjj4QtXZOeefomckiV5TaUO6ReIEabCh3wgDs4Mr01pkif0MeVwKyU/LHEkPJnpwFKRxqWA/7O3w==
dependencies:
- cssnano-util-get-arguments "^4.0.0"
- cssnano-util-get-match "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-string@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-4.0.2.tgz#cd44c40ab07a0c7a36dc5e99aace1eca4ec2690c"
- integrity sha512-RrERod97Dnwqq49WNz8qo66ps0swYZDSb6rM57kN2J+aoyEAJfZ6bMx0sx/F9TIEX0xthPGCmeyiam/jXif0eA==
+postcss-normalize-string@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-string/-/postcss-normalize-string-5.0.1.tgz#d9eafaa4df78c7a3b973ae346ef0e47c554985b0"
+ integrity sha512-Ic8GaQ3jPMVl1OEn2U//2pm93AXUcF3wz+OriskdZ1AOuYV25OdgS7w9Xu2LO5cGyhHCgn8dMXh9bO7vi3i9pA==
dependencies:
- has "^1.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-timing-functions@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-4.0.2.tgz#8e009ca2a3949cdaf8ad23e6b6ab99cb5e7d28d9"
- integrity sha512-acwJY95edP762e++00Ehq9L4sZCEcOPyaHwoaFOhIwWCDfik6YvqsYNxckee65JHLKzuNSSmAdxwD2Cud1Z54A==
+postcss-normalize-timing-functions@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-timing-functions/-/postcss-normalize-timing-functions-5.0.1.tgz#8ee41103b9130429c6cbba736932b75c5e2cb08c"
+ integrity sha512-cPcBdVN5OsWCNEo5hiXfLUnXfTGtSFiBU9SK8k7ii8UD7OLuznzgNRYkLZow11BkQiiqMcgPyh4ZqXEEUrtQ1Q==
dependencies:
- cssnano-util-get-match "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-unicode@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-4.0.1.tgz#841bd48fdcf3019ad4baa7493a3d363b52ae1cfb"
- integrity sha512-od18Uq2wCYn+vZ/qCOeutvHjB5jm57ToxRaMeNuf0nWVHaP9Hua56QyMF6fs/4FSUnVIw0CBPsU0K4LnBPwYwg==
+postcss-normalize-unicode@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-unicode/-/postcss-normalize-unicode-5.0.1.tgz#82d672d648a411814aa5bf3ae565379ccd9f5e37"
+ integrity sha512-kAtYD6V3pK0beqrU90gpCQB7g6AOfP/2KIPCVBKJM2EheVsBQmx/Iof+9zR9NFKLAx4Pr9mDhogB27pmn354nA==
dependencies:
- browserslist "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ browserslist "^4.16.0"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-url@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-4.0.1.tgz#10e437f86bc7c7e58f7b9652ed878daaa95faae1"
- integrity sha512-p5oVaF4+IHwu7VpMan/SSpmpYxcJMtkGppYf0VbdH5B6hN8YNmVyJLuY9FmLQTzY3fag5ESUUHDqM+heid0UVA==
+postcss-normalize-url@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/postcss-normalize-url/-/postcss-normalize-url-5.0.3.tgz#42eca6ede57fe69075fab0f88ac8e48916ef931c"
+ integrity sha512-qWiUMbvkRx3kc1Dp5opzUwc7MBWZcSDK2yofCmdvFBCpx+zFPkxBC1FASQ59Pt+flYfj/nTZSkmF56+XG5elSg==
dependencies:
- is-absolute-url "^2.0.0"
- normalize-url "^3.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ is-absolute-url "^3.0.3"
+ normalize-url "^6.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-normalize-whitespace@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-4.0.2.tgz#bf1d4070fe4fcea87d1348e825d8cc0c5faa7d82"
- integrity sha512-tO8QIgrsI3p95r8fyqKV+ufKlSHh9hMJqACqbv2XknufqEDhDvbguXGBBqxw9nsQoXWf0qOqppziKJKHMD4GtA==
+postcss-normalize-whitespace@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-normalize-whitespace/-/postcss-normalize-whitespace-5.0.1.tgz#b0b40b5bcac83585ff07ead2daf2dcfbeeef8e9a"
+ integrity sha512-iPklmI5SBnRvwceb/XH568yyzK0qRVuAG+a1HFUsFRf11lEJTiQQa03a4RSCQvLKdcpX7XsI1Gen9LuLoqwiqA==
dependencies:
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ postcss-value-parser "^4.1.0"
-postcss-ordered-values@^4.1.2:
- version "4.1.2"
- resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-4.1.2.tgz#0cf75c820ec7d5c4d280189559e0b571ebac0eee"
- integrity sha512-2fCObh5UanxvSxeXrtLtlwVThBvHn6MQcu4ksNT2tsaV2Fg76R2CV98W7wNSlX+5/pFwEyaDwKLLoEV7uRybAw==
+postcss-ordered-values@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/postcss-ordered-values/-/postcss-ordered-values-5.0.2.tgz#1f351426977be00e0f765b3164ad753dac8ed044"
+ integrity sha512-8AFYDSOYWebJYLyJi3fyjl6CqMEG/UVworjiyK1r573I56kb3e879sCJLGvR3merj+fAdPpVplXKQZv+ey6CgQ==
dependencies:
- cssnano-util-get-arguments "^4.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
-postcss-reduce-initial@^4.0.3:
- version "4.0.3"
- resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-4.0.3.tgz#7fd42ebea5e9c814609639e2c2e84ae270ba48df"
- integrity sha512-gKWmR5aUulSjbzOfD9AlJiHCGH6AEVLaM0AV+aSioxUDd16qXP1PCh8d1/BGVvpdWn8k/HiK7n6TjeoXN1F7DA==
+postcss-reduce-initial@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-reduce-initial/-/postcss-reduce-initial-5.0.1.tgz#9d6369865b0f6f6f6b165a0ef5dc1a4856c7e946"
+ integrity sha512-zlCZPKLLTMAqA3ZWH57HlbCjkD55LX9dsRyxlls+wfuRfqCi5mSlZVan0heX5cHr154Dq9AfbH70LyhrSAezJw==
dependencies:
- browserslist "^4.0.0"
+ browserslist "^4.16.0"
caniuse-api "^3.0.0"
- has "^1.0.0"
- postcss "^7.0.0"
-postcss-reduce-transforms@^4.0.2:
- version "4.0.2"
- resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-4.0.2.tgz#17efa405eacc6e07be3414a5ca2d1074681d4e29"
- integrity sha512-EEVig1Q2QJ4ELpJXMZR8Vt5DQx8/mo+dGWSR7vWXqcob2gQLyQGsionYcGKATXvQzMPn6DSN1vTN7yFximdIAg==
+postcss-reduce-transforms@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/postcss-reduce-transforms/-/postcss-reduce-transforms-5.0.1.tgz#93c12f6a159474aa711d5269923e2383cedcf640"
+ integrity sha512-a//FjoPeFkRuAguPscTVmRQUODP+f3ke2HqFNgGPwdYnpeC29RZdCBvGRGTsKpMURb/I3p6jdKoBQ2zI+9Q7kA==
dependencies:
- cssnano-util-get-match "^4.0.0"
- has "^1.0.0"
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
-
-postcss-selector-parser@^3.0.0:
- version "3.1.2"
- resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-3.1.2.tgz#b310f5c4c0fdaf76f94902bbaa30db6aa84f5270"
- integrity sha512-h7fJ/5uWuRVyOtkO45pnt1Ih40CEleeyCHzipqAZO2e5H20g25Y48uYnFUiShvY4rZWNJ/Bib/KVPmanaCtOhA==
- dependencies:
- dot-prop "^5.2.0"
- indexes-of "^1.0.1"
- uniq "^1.0.1"
+ cssnano-utils "^2.0.1"
+ postcss-value-parser "^4.1.0"
postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector-parser@^6.0.4:
version "6.0.4"
@@ -23168,35 +23069,36 @@ postcss-selector-parser@^6.0.0, postcss-selector-parser@^6.0.2, postcss-selector
uniq "^1.0.1"
util-deprecate "^1.0.2"
-postcss-svgo@^4.0.2:
- version "4.0.3"
- resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-4.0.3.tgz#343a2cdbac9505d416243d496f724f38894c941e"
- integrity sha512-NoRbrcMWTtUghzuKSoIm6XV+sJdvZ7GZSc3wdBN0W19FTtp2ko8NqLsgoh/m9CzNhU3KLPvQmjIwtaNFkaFTvw==
+postcss-selector-parser@^6.0.5:
+ version "6.0.6"
+ resolved "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz#2c5bba8174ac2f6981ab631a42ab0ee54af332ea"
+ integrity sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==
dependencies:
- postcss "^7.0.0"
- postcss-value-parser "^3.0.0"
- svgo "^1.0.0"
+ cssesc "^3.0.0"
+ util-deprecate "^1.0.2"
-postcss-unique-selectors@^4.0.1:
- version "4.0.1"
- resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-4.0.1.tgz#9446911f3289bfd64c6d680f073c03b1f9ee4bac"
- integrity sha512-+JanVaryLo9QwZjKrmJgkI4Fn8SBgRO6WXQBJi7KiAVPlmxikB5Jzc4EvXMT2H0/m0RjrVVm9rGNhZddm/8Spg==
+postcss-svgo@^5.0.3:
+ version "5.0.3"
+ resolved "https://registry.npmjs.org/postcss-svgo/-/postcss-svgo-5.0.3.tgz#d945185756e5dfaae07f9edb0d3cae7ff79f9b30"
+ integrity sha512-41XZUA1wNDAZrQ3XgWREL/M2zSw8LJPvb5ZWivljBsUQAGoEKMYm6okHsTjJxKYI4M75RQEH4KYlEM52VwdXVA==
dependencies:
- alphanum-sort "^1.0.0"
- postcss "^7.0.0"
- uniqs "^2.0.0"
+ postcss-value-parser "^4.1.0"
+ svgo "^2.7.0"
-postcss-value-parser@^3.0.0:
- version "3.3.1"
- resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-3.3.1.tgz#9ff822547e2893213cf1c30efa51ac5fd1ba8281"
- integrity sha512-pISE66AbVkp4fDQ7VHBwRNXzAAKJjw4Vw7nWI/+Q3vuly7SNfgYXvm6i5IgFylHGK5sP/xHAbB7N49OS4gWNyQ==
+postcss-unique-selectors@^5.0.2:
+ version "5.0.2"
+ resolved "https://registry.npmjs.org/postcss-unique-selectors/-/postcss-unique-selectors-5.0.2.tgz#5d6893daf534ae52626708e0d62250890108c0c1"
+ integrity sha512-w3zBVlrtZm7loQWRPVC0yjUwwpty7OM6DnEHkxcSQXO1bMS3RJ+JUS5LFMSDZHJcvGsRwhZinCWVqn8Kej4EDA==
+ dependencies:
+ alphanum-sort "^1.0.2"
+ postcss-selector-parser "^6.0.5"
postcss-value-parser@^4.0.0, postcss-value-parser@^4.0.2, postcss-value-parser@^4.1.0:
version "4.1.0"
resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz#443f6a20ced6481a2bda4fa8532a6e55d789a2cb"
integrity sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==
-"postcss@5 - 7", postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.26, postcss@^7.0.27, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
+"postcss@5 - 7", postcss@^7.0.14, postcss@^7.0.16, postcss@^7.0.26, postcss@^7.0.32, postcss@^7.0.5, postcss@^7.0.6:
version "7.0.32"
resolved "https://registry.npmjs.org/postcss/-/postcss-7.0.32.tgz#4310d6ee347053da3433db2be492883d62cec59d"
integrity sha512-03eXong5NLnNCD05xscnGKGDZ98CyzoqPSMjOe6SuoQY7Z2hIj0Ld1g/O/UQRuOle2aRtiIRDg9tDcTGAkLfKw==
@@ -23347,6 +23249,16 @@ pretty-format@^26.0.0, pretty-format@^26.6.2:
ansi-styles "^4.0.0"
react-is "^17.0.1"
+pretty-format@^27.0.2:
+ version "27.3.1"
+ resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-27.3.1.tgz#7e9486365ccdd4a502061fa761d3ab9ca1b78df5"
+ integrity sha512-DR/c+pvFc52nLimLROYjnXPtolawm+uWDxr4FjuLDLUn+ktWnSN851KoHwHzzqq6rfCOjkzN8FLgDrSub6UDuA==
+ dependencies:
+ "@jest/types" "^27.2.5"
+ ansi-regex "^5.0.1"
+ ansi-styles "^5.0.0"
+ react-is "^17.0.1"
+
pretty-hrtime@^1.0.3:
version "1.0.3"
resolved "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz#b7e3ea42435a4c9b2759d99e0f201eb195802ee1"
@@ -25072,11 +24984,6 @@ resolve-from@5.0.0, resolve-from@^5.0.0:
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69"
integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==
-resolve-from@^3.0.0:
- version "3.0.0"
- resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz#b22c7af7d9d6881bc8b6e653335eebcb0a188748"
- integrity sha1-six699nWiBvItuZTM17rywoYh0g=
-
resolve-from@^4.0.0:
version "4.0.0"
resolved "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz#4abcd852ad32dd7baabfe9b40e00a36db5f392e6"
@@ -25188,16 +25095,6 @@ rfc4648@^1.3.0:
resolved "https://registry.npmjs.org/rfc4648/-/rfc4648-1.4.0.tgz#c75b2856ad2e2d588b6ddb985d556f1f7f2a2abd"
integrity sha512-3qIzGhHlMHA6PoT6+cdPKZ+ZqtxkIvg8DZGKA5z6PQ33/uuhoJ+Ws/D/J9rXW6gXodgH8QYlz2UCl+sdUDmNIg==
-rgb-regex@^1.0.1:
- version "1.0.1"
- resolved "https://registry.npmjs.org/rgb-regex/-/rgb-regex-1.0.1.tgz#c0e0d6882df0e23be254a475e8edd41915feaeb1"
- integrity sha1-wODWiC3w4jviVKR16O3UGRX+rrE=
-
-rgba-regex@^1.0.0:
- version "1.0.0"
- resolved "https://registry.npmjs.org/rgba-regex/-/rgba-regex-1.0.0.tgz#43374e2e2ca0968b0ef1523460b7d730ff22eeb3"
- integrity sha1-QzdOLiyglosO8VI0YLfXMP8i7rM=
-
rifm@^0.7.0:
version "0.7.0"
resolved "https://registry.npmjs.org/rifm/-/rifm-0.7.0.tgz#debe951a9c83549ca6b33e5919f716044c2230be"
@@ -25265,13 +25162,13 @@ rollup-plugin-peer-deps-external@^2.2.2:
integrity sha512-AWdukIM1+k5JDdAqV/Cxd+nejvno2FVLVeZ74NKggm3Q5s9cbbcOgUPGdbxPi4BXu7xGaZ8HG12F+thImYu/0g==
rollup-plugin-postcss@^4.0.0:
- version "4.0.0"
- resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.0.tgz#2131fb6db0d5dce01a37235e4f6ad4523c681cea"
- integrity sha512-OQzT+YspV01/6dxfyEw8lBO2px3hyL8Xn+k2QGctL7V/Yx2Z1QaMKdYVslP1mqv7RsKt6DROIlnbpmgJ3yxf6g==
+ version "4.0.2"
+ resolved "https://registry.npmjs.org/rollup-plugin-postcss/-/rollup-plugin-postcss-4.0.2.tgz#15e9462f39475059b368ce0e49c800fa4b1f7050"
+ integrity sha512-05EaY6zvZdmvPUDi3uCcAQoESDcYnv8ogJJQRp6V5kZ6J6P7uAVJlrTZcaaA20wTH527YTnKfkAoPxWI/jPp4w==
dependencies:
chalk "^4.1.0"
concat-with-sourcemaps "^1.1.0"
- cssnano "^4.1.10"
+ cssnano "^5.0.1"
import-cwd "^3.0.0"
p-queue "^6.6.2"
pify "^5.0.0"
@@ -26757,14 +26654,13 @@ style-to-object@0.3.0, style-to-object@^0.3.0:
dependencies:
inline-style-parser "0.1.1"
-stylehacks@^4.0.0:
- version "4.0.3"
- resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-4.0.3.tgz#6718fcaf4d1e07d8a1318690881e8d96726a71d5"
- integrity sha512-7GlLk9JwlElY4Y6a/rmbH2MhVlTyVmiJd1PfTCqFaIBEGMYNsrO/v3SeGTdhBThLg4Z+NbOk/qFMwCa+J+3p/g==
+stylehacks@^5.0.1:
+ version "5.0.1"
+ resolved "https://registry.npmjs.org/stylehacks/-/stylehacks-5.0.1.tgz#323ec554198520986806388c7fdaebc38d2c06fb"
+ integrity sha512-Es0rVnHIqbWzveU1b24kbw92HsebBepxfcqe5iix7t9j0PQqhs0IxXVXv0pY2Bxa08CgMkzD6OWql7kbGOuEdA==
dependencies:
- browserslist "^4.0.0"
- postcss "^7.0.0"
- postcss-selector-parser "^3.0.0"
+ browserslist "^4.16.0"
+ postcss-selector-parser "^6.0.4"
stylis@^4.0.6:
version "4.0.7"
@@ -26896,7 +26792,7 @@ svg-parser@^2.0.2:
resolved "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz#fdc2e29e13951736140b76cb122c8ee6630eb6b5"
integrity sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==
-svgo@^1.0.0, svgo@^1.2.2:
+svgo@^1.2.2:
version "1.3.2"
resolved "https://registry.npmjs.org/svgo/-/svgo-1.3.2.tgz#b6dc511c063346c9e415b81e43401145b96d4167"
integrity sha512-yhy/sQYxR5BkC98CY7o31VGsg014AKLEPxdfhora76l36hD9Rdy5NZA/Ocn6yayNPgSamYdtX2rFJdcv07AYVw==
@@ -26915,6 +26811,19 @@ svgo@^1.0.0, svgo@^1.2.2:
unquote "~1.1.1"
util.promisify "~1.0.0"
+svgo@^2.7.0:
+ version "2.8.0"
+ resolved "https://registry.npmjs.org/svgo/-/svgo-2.8.0.tgz#4ff80cce6710dc2795f0c7c74101e6764cfccd24"
+ integrity sha512-+N/Q9kV1+F+UeWYoSiULYo4xYSDQlTgb+ayMobAXPwMnLvop7oxKMo9OzIrX5x3eS4L4f2UHhc9axXwY8DpChg==
+ dependencies:
+ "@trysound/sax" "0.2.0"
+ commander "^7.2.0"
+ css-select "^4.1.3"
+ css-tree "^1.1.3"
+ csso "^4.2.0"
+ picocolors "^1.0.0"
+ stable "^0.1.8"
+
swagger-client@3.16.1, swagger-client@^3.16.1:
version "3.16.1"
resolved "https://registry.npmjs.org/swagger-client/-/swagger-client-3.16.1.tgz#df86c9d407ab52c00cb356e714b0ec732bb3ad40"
@@ -28072,11 +27981,6 @@ uniq@^1.0.1:
resolved "https://registry.npmjs.org/uniq/-/uniq-1.0.1.tgz#b31c5ae8254844a3a8281541ce2b04b865a734ff"
integrity sha1-sxxa6CVIRKOoKBVBzisEuGWnNP8=
-uniqs@^2.0.0:
- version "2.0.0"
- resolved "https://registry.npmjs.org/uniqs/-/uniqs-2.0.0.tgz#ffede4b36b25290696e6e165d4a59edb998e6b02"
- integrity sha1-/+3ks2slKQaW5uFl1KWe25mOawI=
-
unique-filename@^1.1.1:
version "1.1.1"
resolved "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz#1d69769369ada0583103a1e6ae87681b56573230"
@@ -28616,11 +28520,6 @@ vasync@^2.2.0:
dependencies:
verror "1.10.0"
-vendors@^1.0.0:
- version "1.0.4"
- resolved "https://registry.npmjs.org/vendors/-/vendors-1.0.4.tgz#e2b800a53e7a29b93506c3cf41100d16c4c4ad8e"
- integrity sha512-/juG65kTL4Cy2su4P8HjtkTxk6VmJDiOPBufWniqQ6wknac6jNiXS9vU+hO3wgusiyqWlzTbVHi0dyJqRONg3w==
-
verror@1.10.0, verror@^1.8.1:
version "1.10.0"
resolved "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz#3a105ca17053af55d6e270c1f8288682e18da400"
@@ -29269,9 +29168,9 @@ write-pkg@^4.0.0:
write-json-file "^3.2.0"
ws@7.4.5, ws@^7.4.6:
- version "7.5.5"
- resolved "https://registry.npmjs.org/ws/-/ws-7.5.5.tgz#8b4bc4af518cfabd0473ae4f99144287b33eb881"
- integrity sha512-BAkMFcAzl8as1G/hArkxOxq3G7pjUqQ3gzYbLL0/5zNkph70e+lCoxBGnm6AW1+/aiNeV4fnKqZ8m4GZewmH2w==
+ version "7.5.6"
+ resolved "https://registry.npmjs.org/ws/-/ws-7.5.6.tgz#e59fc509fb15ddfb65487ee9765c5a51dec5fe7b"
+ integrity sha512-6GLgCqo2cy2A2rjCNFlxQS6ZljG/coZfZXclldI8FB/1G3CCI36Zd8xy2HrFVACi8tfk5XrgLQEk+P0Tnz9UcA==
ws@7.4.6:
version "7.4.6"