From 22a60518ca90cf1065076d4d061352ff83ced8ed Mon Sep 17 00:00:00 2001 From: Phil Kuang Date: Mon, 7 Jun 2021 19:01:17 -0400 Subject: [PATCH 01/89] feat(GithubMultiOrgReaderProcessor): implement processor to handle multi-org ingestion Signed-off-by: Phil Kuang --- .changeset/orange-news-sparkle.md | 46 +++++ packages/integration/api-report.md | 10 + .../github/GithubCredentialsProvider.test.ts | 27 +-- .../src/github/GithubCredentialsProvider.ts | 38 ++-- packages/integration/src/github/index.ts | 5 +- plugins/catalog-backend/config.d.ts | 21 ++ .../GithubMultiOrgReaderProcessor.ts | 181 ++++++++++++++++++ .../processors/github/config.test.ts | 33 +++- .../src/ingestion/processors/github/config.ts | 24 +++ .../processors/github/github.test.ts | 40 +++- .../src/ingestion/processors/github/github.ts | 8 +- .../src/ingestion/processors/github/index.ts | 4 +- .../src/ingestion/processors/index.ts | 1 + 13 files changed, 397 insertions(+), 41 deletions(-) create mode 100644 .changeset/orange-news-sparkle.md create mode 100644 plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts diff --git a/.changeset/orange-news-sparkle.md b/.changeset/orange-news-sparkle.md new file mode 100644 index 0000000000..d3a0292d52 --- /dev/null +++ b/.changeset/orange-news-sparkle.md @@ -0,0 +1,46 @@ +--- +'@backstage/integration': patch +'@backstage/plugin-catalog-backend': patch +--- + +Support ingesting multiple GitHub organizations via a new `GithubMultiOrgReaderProcessor`. + +This new processor handles namespacing created groups according to the org of the associated GitHub team to prevent potential name clashes between organizations. Be aware that this processor is considered alpha and may not be compatible with future org structures in the catalog. + +NOTE: This processor only fully supports auth via GitHub Apps + +To install this processor, import and add it as follows: + +```typescript +// Typically in packages/backend/src/plugins/catalog.ts +import { GithubMultiOrgReaderProcessor } from '@backstage/plugin-catalog-backend'; +// ... +export default async function createPlugin(env: PluginEnvironment) { + const builder = new CatalogBuilder(env); + builder.addProcessor( + GithubMultiOrgReaderProcessor.fromConfig(env.config, { + logger: env.logger, + }), + ); + // ... +} +``` + +Configure in your `app-config.yaml` by pointing to your GitHub instance and optionally list which GitHub organizations you wish to import. You can also configure what namespace you want to set for teams from each org. If unspecified, the org name will be used as the namespace. If no organizations are listed, by default this processor will import from all organizations accessible by all configured GitHub Apps: + +```yaml +catalog: + locations: + - type: github-multi-org + target: https://github.myorg.com + + processors: + githubMultiOrg: + orgs: + - name: fooOrg + groupNamespace: foo + - name: barOrg + groupNamespace: bar + - name: awesomeOrg + - name: anotherOrg +``` diff --git a/packages/integration/api-report.md b/packages/integration/api-report.md index 31cbc8b5d8..afd048cb13 100644 --- a/packages/integration/api-report.md +++ b/packages/integration/api-report.md @@ -5,6 +5,7 @@ ```ts import { Config } from '@backstage/config'; +import { RestEndpointMethodTypes } from '@octokit/rest'; // @public (undocumented) export class AzureIntegration implements ScmIntegration { @@ -106,6 +107,15 @@ export function getGitLabFileFetchUrl(url: string, config: GitLabIntegrationConf // @public export function getGitLabRequestOptions(config: GitLabIntegrationConfig): RequestInit; +// @public (undocumented) +export class GithubAppCredentialsMux { + constructor(config: GitHubIntegrationConfig); + // (undocumented) + getAllInstallations(): Promise; + // (undocumented) + getAppToken(owner: string, repo?: string): Promise; +} + // @public (undocumented) export class GithubCredentialsProvider { // (undocumented) diff --git a/packages/integration/src/github/GithubCredentialsProvider.test.ts b/packages/integration/src/github/GithubCredentialsProvider.test.ts index 271d4b9920..6b70109a26 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.test.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.test.ts @@ -15,6 +15,7 @@ */ const octokit = { + paginate: async (fn: any) => (await fn()).data, apps: { listInstallations: jest.fn(), createInstallationAccessToken: jest.fn(), @@ -53,7 +54,7 @@ describe('GithubCredentialsProvider tests', () => { jest.resetAllMocks(); }); it('create repository specific tokens', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -72,7 +73,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -84,12 +84,8 @@ describe('GithubCredentialsProvider tests', () => { const { token, headers, type } = await github.getCredentials({ url: 'https://github.com/backstage/foobar', }); - const { token: accessToken2 } = await github.getCredentials({ - url: 'https://github.com/backstage/foobar', - }); expect(type).toEqual('app'); expect(token).toEqual('secret_token'); - expect(token).toEqual(accessToken2); expect(headers).toEqual({ Authorization: 'Bearer secret_token' }); // fallback to the configured token if no application is matching @@ -107,7 +103,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('creates tokens for an organization', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -121,7 +117,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -133,17 +128,13 @@ describe('GithubCredentialsProvider tests', () => { const { token, headers } = await github.getCredentials({ url: 'https://github.com/backstage', }); - const { token: accessToken2 } = await github.getCredentials({ - url: 'https://github.com/backstage', - }); expect(headers).toEqual({ Authorization: 'Bearer secret_token' }); expect(token).toEqual('secret_token'); - expect(token).toEqual(accessToken2); }); it('should fail to issue tokens for an organization when the app is installed for a single repo', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -157,7 +148,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); octokit.apps.createInstallationAccessToken.mockResolvedValueOnce({ data: { @@ -176,7 +166,7 @@ describe('GithubCredentialsProvider tests', () => { }); it('should throw if the app is suspended', async () => { - octokit.apps.listInstallations.mockResolvedValueOnce({ + octokit.apps.listInstallations.mockResolvedValue({ headers: { etag: '123', }, @@ -193,7 +183,6 @@ describe('GithubCredentialsProvider tests', () => { }, ], } as RestEndpointMethodTypes['apps']['listInstallations']['response']); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); await expect( github.getCredentials({ @@ -229,7 +218,7 @@ describe('GithubCredentialsProvider tests', () => { ).resolves.toEqual(expect.objectContaining({ token: 'fallback_token' })); }); - it('should return the configured token if listing installations throws', async () => { + it('should return the configured token if there are no installations', async () => { const githubProvider = GithubCredentialsProvider.create({ host: 'github.com', apps: [ @@ -243,7 +232,9 @@ describe('GithubCredentialsProvider tests', () => { ], token: 'hardcoded_token', }); - octokit.apps.listInstallations.mockRejectedValue({ status: 304 }); + octokit.apps.listInstallations.mockResolvedValue(({ + data: [], + } as unknown) as RestEndpointMethodTypes['apps']['listInstallations']['response']); await expect( githubProvider.getCredentials({ diff --git a/packages/integration/src/github/GithubCredentialsProvider.ts b/packages/integration/src/github/GithubCredentialsProvider.ts index 9bc09deab3..e0517bdff4 100644 --- a/packages/integration/src/github/GithubCredentialsProvider.ts +++ b/packages/integration/src/github/GithubCredentialsProvider.ts @@ -66,7 +66,6 @@ const HEADERS = { class GithubAppManager { private readonly appClient: Octokit; private readonly baseAuthConfig: { appId: number; privateKey: string }; - private installations?: RestEndpointMethodTypes['apps']['listInstallations']['response']; private readonly cache = new Cache(); constructor(config: GithubAppConfig, baseUrl?: string) { @@ -121,22 +120,15 @@ class GithubAppManager { }); } + getInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + return this.appClient.paginate(this.appClient.apps.listInstallations); + } + private async getInstallationData(owner: string): Promise { - // List all installations using the last used etag. - // Return cached InstallationData if error with status 304 is thrown. - try { - this.installations = await this.appClient.apps.listInstallations({ - headers: { - 'If-None-Match': this.installations?.headers.etag, - Accept: HEADERS.Accept, - }, - }); - } catch (error) { - if (error.status !== 304) { - throw error; - } - } - const installation = this.installations?.data.find( + const allInstallations = await this.getInstallations(); + const installation = allInstallations.find( inst => inst.account?.login === owner, ); if (installation) { @@ -163,6 +155,20 @@ export class GithubAppCredentialsMux { config.apps?.map(ac => new GithubAppManager(ac, config.apiBaseUrl)) ?? []; } + async getAllInstallations(): Promise< + RestEndpointMethodTypes['apps']['listInstallations']['response']['data'] + > { + if (!this.apps.length) { + return []; + } + + const installs = await Promise.all( + this.apps.map(app => app.getInstallations()), + ); + + return installs.flat(); + } + async getAppToken(owner: string, repo?: string): Promise { if (this.apps.length === 0) { return undefined; diff --git a/packages/integration/src/github/index.ts b/packages/integration/src/github/index.ts index ee92b7d934..b987a06bb1 100644 --- a/packages/integration/src/github/index.ts +++ b/packages/integration/src/github/index.ts @@ -20,6 +20,9 @@ export { } from './config'; export type { GitHubIntegrationConfig } from './config'; export { getGitHubFileFetchUrl, getGitHubRequestOptions } from './core'; -export { GithubCredentialsProvider } from './GithubCredentialsProvider'; +export { + GithubAppCredentialsMux, + GithubCredentialsProvider, +} from './GithubCredentialsProvider'; export type { GithubCredentialType } from './GithubCredentialsProvider'; export { GitHubIntegration } from './GitHubIntegration'; diff --git a/plugins/catalog-backend/config.d.ts b/plugins/catalog-backend/config.d.ts index 20fcd975cf..a4cfdf95aa 100644 --- a/plugins/catalog-backend/config.d.ts +++ b/plugins/catalog-backend/config.d.ts @@ -145,6 +145,27 @@ export interface Config { }>; }; + /** + * GithubMultiOrgReaderProcessor configuration + */ + githubMultiOrg?: { + /** + * The configuration parameters for each GitHub org to process. + */ + orgs: Array<{ + /** + * The name of the GitHub org to process. + */ + name: string; + /** + * The namespace of the group created for this org. + * + * Defaults to org name if omitted. + */ + groupNamespace?: string; + }>; + }; + /** * LdapOrgReaderProcessor configuration */ diff --git a/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts new file mode 100644 index 0000000000..5223acba4a --- /dev/null +++ b/plugins/catalog-backend/src/ingestion/processors/GithubMultiOrgReaderProcessor.ts @@ -0,0 +1,181 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 { + GithubAppCredentialsMux, + GithubCredentialsProvider, + GitHubIntegrationConfig, + ScmIntegrations, +} from '@backstage/integration'; +import { graphql } from '@octokit/graphql'; +import { Logger } from 'winston'; +import { + getOrganizationTeams, + getOrganizationUsers, + GithubMultiOrgConfig, + readGithubMultiOrgConfig, +} from './github'; +import * as results from './results'; +import { CatalogProcessor, CatalogProcessorEmit } from './types'; +import { buildOrgHierarchy } from './util/org'; + +/** + * @alpha + * Extracts teams and users out of a multiple GitHub orgs namespaced per org. + * + * Be aware that this processor may not be compatible with future org structures in the catalog. + */ +export class GithubMultiOrgReaderProcessor implements CatalogProcessor { + private readonly integrations: ScmIntegrations; + private readonly orgs: GithubMultiOrgConfig; + private readonly logger: Logger; + + static fromConfig(config: Config, options: { logger: Logger }) { + const c = config.getOptionalConfig('catalog.processors.githubMultiOrg'); + const integrations = ScmIntegrations.fromConfig(config); + + return new GithubMultiOrgReaderProcessor({ + ...options, + integrations, + orgs: c ? readGithubMultiOrgConfig(c) : [], + }); + } + + constructor(options: { + integrations: ScmIntegrations; + logger: Logger; + orgs: GithubMultiOrgConfig; + }) { + this.integrations = options.integrations; + this.logger = options.logger; + this.orgs = options.orgs; + } + + async readLocation( + location: LocationSpec, + _optional: boolean, + emit: CatalogProcessorEmit, + ): Promise { + if (location.type !== 'github-multi-org') { + return false; + } + + const gitHubConfig = this.integrations.github.byUrl(location.target) + ?.config; + if (!gitHubConfig) { + throw new Error( + `There is no GitHub integration that matches ${location.target}. Please add a configuration entry for it under integrations.github`, + ); + } + + const allUsersMap = new Map(); + const baseUrl = new URL(location.target).origin; + const credentialsProvider = GithubCredentialsProvider.create(gitHubConfig); + + const orgsToProcess = this.orgs.length + ? this.orgs + : await this.getAllOrgs(gitHubConfig); + + for (const orgConfig of orgsToProcess) { + try { + const { + headers, + type: tokenType, + } = await credentialsProvider.getCredentials({ + url: `${baseUrl}/${orgConfig.name}`, + }); + const client = graphql.defaults({ + baseUrl: gitHubConfig.apiBaseUrl, + headers, + }); + + const startTimestamp = Date.now(); + this.logger.info( + `Reading GitHub users and teams for org: ${orgConfig.name}`, + ); + const { users } = await getOrganizationUsers( + client, + orgConfig.name, + tokenType, + ); + const { groups, groupMemberUsers } = await getOrganizationTeams( + client, + orgConfig.name, + orgConfig.groupNamespace, + ); + + const duration = ((Date.now() - startTimestamp) / 1000).toFixed(1); + this.logger.debug( + `Read ${users.length} GitHub users and ${groups.length} GitHub teams from ${orgConfig.name} in ${duration} seconds`, + ); + + users.forEach(u => { + if (!allUsersMap.has(u.metadata.name)) { + allUsersMap.set(u.metadata.name, u); + } + }); + + for (const [groupName, userNames] of groupMemberUsers.entries()) { + for (const userName of userNames) { + const user = allUsersMap.get(userName); + if (user && !user.spec.memberOf.includes(groupName)) { + user.spec.memberOf.push(groupName); + } + } + } + buildOrgHierarchy(groups); + + for (const group of groups) { + emit(results.entity(location, group)); + } + } catch (e) { + this.logger.error( + `Failed to read GitHub org data for ${orgConfig.name}: ${e}`, + ); + } + } + + const allUsers = Array.from(allUsersMap.values()); + for (const user of allUsers) { + emit(results.entity(location, user)); + } + + return true; + } + + // Note: Does not support usage of PATs + private async getAllOrgs( + gitHubConfig: GitHubIntegrationConfig, + ): Promise { + const githubAppMux = new GithubAppCredentialsMux(gitHubConfig); + const installs = await githubAppMux.getAllInstallations(); + + return installs + .map(install => + install.target_type === 'Organization' && + install.account && + install.account.login + ? { + name: install.account.login, + groupNamespace: install.account.login.toLowerCase(), + } + : undefined, + ) + .filter(Boolean) as GithubMultiOrgConfig; + } +} diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts index 14f3caa42c..560a916f09 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.test.ts @@ -15,7 +15,7 @@ */ import { ConfigReader } from '@backstage/config'; -import { readGithubConfig } from './config'; +import { readGithubConfig, readGithubMultiOrgConfig } from './config'; describe('config', () => { describe('readGithubConfig', () => { @@ -76,4 +76,35 @@ describe('config', () => { ).toThrow(/token/); }); }); + + describe('readGithubMultiOrgConfig', () => { + function config(orgs: { name: string; groupNamespace?: string }[]) { + return new ConfigReader({ orgs }); + } + + it('reads org configs', () => { + const output = readGithubMultiOrgConfig( + config([ + { name: 'foo', groupNamespace: 'apple' }, + { name: 'bar', groupNamespace: 'Orange' }, + ]), + ); + + expect(output).toEqual([ + { name: 'foo', groupNamespace: 'apple' }, + { name: 'bar', groupNamespace: 'orange' }, + ]); + }); + + it('defaults groupNamespace to org name if undefined', () => { + const output = readGithubMultiOrgConfig( + config([{ name: 'foo' }, { name: 'bar' }]), + ); + + expect(output).toEqual([ + { name: 'foo', groupNamespace: 'foo' }, + { name: 'bar', groupNamespace: 'bar' }, + ]); + }); + }); }); diff --git a/plugins/catalog-backend/src/ingestion/processors/github/config.ts b/plugins/catalog-backend/src/ingestion/processors/github/config.ts index 88f2f96218..6a177f7be0 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/config.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/config.ts @@ -82,3 +82,27 @@ export function readGithubConfig(config: Config): ProviderConfig[] { return providers; } + +/** + * The configuration parameters for a multi-org GitHub processor. + */ +export type GithubMultiOrgConfig = Array<{ + /** + * The name of the GitHub org to process. + */ + name: string; + /** + * The namespace of the group created for this org. + */ + groupNamespace: string; +}>; + +export function readGithubMultiOrgConfig(config: Config): GithubMultiOrgConfig { + const orgConfigs = config.getOptionalConfigArray('orgs') ?? []; + return orgConfigs.map(c => ({ + name: c.getString('name'), + groupNamespace: ( + c.getOptionalString('groupNamespace') ?? c.getString('name') + ).toLowerCase(), + })); +} diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts index c5094230f8..b9da8e735e 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.test.ts @@ -72,8 +72,10 @@ describe('github', () => { }); describe('getOrganizationTeams', () => { - it('reads teams', async () => { - const input: QueryResponse = { + let input: QueryResponse; + + beforeEach(() => { + input = { organization: { teams: { pageInfo: { hasNextPage: false }, @@ -98,7 +100,9 @@ describe('github', () => { }, }, }; + }); + it('reads teams', async () => { const output = { groups: [ expect.objectContaining({ @@ -126,6 +130,38 @@ describe('github', () => { await expect(getOrganizationTeams(graphql, 'a')).resolves.toEqual(output); }); + + it('applies namespaces', async () => { + const output = { + groups: [ + expect.objectContaining({ + metadata: expect.objectContaining({ + name: 'team', + namespace: 'foo', + description: 'The one and only team', + }), + spec: { + type: 'team', + profile: { + displayName: 'Team', + picture: 'http://example.com/team.jpeg', + }, + parent: 'parent', + children: [], + }, + }), + ], + groupMemberUsers: new Map([['foo/team', ['user']]]), + }; + + server.use( + graphqlMsw.query('teams', (_req, res, ctx) => res(ctx.data(input))), + ); + + await expect(getOrganizationTeams(graphql, 'a', 'foo')).resolves.toEqual( + output, + ); + }); }); describe('getTeamMembers', () => { diff --git a/plugins/catalog-backend/src/ingestion/processors/github/github.ts b/plugins/catalog-backend/src/ingestion/processors/github/github.ts index 2cc97d2ec5..093012544a 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/github.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/github.ts @@ -144,6 +144,7 @@ export async function getOrganizationUsers( export async function getOrganizationTeams( client: typeof graphql, org: string, + orgNamespace?: string, ): Promise<{ groups: GroupEntity[]; groupMemberUsers: Map; @@ -189,6 +190,10 @@ export async function getOrganizationTeams( }, }; + if (orgNamespace) { + entity.metadata.namespace = orgNamespace; + } + if (team.description) { entity.metadata.description = team.description; } @@ -203,7 +208,8 @@ export async function getOrganizationTeams( } const memberNames: string[] = []; - groupMemberUsers.set(team.slug, memberNames); + const groupKey = orgNamespace ? `${orgNamespace}/${team.slug}` : team.slug; + groupMemberUsers.set(groupKey, memberNames); if (!team.members.pageInfo.hasNextPage) { // We got all the members in one go, run the fast path diff --git a/plugins/catalog-backend/src/ingestion/processors/github/index.ts b/plugins/catalog-backend/src/ingestion/processors/github/index.ts index 2063e8c1b2..6feeea2e15 100644 --- a/plugins/catalog-backend/src/ingestion/processors/github/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/github/index.ts @@ -14,8 +14,8 @@ * limitations under the License. */ -export { readGithubConfig } from './config'; -export type { ProviderConfig } from './config'; +export { readGithubConfig, readGithubMultiOrgConfig } from './config'; +export type { GithubMultiOrgConfig, ProviderConfig } from './config'; export { getOrganizationTeams, getOrganizationUsers, diff --git a/plugins/catalog-backend/src/ingestion/processors/index.ts b/plugins/catalog-backend/src/ingestion/processors/index.ts index a92cc9ed3b..9cd851d471 100644 --- a/plugins/catalog-backend/src/ingestion/processors/index.ts +++ b/plugins/catalog-backend/src/ingestion/processors/index.ts @@ -25,6 +25,7 @@ export { CodeOwnersProcessor } from './CodeOwnersProcessor'; export { FileReaderProcessor } from './FileReaderProcessor'; export { GithubDiscoveryProcessor } from './GithubDiscoveryProcessor'; export { GithubOrgReaderProcessor } from './GithubOrgReaderProcessor'; +export { GithubMultiOrgReaderProcessor } from './GithubMultiOrgReaderProcessor'; export { LdapOrgReaderProcessor } from './LdapOrgReaderProcessor'; export { LocationEntityProcessor } from './LocationEntityProcessor'; export { MicrosoftGraphOrgReaderProcessor } from './MicrosoftGraphOrgReaderProcessor'; From a7f5fe7d7cb10d797e07ba689d45c6942ce2919c Mon Sep 17 00:00:00 2001 From: Rogerio Angeliski Date: Fri, 18 Jun 2021 08:35:21 -0300 Subject: [PATCH 02/89] feat:created an action to write a catalog-info.yml file Signed-off-by: Rogerio Angeliski --- .changeset/beige-papayas-relax.md | 5 ++ .../actions/builtin/catalog/index.ts | 1 + .../actions/builtin/catalog/write.test.ts | 71 +++++++++++++++++++ .../actions/builtin/catalog/write.ts | 49 +++++++++++++ .../actions/builtin/createBuiltinActions.ts | 6 +- .../src/scaffolder/tasks/TaskWorker.ts | 5 ++ 6 files changed, 136 insertions(+), 1 deletion(-) create mode 100644 .changeset/beige-papayas-relax.md create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts create mode 100644 plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts diff --git a/.changeset/beige-papayas-relax.md b/.changeset/beige-papayas-relax.md new file mode 100644 index 0000000000..9fb6fc1c12 --- /dev/null +++ b/.changeset/beige-papayas-relax.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-scaffolder-backend': patch +--- + +created an action to write a catalog-info file diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/index.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/index.ts index 2e25b5592f..7d5caaf082 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/index.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/index.ts @@ -15,3 +15,4 @@ */ export { createCatalogRegisterAction } from './register'; +export { createCatalogWriteAction } from './write'; diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts new file mode 100644 index 0000000000..0af6822ccf --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.test.ts @@ -0,0 +1,71 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 fs from 'fs-extra'; + +jest.mock('fs-extra'); + +const fsMock = fs as jest.Mocked; + +import { PassThrough } from 'stream'; +import os from 'os'; +import { getVoidLogger } from '@backstage/backend-common'; +import { ORIGIN_LOCATION_ANNOTATION } from '@backstage/catalog-model'; +import { createCatalogWriteAction } from './write'; +import { resolve as resolvePath } from 'path'; +import * as yaml from 'yaml'; + +describe('catalog:write', () => { + const action = createCatalogWriteAction(); + + const mockContext = { + workspacePath: os.tmpdir(), + logger: getVoidLogger(), + logStream: new PassThrough(), + output: jest.fn(), + createTemporaryDirectory: jest.fn(), + }; + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('should write the catalog-info.yml in the workspace', async () => { + const entity = { + apiVersion: 'backstage.io/v1alpha1', + kind: 'Component', + metadata: { + name: 'n', + namespace: 'ns', + annotations: { + [ORIGIN_LOCATION_ANNOTATION]: 'url:https://example.com', + }, + }, + spec: {}, + }; + + await action.handler({ + ...mockContext, + input: { + entity, + }, + }); + + expect(fsMock.writeFile).toHaveBeenCalledTimes(1); + expect(fsMock.writeFile).toHaveBeenCalledWith( + resolvePath(mockContext.workspacePath, 'catalog-info.yaml'), + yaml.stringify(entity), + ); + }); +}); diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts new file mode 100644 index 0000000000..d7f302031e --- /dev/null +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/catalog/write.ts @@ -0,0 +1,49 @@ +/* + * Copyright 2021 Spotify AB + * + * 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 fs from 'fs-extra'; +import { createTemplateAction } from '../../createTemplateAction'; +import * as yaml from 'yaml'; +import { Entity } from '@backstage/catalog-model'; + +export function createCatalogWriteAction() { + return createTemplateAction<{ name?: string; entity: Entity }>({ + id: 'catalog:write', + description: 'Writes the catalog-info.yaml for your template', + schema: { + input: { + type: 'object', + properties: { + entity: { + title: 'Entity info to write catalog-info.yaml', + description: + 'You can provide the same values used in the Entity schema.', + type: 'object', + }, + }, + }, + }, + async handler(ctx) { + ctx.logStream.write(`Writing catalog-info.yaml`); + const { entity } = ctx.input; + + await fs.writeFile( + `${ctx.workspacePath}/catalog-info.yaml`, + yaml.stringify(entity), + ); + }, + }); +} diff --git a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts index b4fb37de8f..2ba46274fb 100644 --- a/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts +++ b/plugins/scaffolder-backend/src/scaffolder/actions/builtin/createBuiltinActions.ts @@ -18,7 +18,10 @@ import { UrlReader } from '@backstage/backend-common'; import { CatalogApi } from '@backstage/catalog-client'; import { ScmIntegrations } from '@backstage/integration'; import { TemplaterBuilder } from '../../stages'; -import { createCatalogRegisterAction } from './catalog'; +import { + createCatalogRegisterAction, + createCatalogWriteAction, +} from './catalog'; import { createDebugLogAction } from './debug'; import { createFetchCookiecutterAction, createFetchPlainAction } from './fetch'; import { @@ -64,5 +67,6 @@ export const createBuiltinActions = (options: { }), createDebugLogAction(), createCatalogRegisterAction({ catalogClient, integrations }), + createCatalogWriteAction(), ]; }; diff --git a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts index bff10fc7e9..1716c1aa5b 100644 --- a/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts +++ b/plugins/scaffolder-backend/src/scaffolder/tasks/TaskWorker.ts @@ -48,6 +48,11 @@ export class TaskWorker { return JSON.stringify(parseRepoUrl(repoUrl)); }); + this.handlebars.registerHelper('projectSlug', repoUrl => { + const { owner, repo } = parseRepoUrl(repoUrl); + return `${owner}/${repo}`; + }); + this.handlebars.registerHelper('json', obj => JSON.stringify(obj)); this.handlebars.registerHelper('not', value => !isTruthy(value)); From b844817bc9bcc895b110edf06662e815f066455a Mon Sep 17 00:00:00 2001 From: Rogerio Angeliski Date: Fri, 18 Jun 2021 09:49:14 -0300 Subject: [PATCH 03/89] docs: added generated files Signed-off-by: Rogerio Angeliski --- plugins/scaffolder-backend/api-report.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/plugins/scaffolder-backend/api-report.md b/plugins/scaffolder-backend/api-report.md index 69c54343e3..792913749b 100644 --- a/plugins/scaffolder-backend/api-report.md +++ b/plugins/scaffolder-backend/api-report.md @@ -124,6 +124,9 @@ export function createCatalogRegisterAction(options: { integrations: ScmIntegrations; }): TemplateAction; +// @public (undocumented) +export function createCatalogWriteAction(): TemplateAction; + // @public export function createDebugLogAction(): TemplateAction; From c17c0fcf9e64a48bf8b0a1f2f4cf6ccc6f85fe70 Mon Sep 17 00:00:00 2001 From: Jussi Hallila Date: Fri, 18 Jun 2021 16:56:03 +0200 Subject: [PATCH 04/89] Add check against directory traversal for `docs_dir` config value Signed-off-by: Jussi Hallila --- .changeset/cold-rockets-mix.md | 5 +++++ .../__fixtures__/mkdocs_invalid_doc_dir2.yml | 3 +++ .../src/stages/generate/helpers.test.ts | 15 ++++++++++++--- .../src/stages/generate/helpers.ts | 17 +++++++++++++---- .../src/stages/generate/techdocs.ts | 2 +- 5 files changed, 34 insertions(+), 8 deletions(-) create mode 100644 .changeset/cold-rockets-mix.md create mode 100644 packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir2.yml diff --git a/.changeset/cold-rockets-mix.md b/.changeset/cold-rockets-mix.md new file mode 100644 index 0000000000..f38f5f61e9 --- /dev/null +++ b/.changeset/cold-rockets-mix.md @@ -0,0 +1,5 @@ +--- +'@backstage/techdocs-common': patch +--- + +Adding additional checks on tech docs to prevent folder traversal via mkdocs.yml docs_dir value. diff --git a/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir2.yml b/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir2.yml new file mode 100644 index 0000000000..514939e18c --- /dev/null +++ b/packages/techdocs-common/src/stages/generate/__fixtures__/mkdocs_invalid_doc_dir2.yml @@ -0,0 +1,3 @@ +site_name: Test site name +site_description: Test site description +docs_dir: ../../etc/ diff --git a/packages/techdocs-common/src/stages/generate/helpers.test.ts b/packages/techdocs-common/src/stages/generate/helpers.test.ts index 1ecb6252e5..9db76c8485 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.test.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.test.ts @@ -344,19 +344,28 @@ describe('helpers', () => { mockFs.restore(); }); + const inputDir = resolvePath(__filename, '../__fixtures__/'); it('should return true on when no docs_dir present', async () => { - await expect(validateMkdocsYaml('/mkdocs.yml')).resolves.toBeUndefined(); + await expect( + validateMkdocsYaml(inputDir, '/mkdocs.yml'), + ).resolves.toBeUndefined(); }); it('should return false on absolute doc_dir path', async () => { await expect( - validateMkdocsYaml('/mkdocs_invalid_doc_dir.yml'), + validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir.yml'), + ).rejects.toThrow(); + }); + + it('should return false on doc_dir path that traverses directory structure backwards', async () => { + await expect( + validateMkdocsYaml(inputDir, '/mkdocs_invalid_doc_dir2.yml'), ).rejects.toThrow(); }); it('should validate files with custom yaml tags', async () => { await expect( - validateMkdocsYaml('/mkdocs_with_extensions.yml'), + validateMkdocsYaml(inputDir, '/mkdocs_with_extensions.yml'), ).resolves.toBeUndefined(); }); }); diff --git a/packages/techdocs-common/src/stages/generate/helpers.ts b/packages/techdocs-common/src/stages/generate/helpers.ts index 3a65591c46..93915480dd 100644 --- a/packages/techdocs-common/src/stages/generate/helpers.ts +++ b/packages/techdocs-common/src/stages/generate/helpers.ts @@ -18,12 +18,12 @@ import { Entity } from '@backstage/catalog-model'; import { spawn } from 'child_process'; import fs from 'fs-extra'; import yaml, { DEFAULT_SCHEMA, Type } from 'js-yaml'; -import { isAbsolute, normalize } from 'path'; import { PassThrough, Writable } from 'stream'; import { Logger } from 'winston'; import { ParsedLocationAnnotation } from '../../helpers'; import { RemoteProtocol } from '../prepare/types'; import { SupportedGeneratorKey } from './types'; +import { resolve as resolvePath } from 'path'; // TODO: Implement proper support for more generators. export function getGeneratorKey(entity: Entity): SupportedGeneratorKey { @@ -158,9 +158,13 @@ const MKDOCS_SCHEMA = DEFAULT_SCHEMA.extend([ * Validating mkdocs config file for incorrect/insecure values * Throws on invalid configs * + * @param {string} inputDir base dir to be used as a docs_dir path validity check * @param {string} mkdocsYmlPath Absolute path to mkdocs.yml or equivalent of a docs site */ -export const validateMkdocsYaml = async (mkdocsYmlPath: string) => { +export const validateMkdocsYaml = async ( + inputDir: string, + mkdocsYmlPath: string, +) => { let mkdocsYmlFileString; try { mkdocsYmlFileString = await fs.readFile(mkdocsYmlPath, 'utf8'); @@ -173,9 +177,14 @@ export const validateMkdocsYaml = async (mkdocsYmlPath: string) => { const mkdocsYml: any = yaml.load(mkdocsYmlFileString, { schema: MKDOCS_SCHEMA, }); - if (mkdocsYml.docs_dir && isAbsolute(normalize(mkdocsYml.docs_dir))) { + + if ( + mkdocsYml.docs_dir && + !resolvePath(inputDir, mkdocsYml.docs_dir).startsWith(inputDir) + ) { throw new Error( - "docs_dir configuration value in mkdocs can't be an absolute directory path for security reasons. Use relative paths instead which are resolved relative to your mkdocs.yml file location.", + `docs_dir configuration value in mkdocs can't be an absolute directory or start with ../ for security reasons. + Use relative paths instead which are resolved relative to your mkdocs.yml file location.`, ); } }; diff --git a/packages/techdocs-common/src/stages/generate/techdocs.ts b/packages/techdocs-common/src/stages/generate/techdocs.ts index 1f2a9ae3ff..be9f494dc7 100644 --- a/packages/techdocs-common/src/stages/generate/techdocs.ts +++ b/packages/techdocs-common/src/stages/generate/techdocs.ts @@ -89,7 +89,7 @@ export class TechdocsGenerator implements GeneratorBase { ); } - await validateMkdocsYaml(mkdocsYmlPath); + await validateMkdocsYaml(inputDir, mkdocsYmlPath); // Directories to bind on container const mountDirs = { From 709589a8396fe0607011c57ae2eb5a8f28a47fcf Mon Sep 17 00:00:00 2001 From: Joe Porpeglia Date: Thu, 17 Jun 2021 16:29:32 -0400 Subject: [PATCH 05/89] Rename service catalog to software catalog, excluding blog posts Signed-off-by: Joe Porpeglia --- README.md | 8 +++--- ...log-home.png => software-catalog-home.png} | Bin docs/features/kubernetes/configuration.md | 4 +-- docs/features/kubernetes/index.md | 2 +- docs/features/software-catalog/index.md | 24 +++++++++--------- .../software-templates/adding-templates.md | 2 +- .../software-templates/writing-templates.md | 4 +-- docs/overview/architecture-overview.md | 4 +-- docs/overview/background.md | 4 +-- docs/overview/roadmap.md | 6 ++--- docs/overview/what-is-backstage.md | 8 +++--- docs/plugins/index.md | 8 +++--- ...tegrating-plugin-into-software-catalog.md} | 6 ++--- microsite/core/Footer.js | 2 +- ...g.yaml => backstage-software-catalog.yaml} | 4 +-- microsite/pages/en/demos.js | 2 +- microsite/pages/en/index.js | 8 +++--- microsite/sidebars.json | 2 +- microsite/siteConfig.js | 2 +- ... => backstage-software-catalog-icon-1.gif} | Bin ...log.svg => backstage-software-catalog.svg} | 0 mkdocs.yml | 2 +- .../src/stages/prepare/types.ts | 4 +-- .../RegisterComponentPage.test.tsx | 2 +- .../RegisterComponentResultDialog.test.tsx | 4 +-- .../src/DocsBuilder/builder.ts | 2 +- 26 files changed, 57 insertions(+), 57 deletions(-) rename docs/assets/software-catalog/{service-catalog-home.png => software-catalog-home.png} (100%) rename docs/plugins/{integrating-plugin-into-service-catalog.md => integrating-plugin-into-software-catalog.md} (95%) rename microsite/data/plugins/{backstage-service-catalog.yaml => backstage-software-catalog.yaml} (79%) rename microsite/static/animations/{backstage-service-catalog-icon-1.gif => backstage-software-catalog-icon-1.gif} (100%) rename microsite/static/img/{backstage-service-catalog.svg => backstage-software-catalog.svg} (100%) diff --git a/README.md b/README.md index 6dd490e43b..f8b54ad200 100644 --- a/README.md +++ b/README.md @@ -12,15 +12,15 @@ ## What is Backstage? -[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. +[Backstage](https://backstage.io/) is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. Backstage unifies all your infrastructure tooling, services, and documentation to create a streamlined development environment from end to end. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) Out of the box, Backstage includes: -- [Backstage Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) +- [Backstage Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) - [Backstage Software Templates](https://backstage.io/docs/features/software-templates/software-templates-index) for quickly spinning up new projects and standardizing your tooling with your organization’s best practices - [Backstage TechDocs](https://backstage.io/docs/features/techdocs/techdocs-overview) for making it easy to create, maintain, find, and use technical documentation, using a "docs like code" approach - Plus, a growing ecosystem of [open source plugins](https://github.com/backstage/backstage/tree/master/plugins) that further expand Backstage’s customizability and functionality @@ -38,7 +38,7 @@ Check out [the documentation](https://backstage.io/docs/getting-started) on how ## Documentation - [Main documentation](https://backstage.io/docs) -- [Service Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) +- [Software Catalog](https://backstage.io/docs/features/software-catalog/software-catalog-overview) - [Architecture](https://backstage.io/docs/overview/architecture-overview) ([Decisions](https://backstage.io/docs/architecture-decisions/adrs-overview)) - [Designing for Backstage](https://backstage.io/docs/dls/design) - [Storybook - UI components](https://backstage.io/storybook) diff --git a/docs/assets/software-catalog/service-catalog-home.png b/docs/assets/software-catalog/software-catalog-home.png similarity index 100% rename from docs/assets/software-catalog/service-catalog-home.png rename to docs/assets/software-catalog/software-catalog-home.png diff --git a/docs/features/kubernetes/configuration.md b/docs/features/kubernetes/configuration.md index 45ef28ea2f..068a17249c 100644 --- a/docs/features/kubernetes/configuration.md +++ b/docs/features/kubernetes/configuration.md @@ -68,7 +68,7 @@ The base URL to the Kubernetes control plane. Can be found by using the ##### `clusters.\*.name` A name to represent this cluster, this must be unique within the `clusters` -array. Users will see this value in the Service Catalog Kubernetes plugin. +array. Users will see this value in the Software Catalog Kubernetes plugin. ##### `clusters.\*.authProvider` @@ -195,7 +195,7 @@ annotations: #### Labeling Kubernetes components -In order for Kubernetes components to show up in the service catalog as a part +In order for Kubernetes components to show up in the software catalog as a part of an entity, Kubernetes components themselves can have the following label: ```yaml diff --git a/docs/features/kubernetes/index.md b/docs/features/kubernetes/index.md index 26794eca10..159370fbf4 100644 --- a/docs/features/kubernetes/index.md +++ b/docs/features/kubernetes/index.md @@ -2,7 +2,7 @@ id: overview title: Kubernetes sidebar_label: Overview -description: Monitoring Kubernetes based services with the service catalog +description: Monitoring Kubernetes based services with the software catalog --- Kubernetes in Backstage is a tool that's designed around the needs of service diff --git a/docs/features/software-catalog/index.md b/docs/features/software-catalog/index.md index 2189b7d799..a502c2aee5 100644 --- a/docs/features/software-catalog/index.md +++ b/docs/features/software-catalog/index.md @@ -1,29 +1,29 @@ --- id: software-catalog-overview -title: Backstage Service Catalog (alpha) +title: Backstage Software Catalog (alpha) sidebar_label: Overview # prettier-ignore -description: The Backstage Service Catalog — actually, a software catalog, since it includes more than just services +description: The Backstage Software Catalog --- -## What is a Service Catalog? +## What is a Software Catalog? -The Backstage Service Catalog — actually, a software catalog, since it includes +The Backstage Software Catalog — actually, a software catalog, since it includes more than just services — is a centralized system that keeps track of ownership and metadata for all the software in your ecosystem (services, websites, libraries, data pipelines, etc). The catalog is built around the concept of [metadata YAML files](descriptor-format.md) stored together with the code, which are then harvested and visualized in Backstage. -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) ## How it works -Backstage and the Backstage Service Catalog make it easy for one team to manage +Backstage and the Backstage Software Catalog make it easy for one team to manage 10 services — and makes it possible for your company to manage thousands of them. -More specifically, the Service Catalog enables two main use-cases: +More specifically, the Software Catalog enables two main use-cases: 1. Helping teams manage and maintain the software they own. Teams get a uniform view of all their software; services, libraries, websites, ML models — you @@ -37,11 +37,11 @@ The Software Catalog is available to browse at `/catalog`. If you've followed [Getting Started with Backstage](../../getting-started), you should be able to browse the catalog at `http://localhost:3000`. -![](../../assets/software-catalog/service-catalog-home.png) +![](../../assets/software-catalog/software-catalog-home.png) ## Adding components to the catalog -The source of truth for the components in your service catalog are +The source of truth for the components in your software catalog are [metadata YAML files](descriptor-format.md) stored in source control (GitHub, GitHub Enterprise, GitLab, ...). @@ -104,11 +104,11 @@ them, and do so using their normal Git workflow. ![](../../assets/software-catalog/bsc-edit.png) Once the change has been merged, Backstage will automatically show the updated -metadata in the service catalog after a short while. +metadata in the software catalog after a short while. ## Finding software in the catalog -By default the service catalog shows components owned by the team of the logged +By default the software catalog shows components owned by the team of the logged in user. But you can also switch to _All_ to see all the components across your company's software ecosystem. Basic inline _search_ and _column filtering_ makes it easy to browse a big set of components. @@ -124,7 +124,7 @@ _starring_ of components: ## Integrated tooling through plugins -The service catalog is a great way to organize the infrastructure tools you use +The software catalog is a great way to organize the infrastructure tools you use to manage the software. This is how Backstage creates one developer portal for all your tools. Rather than asking teams to jump between different infrastructure UIs (and incurring additional cognitive overhead each time they diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index ecb7d11be9..d185558949 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -83,7 +83,7 @@ spec: [Template Entity](../software-catalog/descriptor-format.md#kind-template) contains more information about the required fields. -Once we have a `template.yaml` ready, we can then add it to the service catalog +Once we have a `template.yaml` ready, we can then add it to the software catalog for use by the scaffolder. You can add the template files to the catalog through diff --git a/docs/features/software-templates/writing-templates.md b/docs/features/software-templates/writing-templates.md index 8481e64eac..529376eb89 100644 --- a/docs/features/software-templates/writing-templates.md +++ b/docs/features/software-templates/writing-templates.md @@ -4,8 +4,8 @@ title: Writing Templates description: Details around creating your own custom Software Templates --- -Templates are stored in the **Service Catalog** under a kind `Template`. You can -create your own templates with a small `yaml` definition which describes the +Templates are stored in the **Software Catalog** under a kind `Template`. You +can create your own templates with a small `yaml` definition which describes the template and it's metadata, along with some input variables that your template will need, and then a list of actions which are then executed by the scaffolding service. diff --git a/docs/overview/architecture-overview.md b/docs/overview/architecture-overview.md index 00d83b43ed..7ea892840c 100644 --- a/docs/overview/architecture-overview.md +++ b/docs/overview/architecture-overview.md @@ -25,7 +25,7 @@ different ways. The following diagram shows how Backstage might look when deployed inside a company which uses the Tech Radar plugin, the Lighthouse plugin, the CircleCI -plugin and the service catalog. +plugin and the software catalog. There are 3 main components in this architecture: @@ -142,7 +142,7 @@ Its architecture looks like this: ![lighthouse plugin backed to microservice and database](../assets/architecture-overview/lighthouse-plugin-architecture.png) -The service catalog in Backstage is another example of a service backed plugin. +The software catalog in Backstage is another example of a service backed plugin. It retrieves a list of services, or "entities", from the Backstage Backend service and renders them in a table for the user. diff --git a/docs/overview/background.md b/docs/overview/background.md index 0c22396e72..00fb5107ea 100644 --- a/docs/overview/background.md +++ b/docs/overview/background.md @@ -22,8 +22,8 @@ Our idea was to centralize and simplify end-to-end software development with an abstraction layer that sits on top of all of our infrastructure and developer tooling. That’s Backstage. -It’s a developer portal powered by a centralized service catalog — with a plugin -architecture that makes it endlessly extensible and customizable. +It’s a developer portal powered by a centralized software catalog — with a +plugin architecture that makes it endlessly extensible and customizable. Manage all your services, software, tooling, and testing in Backstage. Start building a new microservice using an automated template in Backstage. Create, diff --git a/docs/overview/roadmap.md b/docs/overview/roadmap.md index a736126324..0013c99c6b 100644 --- a/docs/overview/roadmap.md +++ b/docs/overview/roadmap.md @@ -22,7 +22,7 @@ We have divided the project into three high-level _phases_: [UX patterns and components](https://backstage.io/storybook) help ensure a consistent experience between tools. -- 🐢 **Phase 2:** Service Catalog +- 🐢 **Phase 2:** Software Catalog ([alpha released](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha)) - With a single catalog, Backstage makes it easy for a team to manage ten services — and makes it possible for your company to manage thousands of them. @@ -120,13 +120,13 @@ Chances are that someone will jump in and help build it. - [TechDocs v1](https://backstage.io/blog/2020/09/08/announcing-tech-docs) - [Plugin marketplace](https://backstage.io/plugins) - [Improved and move documentation to backstage.io](https://backstage.io/docs/overview/what-is-backstage) -- [Backstage Service Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) +- [Backstage Software Catalog (alpha)](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha) - [Backstage Software Templates (alpha)](https://backstage.io/blog/2020/08/05/announcing-backstage-software-templates) - [Make it possible to add custom auth providers](https://backstage.io/blog/2020/07/01/how-to-enable-authentication-in-backstage-using-passport) - [TechDocs v0](https://github.com/backstage/backstage/milestone/15) - CI plugins: CircleCI, Jenkins, GitHub Actions and TravisCI - [Service API documentation](https://github.com/backstage/backstage/pull/1737) -- Backstage Service Catalog can read from: GitHub, GitLab, +- Backstage Software Catalog can read from: GitHub, GitLab, [Bitbucket](https://github.com/backstage/backstage/pull/1938) - Support auth providers: Google, Okta, GitHub, GitLab, [auth0](https://github.com/backstage/backstage/pull/1611), diff --git a/docs/overview/what-is-backstage.md b/docs/overview/what-is-backstage.md index ec824a1e11..1d9310541a 100644 --- a/docs/overview/what-is-backstage.md +++ b/docs/overview/what-is-backstage.md @@ -2,13 +2,13 @@ id: what-is-backstage title: What is Backstage? # prettier-ignore -description: Backstage is an open platform for building developer portals. Powered by a centralized service catalog, Backstage restores order to your microservices and infrastructure +description: Backstage is an open platform for building developer portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure --- -![service-catalog](https://backstage.io/blog/assets/6/header.png) +![software-catalog](https://backstage.io/blog/assets/6/header.png) [Backstage](https://backstage.io/) is an open platform for building developer -portals. Powered by a centralized service catalog, Backstage restores order to +portals. Powered by a centralized software catalog, Backstage restores order to your microservices and infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -17,7 +17,7 @@ to create a streamlined development environment from end to end. Out of the box, Backstage includes: -- [Backstage Service Catalog](../features/software-catalog/index.md) for +- [Backstage Software Catalog](../features/software-catalog/index.md) for managing all your software (microservices, libraries, data pipelines, websites, ML models, etc.) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 32d523b2f5..a582556311 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -28,9 +28,9 @@ This helps the community know what plugins are in development. You can also use this process if you have an idea for a good plugin but you hope that someone else will pick up the work. -## Integrate into the Service Catalog +## Integrate into the Software Catalog If your plugin isn't supposed to live as a standalone page, but rather needs to -be presented as a part of a Service Catalog (e.g. a separate tab or a card on an -"Overview" tab), then check out -[the instruction](integrating-plugin-into-service-catalog.md) on how to do it. +be presented as a part of a Software Catalog (e.g. a separate tab or a card on +an "Overview" tab), then check out +[the instruction](integrating-plugin-into-software-catalog.md) on how to do it. diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-software-catalog.md similarity index 95% rename from docs/plugins/integrating-plugin-into-service-catalog.md rename to docs/plugins/integrating-plugin-into-software-catalog.md index 047b6f14e6..ae7c3189da 100644 --- a/docs/plugins/integrating-plugin-into-service-catalog.md +++ b/docs/plugins/integrating-plugin-into-software-catalog.md @@ -1,7 +1,7 @@ --- -id: integrating-plugin-into-service-catalog -title: Integrate into the Service Catalog -description: How to integrate a plugin into service catalog +id: integrating-plugin-into-software-catalog +title: Integrate into the Software Catalog +description: How to integrate a plugin into software catalog --- > This is an advanced use case and currently is an experimental feature. Expect diff --git a/microsite/core/Footer.js b/microsite/core/Footer.js index ec08216367..8e69b7ff6f 100644 --- a/microsite/core/Footer.js +++ b/microsite/core/Footer.js @@ -33,7 +33,7 @@ class Footer extends React.Component { - Service Catalog + Software Catalog Create a Plugin Designing for Backstage diff --git a/microsite/data/plugins/backstage-service-catalog.yaml b/microsite/data/plugins/backstage-software-catalog.yaml similarity index 79% rename from microsite/data/plugins/backstage-service-catalog.yaml rename to microsite/data/plugins/backstage-software-catalog.yaml index 1a788078a5..ae16550297 100644 --- a/microsite/data/plugins/backstage-service-catalog.yaml +++ b/microsite/data/plugins/backstage-software-catalog.yaml @@ -1,10 +1,10 @@ --- -title: Backstage Service Catalog +title: Backstage Software Catalog author: Spotify authorUrl: https://github.com/spotify category: Core Feature description: Manage all your services and software components, all in one place. documentation: https://backstage.io/docs/features/software-catalog/software-catalog-overview -iconUrl: img/backstage-service-catalog.svg +iconUrl: img/backstage-software-catalog.svg npmPackageName: '@backstage/plugin-catalog' order: 1 diff --git a/microsite/pages/en/demos.js b/microsite/pages/en/demos.js index e3943d2905..7d97641815 100644 --- a/microsite/pages/en/demos.js +++ b/microsite/pages/en/demos.js @@ -42,7 +42,7 @@ const Background = props => { To explore the UI and basic features of Backstage firsthand, go to: demo.backstage.io. (Tip: click “All” to view all the example components in the - service catalog.) + software catalog.) diff --git a/microsite/pages/en/index.js b/microsite/pages/en/index.js index 45d87daa1d..d7325bb234 100644 --- a/microsite/pages/en/index.js +++ b/microsite/pages/en/index.js @@ -27,7 +27,7 @@ class Index extends React.Component { An open platform for building developer portals - Powered by a centralized service catalog, Backstage restores + Powered by a centralized software catalog, Backstage restores order to your infrastructure and enables your product teams to ship high-quality code quickly — without compromising autonomy. @@ -102,10 +102,10 @@ class Index extends React.Component { {' '} - Backstage Service Catalog{' '} + Backstage Software Catalog{' '} - Learn more about the service catalog + Learn more about the software catalog { , diff --git a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx index e276b1e755..63d218b242 100644 --- a/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentResultDialog/RegisterComponentResultDialog.test.tsx @@ -37,7 +37,7 @@ describe('RegisterComponentResultDialog', () => { entities={[]} catalogRouteRef={createRouteRef({ path: '/catalog', - title: 'Service Catalog', + title: 'Software Catalog', })} />, { wrapper: Wrapper }, @@ -76,7 +76,7 @@ describe('RegisterComponentResultDialog', () => { entities={entities} catalogRouteRef={createRouteRef({ path: '/catalog', - title: 'Service Catalog', + title: 'Software Catalog', })} />, { wrapper: Wrapper }, diff --git a/plugins/techdocs-backend/src/DocsBuilder/builder.ts b/plugins/techdocs-backend/src/DocsBuilder/builder.ts index 46e4bd0c75..e9df0b8400 100644 --- a/plugins/techdocs-backend/src/DocsBuilder/builder.ts +++ b/plugins/techdocs-backend/src/DocsBuilder/builder.ts @@ -75,7 +75,7 @@ export class DocsBuilder { public async build(): Promise { if (!this.entity.metadata.uid) { throw new Error( - 'Trying to build documentation for entity not in service catalog', + 'Trying to build documentation for entity not in software catalog', ); } From 594444d0e0eb41c6aa817d34c31ac4b3f1bdd21f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 21 Jun 2021 17:19:33 +0200 Subject: [PATCH 06/89] docs: add tutorial for how to migrate away from @backstage/core Signed-off-by: Patrik Oldsberg --- docs/tutorials/migrating-away-from-core.md | 135 +++++++++++++++++++++ microsite/sidebars.json | 3 +- mkdocs.yml | 1 + 3 files changed, 138 insertions(+), 1 deletion(-) create mode 100644 docs/tutorials/migrating-away-from-core.md diff --git a/docs/tutorials/migrating-away-from-core.md b/docs/tutorials/migrating-away-from-core.md new file mode 100644 index 0000000000..da9d177ccb --- /dev/null +++ b/docs/tutorials/migrating-away-from-core.md @@ -0,0 +1,135 @@ +--- +id: migrating-away-from-core +title: Migrating away from @backstage/core +description: Guide on how to migrate to the new Backstage core libraries. +--- + +The `@backstage/core` package has been split into three separate packages, +`@backstage/core-app-api`, `@backstage/core-plugin-api`, and +`@backstage/core-components`. For more information about the reasoning behind +this change and the naming of the packages, see the +[original RFC](https://github.com/backstage/backstage/issues/4872) and +[initial PR](https://github.com/backstage/backstage/pull/5825). + +The main purpose of the split is to make plugins more decoupled from the app, +and open up for the possibility of combining plugins using many different +versions of the core libraries. This should significantly reduce the maintenance +burden on plugin authors, as well as reduce the impact of breaking changes in +the core APIs. + +## Migration + +At a high level the migration is done by simply replacing usages of +`@backstage/core` with one or more of the three new core libraries. There are a +few breaking changes in the new packages that are listed below, but for most +plugins the migration is a simple replacement. In order to make the migration as +smooth as possible we provide a collection of tools to automate the majority of +the migration effort. + +Below is a list of steps that should get most projects completely migrated, the +order of the steps is a recommendation but not required, so don't worry if you +need to go back to previous steps to fix things. + +### Step 1 - Run codemod + +The first step is to run +[`@backstage/codemods`](https://www.npmjs.com/package/@backstage/codemods) +across your project. This will automatically convert all module imports in your +source code to use one of the three new core packages instead. For example, the +following change might occur: + +```diff +-import { useApi, configApiRef, InfoCard } from '@backstage/core'; ++import { useApi, configApiRef } from '@backstage/core-plugin-api'; ++import { InfoCard } from '@backstage/core-components'; +``` + +In a typical app created with `@backstage/create-app`, you would run the +following: + +```shell +npx @backstage/codemods apply core-imports packages plugins +``` + +The last two arguments, `packages` and `plugins`, are the folders that the +codemod should be applied to. Add or remove folders as needed for your project. + +The codemod might fail for some files because of the missing `IconKey` type in +any of the new packages. This is one of the few breaking changes. To fix, remove +any `IconKey` imports and replace usages of it with the `string` type, see the +breaking changes section below for details. Once usages of `IconKey` type have +been removed, you can re-run the codemod for those files. + +Note that while the codemod tries to stick to using the existing formatting in +your project, it doesn't always manage to do that. If you're using `prettier` to +format the code in your project, it's best to run `prettier --write` on any +files that were changed by the codemod. + +### Step 2 - Update dependencies + +The next step is to update dependencies in your `package.json` files. Any +package that currently depends on `@backstage/core` will need to have it +replaced by one or more of the new packages. The app package should have all +three packages added to `dependencies`, while for plugins and additional non-app +packages, the `@backstage/core-plugin-api` and `@backstage/core-components` +packages should be added to the set of regular `dependencies`, and +`@backstage/core-app-api` should be added to `devDependencies` for usage in +tests. + +A tool that can help out with step is the `plugin:diff` command from the +`@backstage/cli`, it will compare your plugin to the base plugin template and +suggest changes where the plugin deviates. A quick way to get this step done if +you have up-to-date project is to run the following in the project root: + +```bash +# The --yes flag causes all suggested changes to be accepted automatically +yarn diff --yes +``` + +If you do not have the `diff` command set up in `package.json`, you can also +manually execute the following in each plugin folder: + +```bash +yarn backstage-cli plugin:diff --yes +``` + +### Step 3 - Manual review + +At this point your app is either completely or very close to being migrated. Run +type checks with `yarn tsc` to check if you hit any of the breaking changes +below or if there are any other things to fix. It can also be worthwhile +searching for occurrences of `@backstage/core` in the codebase, as that might +find usages in for example `jest` mock calls, which aren't handled by the +codemod. + +As a final step you'll want to boot up the app and take it through any regular +verification step that you have set up for your project. Don't hesitate to open +a GitHub issue, PR, or reach out on Discord if you hit any snags, or if there +are any additional steps or hints that you think should be added to this guide! + +## Breaking Changes + +The following is a list of breaking changes between `@backstage/core` and the +three new core packages. Not that this list may not be exhaustive depending on +when you migrate your app, as new releases of the new core packages may bring +further changes. + +### Removed `IconKey` type + +The `IconKey` type used to be a string union of all known keys used for the app +icons available through `useApp().getSystemIcon(key)`. The type has been removed +since the set of allowed icon keys is no longer constrained, and there is +instead only a guarantee that the app provides a minimum set of icons, but can +provide any icons it wants beyond that. Migration is done by simply replacing +old usages by the `string` type. + +### Constrained `IconComponent` type + +The `IconComponent` type used to allow all of the props from the MUI `SvgIcon`. +This encouraged some bad patterns in open source plugins such as applying colors +to the icons, which in turn hurt the ability to replace the icons with custom +ones. The `IconComponent` type, which is now exported from +`@backstage/core-plugin-api`, now only accepts a `fontSize` prop used to set the +size of the icon. The type is compatible with the MUI `SvgIcon`, but there may +be situations where an icon needs an explicit cast to `IconComponent` in order +to narrow the type. diff --git a/microsite/sidebars.json b/microsite/sidebars.json index ea1e1a4e89..23561a5411 100644 --- a/microsite/sidebars.json +++ b/microsite/sidebars.json @@ -243,7 +243,8 @@ "Tutorials": [ "tutorials/journey", "tutorials/quickstart-app-plugin", - "tutorials/switching-sqlite-postgres" + "tutorials/switching-sqlite-postgres", + "tutorials/migrating-away-from-core" ], "Architecture Decision Records (ADRs)": [ "architecture-decisions/adrs-overview", diff --git a/mkdocs.yml b/mkdocs.yml index 6c4af107ce..6302102447 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -158,6 +158,7 @@ nav: - Backend: 'api/backend.md' - Tutorials: - Future developer journey: 'tutorials/journey.md' + - Migrating away from @backstage/core: 'tutorials/migrating-away-from-core.md' - Adding Custom Plugin to Existing Monorepo App: 'tutorials/quickstart-app-plugin.md' - Switching Backstage from SQLite to PostgreSQL: 'tutorials/switching-sqlite-postgres.md' - Architecture Decision Records (ADRs): From b47fc34bc87d803ee8e26e0db7524a2dc7613190 Mon Sep 17 00:00:00 2001 From: Joe Porpeglia Date: Mon, 21 Jun 2021 14:43:19 -0400 Subject: [PATCH 07/89] Add changeset Signed-off-by: Joe Porpeglia --- .changeset/purple-carpets-fold.md | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 .changeset/purple-carpets-fold.md diff --git a/.changeset/purple-carpets-fold.md b/.changeset/purple-carpets-fold.md new file mode 100644 index 0000000000..bdade07931 --- /dev/null +++ b/.changeset/purple-carpets-fold.md @@ -0,0 +1,7 @@ +--- +'@backstage/techdocs-common': patch +'@backstage/plugin-register-component': patch +'@backstage/plugin-techdocs-backend': patch +--- + +Update "service catalog" references to "software catalog" From ce4abc1e02365b4fb54006fc5b2edf597cc95045 Mon Sep 17 00:00:00 2001 From: Jesse Bye Date: Thu, 17 Jun 2021 13:42:49 -0700 Subject: [PATCH 08/89] Add tooltips to org ownership card tiles Signed-off-by: Jesse Bye --- .changeset/violet-experts-design.md | 5 ++ .../Cards/OwnershipCard/OwnershipCard.tsx | 70 ++++++++++++++----- 2 files changed, 59 insertions(+), 16 deletions(-) create mode 100644 .changeset/violet-experts-design.md diff --git a/.changeset/violet-experts-design.md b/.changeset/violet-experts-design.md new file mode 100644 index 0000000000..1db44aaf4b --- /dev/null +++ b/.changeset/violet-experts-design.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-org': patch +--- + +Display a tooltip for ownership cards listing the related entities diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 0d320f182b..5de201835d 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -33,6 +33,7 @@ import { createStyles, Grid, makeStyles, + Tooltip, Typography, } from '@material-ui/core'; import React from 'react'; @@ -94,39 +95,60 @@ const useStyles = makeStyles((theme: BackstageTheme) => }), ); -const countEntitiesBy = ( +const listEntitiesBy = ( entities: Array, kind: EntitiesKinds, type?: EntitiesTypes, ) => entities.filter( e => e.kind === kind && (type ? e?.spec?.type === type : true), - ).length; + ); + +const countEntitiesBy = ( + entities: Array, + kind: EntitiesKinds, + type?: EntitiesTypes, +) => listEntitiesBy(entities, kind, type).length; const EntityCountTile = ({ counter, className, + entities, name, }: { counter: number; className: EntitiesTypes; + entities: Entity[]; name: string; }) => { + let entityNames; const classes = useStyles(); + + if (entities.length < 20) { + entityNames = entities.map(e => e.metadata.name).join(', '); + } else { + entityNames = `${entities + .map(e => e.metadata.name) + .slice(0, 20) + .join(', ')}, ...`; + } + return ( - - - {counter} - - - {name} - - + + + + {counter} + + + {name} + + + ); }; @@ -166,6 +188,7 @@ export const OwnershipCard = ({ { counter: countEntitiesBy(ownedEntitiesList, 'Component', 'service'), className: 'service', + entities: listEntitiesBy(ownedEntitiesList, 'Component', 'service'), name: 'Services', }, { @@ -175,29 +198,43 @@ export const OwnershipCard = ({ 'documentation', ), className: 'documentation', + entities: listEntitiesBy( + ownedEntitiesList, + 'Component', + 'documentation', + ), name: 'Documentation', }, { counter: countEntitiesBy(ownedEntitiesList, 'API'), className: 'api', + entities: listEntitiesBy(ownedEntitiesList, 'API'), name: 'APIs', }, { counter: countEntitiesBy(ownedEntitiesList, 'Component', 'library'), className: 'library', + entities: listEntitiesBy(ownedEntitiesList, 'Component', 'library'), name: 'Libraries', }, { counter: countEntitiesBy(ownedEntitiesList, 'Component', 'website'), className: 'website', + entities: listEntitiesBy(ownedEntitiesList, 'Component', 'website'), name: 'Websites', }, { counter: countEntitiesBy(ownedEntitiesList, 'Component', 'tool'), className: 'tool', + entities: listEntitiesBy(ownedEntitiesList, 'Component', 'tool'), name: 'Tools', }, - ] as Array<{ counter: number; className: EntitiesTypes; name: string }>; + ] as Array<{ + counter: number; + className: EntitiesTypes; + entities: string[]; + name: string; + }>; }, [catalogApi, entity]); if (loading) { @@ -214,6 +251,7 @@ export const OwnershipCard = ({ From 5e8b591c866e8acaa3256c0ad6cf786d4b4cc03c Mon Sep 17 00:00:00 2001 From: Jesse Bye Date: Thu, 17 Jun 2021 13:55:10 -0700 Subject: [PATCH 09/89] Fix tsc issue Signed-off-by: Jesse Bye --- .../org/src/components/Cards/OwnershipCard/OwnershipCard.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx index 5de201835d..c2535aa801 100644 --- a/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx +++ b/plugins/org/src/components/Cards/OwnershipCard/OwnershipCard.tsx @@ -232,7 +232,7 @@ export const OwnershipCard = ({ ] as Array<{ counter: number; className: EntitiesTypes; - entities: string[]; + entities: Entity[]; name: string; }>; }, [catalogApi, entity]); From 8e5e5ee46e51fd4f9f5e570f58553c50d523277f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 22 Jun 2021 20:16:26 +0200 Subject: [PATCH 10/89] auth: show better error if auth provider keys are missing Signed-off-by: Himanshu Mishra --- plugins/auth-backend/src/service/router.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index fa551f6ec2..a79ad66179 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -139,7 +139,16 @@ export async function createRouter({ router.use('/:provider/', req => { const { provider } = req.params; - throw new NotFoundError(`No auth provider registered for '${provider}'`); + if (providers.includes(provider)) { + // If they added the provider under auth.providers but the clientId and clientSecret etc. were not found. + throw new NotFoundError( + `Auth provider registered for '${provider}' is misconfigured. This could mean the configs under ` + + `auth.providers.${provider} are missing or the environment variables used are not defined. ` + + `Check the auth backend plugin logs when the backend starts to see more details.`, + ); + } else { + throw new NotFoundError(`No auth provider registered for '${provider}'`); + } }); return router; From 72574ac4d2dcd370165c008b135ad56aacbdab92 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 22 Jun 2021 20:17:42 +0200 Subject: [PATCH 11/89] auth: add changeset for error improvement Signed-off-by: Himanshu Mishra --- .changeset/thirty-turtles-carry.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/thirty-turtles-carry.md diff --git a/.changeset/thirty-turtles-carry.md b/.changeset/thirty-turtles-carry.md new file mode 100644 index 0000000000..96b0495561 --- /dev/null +++ b/.changeset/thirty-turtles-carry.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +Show better error message when configs defined under auth.providers. are undefined. From d91ec5b1b0cb2e6e17896cbda48716a093d2b990 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Tue, 22 Jun 2021 20:19:58 +0200 Subject: [PATCH 12/89] auth: update language Signed-off-by: Himanshu Mishra --- plugins/auth-backend/src/service/router.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index a79ad66179..548c551150 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -140,7 +140,7 @@ export async function createRouter({ router.use('/:provider/', req => { const { provider } = req.params; if (providers.includes(provider)) { - // If they added the provider under auth.providers but the clientId and clientSecret etc. were not found. + // If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found. throw new NotFoundError( `Auth provider registered for '${provider}' is misconfigured. This could mean the configs under ` + `auth.providers.${provider} are missing or the environment variables used are not defined. ` + From 3b590b8cf1fbd0b250544f8c6906a19ecd879fe0 Mon Sep 17 00:00:00 2001 From: David Tuite Date: Tue, 22 Jun 2021 10:49:42 +0100 Subject: [PATCH 13/89] Clarify dependency direction in EntityDependsOnResourcesCard Signed-off-by: David Tuite --- .../DependsOnResourcesCard/DependsOnResourcesCard.test.tsx | 4 ++-- .../DependsOnResourcesCard/DependsOnResourcesCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx index f2d41557f9..dc967c5be4 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.test.tsx @@ -66,7 +66,7 @@ describe('', () => { , ); - expect(getByText('Resources')).toBeInTheDocument(); + expect(getByText('Depends on resources')).toBeInTheDocument(); expect( getByText(/No resource is a dependency of this component/i), ).toBeInTheDocument(); @@ -114,7 +114,7 @@ describe('', () => { ); await waitFor(() => { - expect(getByText('Resources')).toBeInTheDocument(); + expect(getByText('Depends on resources')).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx index 163a51b84e..34a8e5824d 100644 --- a/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx +++ b/plugins/catalog/src/components/DependsOnResourcesCard/DependsOnResourcesCard.tsx @@ -31,7 +31,7 @@ export const DependsOnResourcesCard = ({ variant = 'gridItem' }: Props) => { return ( Date: Tue, 22 Jun 2021 10:51:49 +0100 Subject: [PATCH 14/89] Disambiguate HasComponentsCard from other component relationship cards Signed-off-by: David Tuite --- .../components/HasComponentsCard/HasComponentsCard.test.tsx | 4 ++-- .../src/components/HasComponentsCard/HasComponentsCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx index 8ba2340444..3a6cb48369 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.test.tsx @@ -66,7 +66,7 @@ describe('', () => { , ); - expect(getByText('Components')).toBeInTheDocument(); + expect(getByText('Has components')).toBeInTheDocument(); expect( getByText(/No component is part of this system/i), ).toBeInTheDocument(); @@ -114,7 +114,7 @@ describe('', () => { ); await waitFor(() => { - expect(getByText('Components')).toBeInTheDocument(); + expect(getByText('Has components')).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx index a54147d6a8..cefdb15a6e 100644 --- a/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx +++ b/plugins/catalog/src/components/HasComponentsCard/HasComponentsCard.tsx @@ -31,7 +31,7 @@ export const HasComponentsCard = ({ variant = 'gridItem' }: Props) => { return ( Date: Tue, 22 Jun 2021 10:53:19 +0100 Subject: [PATCH 15/89] Clasify system has resources card Signed-off-by: David Tuite --- .../src/components/HasResourcesCard/HasResourcesCard.test.tsx | 4 ++-- .../src/components/HasResourcesCard/HasResourcesCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx index 80ae4f9aee..72202bd4a6 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.test.tsx @@ -66,7 +66,7 @@ describe('', () => { , ); - expect(getByText('Resources')).toBeInTheDocument(); + expect(getByText('Has resources')).toBeInTheDocument(); expect( getByText(/No resource is part of this system/i), ).toBeInTheDocument(); @@ -114,7 +114,7 @@ describe('', () => { ); await waitFor(() => { - expect(getByText('Resources')).toBeInTheDocument(); + expect(getByText('Has resources')).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx index bf96945602..3a57317a06 100644 --- a/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx +++ b/plugins/catalog/src/components/HasResourcesCard/HasResourcesCard.tsx @@ -31,7 +31,7 @@ export const HasResourcesCard = ({ variant = 'gridItem' }: Props) => { return ( Date: Tue, 22 Jun 2021 10:55:24 +0100 Subject: [PATCH 16/89] Clarify the HasSystems card title Signed-off-by: David Tuite --- .../src/components/HasSystemsCard/HasSystemsCard.test.tsx | 4 ++-- .../catalog/src/components/HasSystemsCard/HasSystemsCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx index fba7c88ac1..b1392b7a10 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.test.tsx @@ -66,7 +66,7 @@ describe('', () => { , ); - expect(getByText('Systems')).toBeInTheDocument(); + expect(getByText('Has systems')).toBeInTheDocument(); expect(getByText(/No system is part of this domain/i)).toBeInTheDocument(); }); @@ -112,7 +112,7 @@ describe('', () => { ); await waitFor(() => { - expect(getByText('Systems')).toBeInTheDocument(); + expect(getByText('Has systems')).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx index e5217efe48..b3e756c8dc 100644 --- a/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx +++ b/plugins/catalog/src/components/HasSystemsCard/HasSystemsCard.tsx @@ -31,7 +31,7 @@ export const HasSystemsCard = ({ variant = 'gridItem' }: Props) => { return ( Date: Tue, 22 Jun 2021 13:08:54 +0100 Subject: [PATCH 17/89] Clarify title of HasSubcomponentsCard Signed-off-by: David Tuite --- .../HasSubcomponentsCard/HasSubcomponentsCard.test.tsx | 4 ++-- .../components/HasSubcomponentsCard/HasSubcomponentsCard.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx index d9d8834869..d25cfce216 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.test.tsx @@ -66,7 +66,7 @@ describe('', () => { , ); - expect(getByText('Subcomponents')).toBeInTheDocument(); + expect(getByText('Has subcomponents')).toBeInTheDocument(); expect( getByText(/No subcomponent is part of this component/i), ).toBeInTheDocument(); @@ -114,7 +114,7 @@ describe('', () => { ); await waitFor(() => { - expect(getByText('Subcomponents')).toBeInTheDocument(); + expect(getByText('Has subcomponents')).toBeInTheDocument(); expect(getByText(/target-name/i)).toBeInTheDocument(); }); }); diff --git a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx index 67582453e2..ddeffe3fbd 100644 --- a/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx +++ b/plugins/catalog/src/components/HasSubcomponentsCard/HasSubcomponentsCard.tsx @@ -30,7 +30,7 @@ export const HasSubcomponentsCard = ({ variant = 'gridItem' }: Props) => { return ( Date: Tue, 22 Jun 2021 22:04:34 +0100 Subject: [PATCH 18/89] Add changeset Signed-off-by: David Tuite --- .changeset/three-tables-smash.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/three-tables-smash.md diff --git a/.changeset/three-tables-smash.md b/.changeset/three-tables-smash.md new file mode 100644 index 0000000000..647647b666 --- /dev/null +++ b/.changeset/three-tables-smash.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Clearer titles for the relationship cards From d3b6c6c12a7d94b706055bac6cdc5fd5807df901 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 23 Jun 2021 04:45:44 +0000 Subject: [PATCH 19/89] chore(deps): bump msw from 0.21.2 to 0.29.0 Bumps [msw](https://github.com/mswjs/msw) from 0.21.2 to 0.29.0. - [Release notes](https://github.com/mswjs/msw/releases) - [Changelog](https://github.com/mswjs/msw/blob/master/CHANGELOG.md) - [Commits](https://github.com/mswjs/msw/compare/v0.21.2...v0.29.0) --- updated-dependencies: - dependency-name: msw dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- packages/backend-common/package.json | 2 +- packages/catalog-client/package.json | 2 +- packages/core-api/package.json | 2 +- packages/core-app-api/package.json | 2 +- packages/core-plugin-api/package.json | 2 +- packages/integration-react/package.json | 2 +- packages/integration/package.json | 2 +- packages/test-utils/package.json | 2 +- plugins/api-docs/package.json | 2 +- plugins/app-backend/package.json | 2 +- plugins/auth-backend/package.json | 2 +- plugins/badges/package.json | 2 +- plugins/bitrise/package.json | 2 +- .../package.json | 2 +- plugins/catalog-backend/package.json | 2 +- plugins/catalog-graphql/package.json | 2 +- plugins/catalog-import/package.json | 2 +- plugins/catalog-react/package.json | 2 +- plugins/catalog/package.json | 2 +- plugins/circleci/package.json | 2 +- plugins/cloudbuild/package.json | 2 +- plugins/code-coverage-backend/package.json | 2 +- plugins/code-coverage/package.json | 2 +- plugins/config-schema/package.json | 2 +- plugins/cost-insights/package.json | 2 +- plugins/explore-react/package.json | 2 +- plugins/explore/package.json | 2 +- plugins/fossa/package.json | 2 +- plugins/gcp-projects/package.json | 2 +- plugins/git-release-manager/package.json | 2 +- plugins/github-actions/package.json | 2 +- plugins/github-deployments/package.json | 2 +- plugins/gitops-profiles/package.json | 2 +- plugins/graphiql/package.json | 2 +- plugins/graphql/package.json | 2 +- plugins/ilert/package.json | 2 +- plugins/jenkins/package.json | 2 +- plugins/kafka/package.json | 2 +- plugins/kubernetes/package.json | 2 +- plugins/lighthouse/package.json | 2 +- plugins/newrelic/package.json | 2 +- plugins/org/package.json | 2 +- plugins/pagerduty/package.json | 2 +- plugins/register-component/package.json | 2 +- plugins/rollbar/package.json | 2 +- plugins/scaffolder-backend/package.json | 2 +- plugins/scaffolder/package.json | 2 +- plugins/search/package.json | 2 +- plugins/sentry/package.json | 2 +- plugins/shortcuts/package.json | 2 +- plugins/sonarqube/package.json | 2 +- plugins/splunk-on-call/package.json | 2 +- plugins/tech-radar/package.json | 2 +- plugins/techdocs/package.json | 2 +- plugins/todo-backend/package.json | 2 +- plugins/todo/package.json | 2 +- plugins/user-settings/package.json | 2 +- plugins/welcome/package.json | 2 +- yarn.lock | 203 +++++++++++------- 59 files changed, 184 insertions(+), 135 deletions(-) diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 04231a6a69..fdab4b4504 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -96,7 +96,7 @@ "http-errors": "^1.7.3", "jest": "^26.0.1", "mock-fs": "^4.13.0", - "msw": "^0.21.2", + "msw": "^0.29.0", "mysql2": "^2.2.5", "recursive-readdir": "^2.2.2", "supertest": "^6.1.3" diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index c366374bf5..655c88eff6 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -37,7 +37,7 @@ "devDependencies": { "@backstage/cli": "^0.7.0", "@types/jest": "^26.0.7", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 1eaeeb24b1..14a3f11f69 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -54,7 +54,7 @@ "@types/node": "^14.14.32", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.3" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index 9298a53ea4..c9944922d9 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -55,7 +55,7 @@ "@types/node": "^14.14.32", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.3" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 48706a5877..86088b22f7 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -54,7 +54,7 @@ "@types/prop-types": "^15.7.3", "@types/zen-observable": "^0.8.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.3" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index edc6acf110..f7f20f3aec 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -41,7 +41,7 @@ "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.21.2", + "msw": "^0.29.0", "cross-fetch": "^3.0.6" }, "files": [ diff --git a/packages/integration/package.json b/packages/integration/package.json index bd557d7ec8..076ba70aba 100644 --- a/packages/integration/package.json +++ b/packages/integration/package.json @@ -42,7 +42,7 @@ "@backstage/test-utils": "^0.1.13", "@types/jest": "^26.0.7", "@types/luxon": "^1.25.0", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index 6791d9c132..e183bf1430 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -38,7 +38,7 @@ "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", "@types/react": "^16.9", - "msw": "^0.21.3", + "msw": "^0.29.0", "react": "^16.12.0", "react-dom": "^16.12.0", "react-router": "6.0.0-beta.0", diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 4097175a51..935adda96c 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -62,7 +62,7 @@ "@types/node": "^14.14.32", "@types/swagger-ui-react": "^3.23.3", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index d9de7fbe27..3b79162624 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -42,7 +42,7 @@ "devDependencies": { "@backstage/cli": "^0.7.1", "@types/supertest": "^2.0.8", - "msw": "^0.20.5", + "msw": "^0.29.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 06d443c37d..7c3492992f 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -79,7 +79,7 @@ "@types/passport-saml": "^1.1.2", "@types/passport-strategy": "^0.2.35", "@types/xml2js": "^0.4.7", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/badges/package.json b/plugins/badges/package.json index a7b10ca69a..8717719f8f 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -45,7 +45,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index e74b3c0684..aef5ee05f5 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -49,7 +49,7 @@ "@types/node": "^14.14.32", "@types/recharts": "^1.8.15", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 83bb5dd802..48df5a948f 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -43,7 +43,7 @@ "@backstage/cli": "^0.7.1", "@backstage/test-utils": "^0.1.13", "@types/lodash": "^4.14.151", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 8e9f54504e..517738d317 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -72,7 +72,7 @@ "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", "@types/yup": "^0.29.8", - "msw": "^0.21.2", + "msw": "^0.29.0", "sqlite3": "^5.0.0", "supertest": "^6.1.3", "wait-for-expect": "^3.0.2" diff --git a/plugins/catalog-graphql/package.json b/plugins/catalog-graphql/package.json index 40eb6d3382..4e30d2d218 100644 --- a/plugins/catalog-graphql/package.json +++ b/plugins/catalog-graphql/package.json @@ -50,7 +50,7 @@ "@types/express": "^4.17.7", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", - "msw": "^0.21.2", + "msw": "^0.29.0", "supertest": "^6.1.3", "ts-node": "^9.1.1" }, diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 3374622f07..51aa1e5c13 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -65,7 +65,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index c9af3c75e8..4e6d4374d2 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2", + "msw": "^0.29.0", "react-test-renderer": "^16.13.1" }, "files": [ diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index ddbe2c5764..be89ae1422 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -65,7 +65,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2", + "msw": "^0.29.0", "react-test-renderer": "^16.13.1" }, "files": [ diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index 1425e07e95..eed2435a37 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -61,7 +61,7 @@ "@types/node": "^14.14.32", "@types/react-lazylog": "^4.5.0", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 46a02fdf27..43d0a3d01e 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/code-coverage-backend/package.json b/plugins/code-coverage-backend/package.json index e3aab9bd38..0d44ea3d12 100644 --- a/plugins/code-coverage-backend/package.json +++ b/plugins/code-coverage-backend/package.json @@ -39,7 +39,7 @@ "@backstage/cli": "^0.7.1", "@types/express-xml-bodyparser": "^0.3.2", "@types/supertest": "^2.0.8", - "msw": "^0.21.2", + "msw": "^0.29.0", "supertest": "^4.0.2", "xml2js": "^0.4.23" }, diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 9b0da3b72c..3eb6e1a36d 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -52,7 +52,7 @@ "@types/node": "^14.14.32", "@types/recharts": "^1.8.15", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index f733744b50..cc5d638713 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -45,7 +45,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index dd1e9536bf..faba0ddf6d 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -70,7 +70,7 @@ "@types/yup": "^0.29.8", "canvas": "^2.6.1", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index e19653ba89..1f3c5c16bb 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -40,7 +40,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/explore/package.json b/plugins/explore/package.json index 4783dc7681..c4a1ac59c0 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 29cf7d5969..4878f247c3 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index 400d793195..ffd68dcf7a 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -52,7 +52,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 19925d7282..269ee2432c 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -49,7 +49,7 @@ "@types/node": "^14.14.32", "@types/recharts": "^1.8.15", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index b01208a670..bbbbc23d7e 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -61,7 +61,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 52dc70d9aa..67cdadc223 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -48,7 +48,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index cdb0cfa18a..105d6ec2b4 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index 6ec5ae1168..c2baf9f720 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -55,7 +55,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2", + "msw": "^0.29.0", "react-router-dom": "6.0.0-beta.0" }, "files": [ diff --git a/plugins/graphql/package.json b/plugins/graphql/package.json index 9d278bceb6..d0d2eac259 100644 --- a/plugins/graphql/package.json +++ b/plugins/graphql/package.json @@ -48,7 +48,7 @@ "@backstage/cli": "^0.7.0", "@types/supertest": "^2.0.8", "eslint-plugin-graphql": "^4.0.0", - "msw": "^0.20.5", + "msw": "^0.29.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 4813dc2bbf..7dbe6d1469 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -48,7 +48,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index f9fb4e9a23..e2772ac118 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -59,7 +59,7 @@ "@types/node": "^14.14.32", "@types/testing-library__jest-dom": "^5.9.1", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 96e55372e6..4d243b268f 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -46,7 +46,7 @@ "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", "jest-when": "^3.1.0", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index c0c9217497..0e53e7febd 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -61,7 +61,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 5fd6eeaaea..300643cb19 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -58,7 +58,7 @@ "@types/node": "^14.14.32", "@types/react": "^16.9", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 483001913d..72619a4341 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -52,7 +52,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/org/package.json b/plugins/org/package.json index 58570d1dd9..22ca7beaa8 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -45,7 +45,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 2557748844..11cc330ab1 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -57,7 +57,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2", + "msw": "^0.29.0", "node-fetch": "^2.6.1" }, "files": [ diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index af01167e2c..35d1310200 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -56,7 +56,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index 18574ca1d3..f94a72c703 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -60,7 +60,7 @@ "@types/node": "^14.14.32", "@types/react": "^16.9", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index f3e76720af..22979d42e8 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -72,7 +72,7 @@ "@types/supertest": "^2.0.8", "jest-when": "^3.1.0", "mock-fs": "^4.13.0", - "msw": "^0.21.2", + "msw": "^0.29.0", "supertest": "^6.1.3", "yaml": "^1.10.0" }, diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 1afa6241fc..1506b25fa2 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -74,7 +74,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/search/package.json b/plugins/search/package.json index 4c43b55f38..4ae6a778fa 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -60,7 +60,7 @@ "@types/node": "^14.14.32", "@types/react": "^16.9", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 7f8c838aec..465ed115b7 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -59,7 +59,7 @@ "@types/node": "^14.14.32", "@types/react": "^16.9", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 2fad8c4929..2a957572b9 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -46,7 +46,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index b86f51c66c..4f37b05d67 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -58,7 +58,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 510a9620bd..4a6257184a 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -57,7 +57,7 @@ "@types/luxon": "^1.25.0", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2", + "msw": "^0.29.0", "node-fetch": "^2.6.1" }, "configSchema": "config.d.ts", diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 824c8ba211..539a1ee3c5 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -57,7 +57,7 @@ "@types/node": "^14.14.32", "@types/react": "^16.9", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index e7f5f914bc..0f1931b736 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -65,7 +65,7 @@ "@types/react": "^16.9", "canvas": "^2.6.1", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist", diff --git a/plugins/todo-backend/package.json b/plugins/todo-backend/package.json index d08dc3dcae..3e2c87a5eb 100644 --- a/plugins/todo-backend/package.json +++ b/plugins/todo-backend/package.json @@ -41,7 +41,7 @@ "devDependencies": { "@backstage/cli": "^0.7.1", "@types/supertest": "^2.0.8", - "msw": "^0.21.2", + "msw": "^0.29.0", "supertest": "^6.1.3" }, "files": [ diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 34f381f340..291da759e5 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -49,7 +49,7 @@ "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.21.2", + "msw": "^0.29.0", "cross-fetch": "^3.0.6" }, "files": [ diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 97a88f5150..09a177c659 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -53,7 +53,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index df0ceeecf4..067d557a7e 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -52,7 +52,7 @@ "@types/jest": "^26.0.7", "@types/node": "^14.14.32", "cross-fetch": "^3.0.6", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/yarn.lock b/yarn.lock index 10a960a9ca..2da60f7a45 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1417,7 +1417,7 @@ dependencies: "@backstage/catalog-client" "^0.3.13" "@backstage/catalog-model" "^0.8.3" - "@backstage/core" "^0.7.13" + "@backstage/core-components" "^0.1.3" "@backstage/core-plugin-api" "^0.1.2" "@backstage/errors" "^0.1.1" "@backstage/integration" "^0.5.6" @@ -3606,6 +3606,25 @@ call-me-maybe "^1.0.1" glob-to-regexp "^0.3.0" +"@mswjs/cookies@^0.1.5": + version "0.1.6" + resolved "https://registry.npmjs.org/@mswjs/cookies/-/cookies-0.1.6.tgz#176f77034ab6d7373ae5c94bcbac36fee8869249" + integrity sha512-A53XD5TOfwhpqAmwKdPtg1dva5wrng2gH5xMvklzbd9WLTSVU953eCRa8rtrrm6G7Cy60BOGsBRN89YQK0mlKA== + dependencies: + "@types/set-cookie-parser" "^2.4.0" + set-cookie-parser "^2.4.6" + +"@mswjs/interceptors@^0.10.0": + version "0.10.0" + resolved "https://registry.npmjs.org/@mswjs/interceptors/-/interceptors-0.10.0.tgz#f5aad03c2c0591d164e3ed178b21942f1c2f8061" + integrity sha512-/M0GGpid5q2EDI+Keas1sLYF3VZFXHDE5gCmX/jHdp+OJFruVNca3PUk7A8KnGdPpuycZogdPsmRBSOXwjyA7A== + dependencies: + "@open-draft/until" "^1.0.3" + debug "^4.3.0" + headers-utils "^3.0.2" + strict-event-emitter "^0.2.0" + xmldom "^0.6.0" + "@n1ru4l/push-pull-async-iterable-iterator@^2.0.1": version "2.1.2" resolved "https://registry.npmjs.org/@n1ru4l/push-pull-async-iterable-iterator/-/push-pull-async-iterable-iterator-2.1.2.tgz#e486bf86c4c29e78601694a26f31c2dec0c08d9b" @@ -5884,6 +5903,11 @@ resolved "https://registry.npmjs.org/@types/js-cookie/-/js-cookie-2.2.6.tgz#f1a1cb35aff47bc5cfb05cb0c441ca91e914c26f" integrity sha512-+oY0FDTO2GYKEV0YPvSshGq9t7YozVkgvXLty7zogQNuCxBhT9/3INX9Q7H1aRZ4SUDRXAKlJuA4EA5nTt7SNw== +"@types/js-levenshtein@^1.1.0": + version "1.1.0" + resolved "https://registry.npmjs.org/@types/js-levenshtein/-/js-levenshtein-1.1.0.tgz#9541eec4ad6e3ec5633270a3a2b55d981edc44a9" + integrity sha512-14t0v1ICYRtRVcHASzes0v/O+TIeASb8aD55cWF1PidtInhFWSXcmhzhHqGjUWf9SUq1w70cvd1cWKUULubAfQ== + "@types/js-yaml@^3.12.1": version "3.12.5" resolved "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-3.12.5.tgz#136d5e6a57a931e1cce6f9d8126aa98a9c92a6bb" @@ -6480,6 +6504,13 @@ "@types/express-serve-static-core" "*" "@types/mime" "*" +"@types/set-cookie-parser@^2.4.0": + version "2.4.0" + resolved "https://registry.npmjs.org/@types/set-cookie-parser/-/set-cookie-parser-2.4.0.tgz#10cc0446bad372827671a5195fbd14ebce4a9baf" + integrity sha512-w7BFUq81sy7H/0jN0K5cax8MwRN6NOSURpY4YuO4+mOgoicxCZ33BUYz+gyF/sUf7uDl2We2yGJfppxzEXoAXQ== + dependencies: + "@types/node" "*" + "@types/sinonjs__fake-timers@^6.0.2": version "6.0.2" resolved "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.2.tgz#3a84cf5ec3249439015e14049bd3161419bf9eae" @@ -9199,7 +9230,7 @@ chalk@^3.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chalk@^4.0.0, chalk@^4.1.0: +chalk@^4.0.0, chalk@^4.1.0, chalk@^4.1.1: version "4.1.1" resolved "https://registry.npmjs.org/chalk/-/chalk-4.1.1.tgz#c80b3fab28bf6371e6863325eee67e618b77e6ad" integrity sha512-diHzdDKxcU+bAsUboHLPEDQiw0qEe0qd7SYUn3HgcFlWgbDcfLGswOHYeGrHKzG9z6UYf01d9VFMfZxPM1xZSg== @@ -10988,7 +11019,7 @@ debug@4, debug@4.3.2, debug@^4.0.0, debug@^4.0.1, debug@^4.1.0, debug@^4.1.1, de dependencies: ms "2.1.2" -debug@4.3.1: +debug@4.3.1, debug@^4.3.0: version "4.3.1" resolved "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz#f0d229c505e0c6d8c49ac553d1b13dc183f6b2ee" integrity sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ== @@ -12490,6 +12521,11 @@ events@3.1.0, events@^3.0.0: resolved "https://registry.npmjs.org/events/-/events-3.1.0.tgz#84279af1b34cb75aa88bf5ff291f6d0bd9b31a59" integrity sha512-Rv+u8MLHNOdMjTAFeT3nCjHn2aGlx435FP/sDHNaRhDEMwyI/aB22Kj2qIN8R0cw3z28psEQLYwxVKLsKrMgWg== +events@^3.3.0: + version "3.3.0" + resolved "https://registry.npmjs.org/events/-/events-3.3.0.tgz#31a95ad0a924e2d2c419a813aeb2c4e878ea7400" + integrity sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q== + eventsource@^1.0.5, eventsource@^1.0.7: version "1.0.7" resolved "https://registry.npmjs.org/eventsource/-/eventsource-1.0.7.tgz#8fbc72c93fcd34088090bc0a4e64f4b5cee6d8d0" @@ -14241,11 +14277,16 @@ graphql-ws@^4.4.1: resolved "https://registry.npmjs.org/graphql-ws/-/graphql-ws-4.7.0.tgz#b323fbf35a3736eed85dac24c0054d6d10c93e62" integrity sha512-Md8SsmC9ZlsogFPd3Ot8HbIAAqsHh8Xoq7j4AmcIat1Bh6k91tjVyQvA0Au1/BolXSYq+RDvib6rATU2Hcf1Xw== -graphql@15.5.0, graphql@^15.3.0: +graphql@15.5.0: version "15.5.0" resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.0.tgz#39d19494dbe69d1ea719915b578bf920344a69d5" integrity sha512-OmaM7y0kaK31NKG31q4YbD2beNYa6jBBKtMFT6gLYJljHLJr42IqJ8KX08u3Li/0ifzTU5HjmoOOrwa5BRLeDA== +graphql@^15.3.0, graphql@^15.4.0: + version "15.5.1" + resolved "https://registry.npmjs.org/graphql/-/graphql-15.5.1.tgz#f2f84415d8985e7b84731e7f3536f8bb9d383aad" + integrity sha512-FeTRX67T3LoE3LWAxxOlW2K3Bz+rMYAC18rRguK4wgXaTZMiJwSUwDmPFo3UadAKbzirKIg5Qy+sNJXbpPRnQw== + growl@1.9.2: version "1.9.2" resolved "https://registry.npmjs.org/growl/-/growl-1.9.2.tgz#0ea7743715db8d8de2c5ede1775e1b45ac85c02f" @@ -14467,10 +14508,10 @@ header-case@^2.0.4: capital-case "^1.0.4" tslib "^2.0.3" -headers-utils@^1.2.0: - version "1.2.0" - resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-1.2.0.tgz#5e10d1bc9d2bccf789547afca5b991a3167241e8" - integrity sha512-4/BMXcWrJErw7JpM87gF8MNEXcIMLzepYZjNRv/P9ctgupl2Ywa3u1PgHtNhSRq84bHH9Ndlkdy7bSi+bZ9I9A== +headers-utils@^3.0.2: + version "3.0.2" + resolved "https://registry.npmjs.org/headers-utils/-/headers-utils-3.0.2.tgz#dfc65feae4b0e34357308aefbcafa99c895e59ef" + integrity sha512-xAxZkM1dRyGV2Ou5bzMxBPNLoRCjcX+ya7KSWybQD2KwLphxsapUVK6x/02o7f4VU6GPSXch9vNY2+gkU8tYWQ== helmet@^4.0.0: version "4.4.1" @@ -15152,6 +15193,26 @@ inquirer@^7.0.0, inquirer@^7.0.4, inquirer@^7.3.3: strip-ansi "^6.0.0" through "^2.3.6" +inquirer@^8.1.0: + version "8.1.1" + resolved "https://registry.npmjs.org/inquirer/-/inquirer-8.1.1.tgz#7c53d94c6d03011c7bb2a947f0dca3b98246c26a" + integrity sha512-hUDjc3vBkh/uk1gPfMAD/7Z188Q8cvTGl0nxwaCdwSbzFh6ZKkZh+s2ozVxbE5G9ZNRyeY0+lgbAIOUFsFf98w== + dependencies: + ansi-escapes "^4.2.1" + chalk "^4.1.1" + cli-cursor "^3.1.0" + cli-width "^3.0.0" + external-editor "^3.0.3" + figures "^3.0.0" + lodash "^4.17.21" + mute-stream "0.0.8" + ora "^5.3.0" + run-async "^2.4.0" + rxjs "^6.6.6" + string-width "^4.1.0" + strip-ansi "^6.0.0" + through "^2.3.6" + internal-ip@^4.3.0: version "4.3.0" resolved "https://registry.npmjs.org/internal-ip/-/internal-ip-4.3.0.tgz#845452baad9d2ca3b69c635a137acb9a0dad0907" @@ -16484,6 +16545,11 @@ js-file-download@^0.4.12: resolved "https://registry.npmjs.org/js-file-download/-/js-file-download-0.4.12.tgz#10c70ef362559a5b23cdbdc3bd6f399c3d91d821" integrity sha512-rML+NkoD08p5Dllpjo0ffy4jRHeY6Zsapvr/W86N7E0yuzAO6qa5X9+xog6zQNlH102J7IXljNY2FtS6Lj3ucg== +js-levenshtein@^1.1.6: + version "1.1.6" + resolved "https://registry.npmjs.org/js-levenshtein/-/js-levenshtein-1.1.6.tgz#c6cee58eb3550372df8deb85fad5ce66ce01d59d" + integrity sha512-X2BB11YZtrRqY4EnQcLX5Rh373zbK4alC1FW7D7MBhL2gtcC17cTnr6DmfHZeS0s2rTHjUTMMHfG7gO8SSdw+g== + js-string-escape@1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/js-string-escape/-/js-string-escape-1.0.1.tgz#e2625badbc0d67c7533e9edc1068c587ae4137ef" @@ -18729,58 +18795,30 @@ msal@^1.0.2: dependencies: tslib "^1.9.3" -msw@^0.20.5: - version "0.20.5" - resolved "https://registry.npmjs.org/msw/-/msw-0.20.5.tgz#b6141080c0d8b17c451d9ca36c28cc47b4ac487a" - integrity sha512-hmEsey5BbVicMGt7aOh/GZ9ltga5N3tK6NiJXnbfCkAGKgnAVnjASr3i7Z+sWlZyY5uuMUFyLCEcqrlXxC6qIA== +msw@^0.29.0: + version "0.29.0" + resolved "https://registry.npmjs.org/msw/-/msw-0.29.0.tgz#7242d575cb01db0c925241587df1fc2b79230d78" + integrity sha512-C/wz1d5uAEZRvAPAYrXG1rwLxXl0+BOs+JPrCzasoABZW3ATwS6ifSze+/DAgA93e9M86RXwvy6yDtZeZWmCFQ== dependencies: + "@mswjs/cookies" "^0.1.5" + "@mswjs/interceptors" "^0.10.0" "@open-draft/until" "^1.0.3" "@types/cookie" "^0.4.0" - chalk "^4.1.0" - cookie "^0.4.1" - graphql "^15.3.0" - headers-utils "^1.2.0" - node-fetch "^2.6.0" - node-match-path "^0.4.4" - node-request-interceptor "^0.3.5" - statuses "^2.0.0" - yargs "^15.4.1" - -msw@^0.21.2: - version "0.21.2" - resolved "https://registry.npmjs.org/msw/-/msw-0.21.2.tgz#74ed10b8eb224325652a3c3812b5460dac297bd8" - integrity sha512-XOJehxtJThNFdMJdVjxDAbZ8KuC3UltOlO5nQDks0Q1yzSUqqKcVUjbKrH7T+K2hckBr0KEY2fwJHv21R4BV2A== - dependencies: - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.0" - chalk "^4.1.0" + "@types/inquirer" "^7.3.1" + "@types/js-levenshtein" "^1.1.0" + chalk "^4.1.1" chokidar "^3.4.2" cookie "^0.4.1" - graphql "^15.3.0" - headers-utils "^1.2.0" + graphql "^15.4.0" + headers-utils "^3.0.2" + inquirer "^8.1.0" + js-levenshtein "^1.1.6" node-fetch "^2.6.1" - node-match-path "^0.4.4" - node-request-interceptor "^0.5.1" + node-match-path "^0.6.3" statuses "^2.0.0" - yargs "^16.0.3" - -msw@^0.21.3: - version "0.21.3" - resolved "https://registry.npmjs.org/msw/-/msw-0.21.3.tgz#d073842f9570a08f4041806a2c7303a9b8494602" - integrity sha512-voPc/EJsjarvi454vSEuozZQQqLG4AUHT6qQL5Ah47lq7sGCpc7icByeUlfvEj5+MvaugN0c7JwXyCa2rxu8cA== - dependencies: - "@open-draft/until" "^1.0.3" - "@types/cookie" "^0.4.0" - chalk "^4.1.0" - chokidar "^3.4.2" - cookie "^0.4.1" - graphql "^15.3.0" - headers-utils "^1.2.0" - node-fetch "^2.6.1" - node-match-path "^0.4.4" - node-request-interceptor "^0.5.1" - statuses "^2.0.0" - yargs "^16.0.3" + strict-event-emitter "^0.2.0" + type-fest "^1.1.3" + yargs "^17.0.1" multicast-dns-service-types@^1.1.0: version "1.1.0" @@ -19108,10 +19146,10 @@ node-libs-browser@^2.2.1: util "^0.11.0" vm-browserify "^1.0.1" -node-match-path@^0.4.4: - version "0.4.4" - resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.4.4.tgz#516a10926093c0cc6f237d020685b593b19baebb" - integrity sha512-pBq9gp7TG0r0VXuy/oeZmQsjBSnYQo7G886Ly/B3azRwZuEtHCY155dzmfoKWcDPGgyfIGD8WKVC7h3+6y7yTg== +node-match-path@^0.6.3: + version "0.6.3" + resolved "https://registry.npmjs.org/node-match-path/-/node-match-path-0.6.3.tgz#55dd8443d547f066937a0752dce462ea7dc27551" + integrity sha512-fB1reOHKLRZCJMAka28hIxCwQLxGmd7WewOCBDYKpyA1KXi68A7vaGgdZAPhY2E6SXoYt3KqYCCvXLJ+O0Fu/Q== node-modules-regexp@^1.0.0: version "1.0.0" @@ -19177,24 +19215,6 @@ node-releases@^1.1.71: resolved "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz#14802ab6b1039a79a0c7d662b610a5bbd76eacbe" integrity sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw== -node-request-interceptor@^0.3.5: - version "0.3.5" - resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.3.5.tgz#4b26159617829c9a70643012c0fdc3ae4c78ae43" - integrity sha512-lBwA3v00hxCW2xhhZHZ1ab5JMHoAnzgtbeaUXqTufRh7mpAG93ZOChj0btMDB1VZd+CKhCbtigsxCjmerKa2+w== - dependencies: - "@open-draft/until" "^1.0.3" - debug "^4.1.1" - headers-utils "^1.2.0" - -node-request-interceptor@^0.5.1: - version "0.5.1" - resolved "https://registry.npmjs.org/node-request-interceptor/-/node-request-interceptor-0.5.1.tgz#b4757a033bde4412d9ffc4503804abb28ed962d2" - integrity sha512-ex5mlI5nGokxocomS2Rj2r1aspmt7qZoI8OvKLt24ylp1bYCzGQ+0XD911guCNDb/kKLMIGC67HHyeFrJCz7jA== - dependencies: - "@open-draft/until" "^1.0.3" - debug "^4.1.1" - headers-utils "^1.2.0" - nodemon@^2.0.2: version "2.0.7" resolved "https://registry.npmjs.org/nodemon/-/nodemon-2.0.7.tgz#6f030a0a0ebe3ea1ba2a38f71bf9bab4841ced32" @@ -23171,6 +23191,13 @@ rxjs@^6.6.3: dependencies: tslib "^1.9.0" +rxjs@^6.6.6: + version "6.6.7" + resolved "https://registry.npmjs.org/rxjs/-/rxjs-6.6.7.tgz#90ac018acabf491bf65044235d5863c4dab804c9" + integrity sha512-hTdwr+7yYNIT5n4AMYp85KA6yw2Va0FLa3Rguvbpa4W3I5xynaBZo41cM3XM+4Q6fRMj3sBYIR1VAmZMXYJvRQ== + dependencies: + tslib "^1.9.0" + safe-buffer@5.1.1: version "5.1.1" resolved "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz#893312af69b2123def71f57889001671eeb2c853" @@ -23459,6 +23486,11 @@ set-blocking@^2.0.0, set-blocking@~2.0.0: resolved "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" integrity sha1-BF+XgtARrppoA93TgrJDkrPYkPc= +set-cookie-parser@^2.4.6: + version "2.4.8" + resolved "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.4.8.tgz#d0da0ed388bc8f24e706a391f9c9e252a13c58b2" + integrity sha512-edRH8mBKEWNVIVMKejNnuJxleqYE/ZSdcT8/Nem9/mmosx12pctd80s2Oy00KNZzrogMZS5mauK2/ymL1bvlvg== + set-harmonic-interval@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/set-harmonic-interval/-/set-harmonic-interval-1.0.1.tgz#e1773705539cdfb80ce1c3d99e7f298bb3995249" @@ -24254,6 +24286,13 @@ streamsearch@0.1.2, streamsearch@~0.1.2: resolved "https://registry.npmjs.org/streamsearch/-/streamsearch-0.1.2.tgz#808b9d0e56fc273d809ba57338e929919a1a9f1a" integrity sha1-gIudDlb8Jz2Am6VzOOkpkZoanxo= +strict-event-emitter@^0.2.0: + version "0.2.0" + resolved "https://registry.npmjs.org/strict-event-emitter/-/strict-event-emitter-0.2.0.tgz#78e2f75dc6ea502e5d8a877661065a1e2deedecd" + integrity sha512-zv7K2egoKwkQkZGEaH8m+i2D0XiKzx5jNsiSul6ja2IYFvil10A59Z9Y7PPAAe5OW53dQUf9CfsHKzjZzKkm1w== + dependencies: + events "^3.3.0" + strict-uri-encode@^1.0.0: version "1.1.0" resolved "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-1.1.0.tgz#279b225df1d582b1f54e65addd4352e18faa0713" @@ -25572,6 +25611,11 @@ type-fest@^0.8.1: resolved "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz#09e249ebde851d3b1e48d27c105444667f17b83d" integrity sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA== +type-fest@^1.1.3: + version "1.2.1" + resolved "https://registry.npmjs.org/type-fest/-/type-fest-1.2.1.tgz#232990aa513f3f5223abf54363975dfe3a121a2e" + integrity sha512-SbmIRuXhJs8KTneu77Ecylt9zuqL683tuiLYpTRil4H++eIhqCmx6ko6KAFem9dty8sOdnEiX7j4K1nRE628fQ== + type-is@^1.6.16, type-is@~1.6.17, type-is@~1.6.18: version "1.6.18" resolved "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -26881,6 +26925,11 @@ xmldom@0.4.x: resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.4.0.tgz#8771e482a333af44587e30ce026f0998c23f3830" integrity sha512-2E93k08T30Ugs+34HBSTQLVtpi6mCddaY8uO+pMNk1pqSjV5vElzn4mmh6KLxN3hki8rNcHSYzILoh3TEWORvA== +xmldom@^0.6.0: + version "0.6.0" + resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.6.0.tgz#43a96ecb8beece991cef382c08397d82d4d0c46f" + integrity sha512-iAcin401y58LckRZ0TkI4k0VSM1Qg0KGSc3i8rU+xrxe19A/BN1zHyVSJY7uoutVlaTSzYyk/v5AmkewAP7jtg== + xmldom@~0.1.15: version "0.1.31" resolved "https://registry.npmjs.org/xmldom/-/xmldom-0.1.31.tgz#b76c9a1bd9f0a9737e5a72dc37231cf38375e2ff" @@ -27013,7 +27062,7 @@ yargs@^15.1.0, yargs@^15.3.1, yargs@^15.4.1: y18n "^4.0.0" yargs-parser "^18.1.2" -yargs@^16.0.3, yargs@^16.1.1, yargs@^16.2.0: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== @@ -27026,7 +27075,7 @@ yargs@^16.0.3, yargs@^16.1.1, yargs@^16.2.0: y18n "^5.0.5" yargs-parser "^20.2.2" -yargs@^17.0.0: +yargs@^17.0.0, yargs@^17.0.1: version "17.0.1" resolved "https://registry.npmjs.org/yargs/-/yargs-17.0.1.tgz#6a1ced4ed5ee0b388010ba9fd67af83b9362e0bb" integrity sha512-xBBulfCc8Y6gLFcrPvtqKz9hz8SO0l1Ni8GgDekvBX2ro0HRQImDGnikfc33cgzcYUSncapnNcZDjVFIH3f6KQ== From 9458ea5f5dff2f2b459b4fa1b3faadc9fae4601c Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Jun 2021 11:07:13 +0200 Subject: [PATCH 20/89] chore: fixing tests with new breaking change Signed-off-by: blam --- packages/backend-common/src/reading/AzureUrlReader.test.ts | 2 +- packages/backend-common/src/reading/GitlabUrlReader.test.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/backend-common/src/reading/AzureUrlReader.test.ts b/packages/backend-common/src/reading/AzureUrlReader.test.ts index bcbcc19483..2b7c21d4ab 100644 --- a/packages/backend-common/src/reading/AzureUrlReader.test.ts +++ b/packages/backend-common/src/reading/AzureUrlReader.test.ts @@ -61,7 +61,7 @@ describe('AzureUrlReader', () => { ctx.status(200), ctx.json({ url: req.url.toString(), - headers: req.headers.getAllHeaders(), + headers: req.headers.all(), }), ), ), diff --git a/packages/backend-common/src/reading/GitlabUrlReader.test.ts b/packages/backend-common/src/reading/GitlabUrlReader.test.ts index 5971264545..a76b898aaf 100644 --- a/packages/backend-common/src/reading/GitlabUrlReader.test.ts +++ b/packages/backend-common/src/reading/GitlabUrlReader.test.ts @@ -90,7 +90,7 @@ describe('GitlabUrlReader', () => { ctx.status(200), ctx.json({ url: req.url.toString(), - headers: req.headers.getAllHeaders(), + headers: req.headers.all(), }), ), ), From a2f722a6df8f5090caffae4173d8508a28c3aa00 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Jun 2021 11:11:22 +0200 Subject: [PATCH 21/89] chore: added changeset Signed-off-by: blam --- .changeset/nice-donkeys-search.md | 62 +++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 .changeset/nice-donkeys-search.md diff --git a/.changeset/nice-donkeys-search.md b/.changeset/nice-donkeys-search.md new file mode 100644 index 0000000000..ce66fc6282 --- /dev/null +++ b/.changeset/nice-donkeys-search.md @@ -0,0 +1,62 @@ +--- +'@backstage/backend-common': patch +'@backstage/catalog-client': patch +'@backstage/core-api': patch +'@backstage/core-app-api': patch +'@backstage/core-plugin-api': patch +'@backstage/integration': patch +'@backstage/integration-react': patch +'@backstage/test-utils': patch +'@backstage/plugin-api-docs': patch +'@backstage/plugin-app-backend': patch +'@backstage/plugin-auth-backend': patch +'@backstage/plugin-badges': patch +'@backstage/plugin-bitrise': patch +'@backstage/plugin-catalog': patch +'@backstage/plugin-catalog-backend': patch +'@backstage/plugin-catalog-backend-module-msgraph': patch +'@backstage/plugin-catalog-graphql': patch +'@backstage/plugin-catalog-import': patch +'@backstage/plugin-catalog-react': patch +'@backstage/plugin-circleci': patch +'@backstage/plugin-cloudbuild': patch +'@backstage/plugin-code-coverage': patch +'@backstage/plugin-code-coverage-backend': patch +'@backstage/plugin-config-schema': patch +'@backstage/plugin-cost-insights': patch +'@backstage/plugin-explore': patch +'@backstage/plugin-explore-react': patch +'@backstage/plugin-fossa': patch +'@backstage/plugin-gcp-projects': patch +'@backstage/plugin-git-release-manager': patch +'@backstage/plugin-github-actions': patch +'@backstage/plugin-github-deployments': patch +'@backstage/plugin-gitops-profiles': patch +'@backstage/plugin-graphiql': patch +'@backstage/plugin-graphql-backend': patch +'@backstage/plugin-ilert': patch +'@backstage/plugin-jenkins': patch +'@backstage/plugin-kafka': patch +'@backstage/plugin-kubernetes': patch +'@backstage/plugin-lighthouse': patch +'@backstage/plugin-newrelic': patch +'@backstage/plugin-org': patch +'@backstage/plugin-pagerduty': patch +'@backstage/plugin-register-component': patch +'@backstage/plugin-rollbar': patch +'@backstage/plugin-scaffolder': patch +'@backstage/plugin-scaffolder-backend': patch +'@backstage/plugin-search': patch +'@backstage/plugin-sentry': patch +'@backstage/plugin-shortcuts': patch +'@backstage/plugin-sonarqube': patch +'@backstage/plugin-splunk-on-call': patch +'@backstage/plugin-tech-radar': patch +'@backstage/plugin-techdocs': patch +'@backstage/plugin-todo': patch +'@backstage/plugin-todo-backend': patch +'@backstage/plugin-user-settings': patch +'@backstage/plugin-welcome': patch +--- + +chore: bump `msw` dependency to 0.29.0 From 0a09dbe0879172248876849b1ef5ed2c856505b5 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Jun 2021 11:12:48 +0200 Subject: [PATCH 22/89] chore: fix dependencies in `create-app` Signed-off-by: blam --- packages/cli/templates/default-backend-plugin/package.json.hbs | 2 +- packages/cli/templates/default-plugin/package.json.hbs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/cli/templates/default-backend-plugin/package.json.hbs b/packages/cli/templates/default-backend-plugin/package.json.hbs index d041773d3c..8275223f49 100644 --- a/packages/cli/templates/default-backend-plugin/package.json.hbs +++ b/packages/cli/templates/default-backend-plugin/package.json.hbs @@ -36,7 +36,7 @@ "@backstage/cli": "^{{version '@backstage/cli'}}", "@types/supertest": "^2.0.8", "supertest": "^4.0.2", - "msw": "^0.21.2" + "msw": "^0.29.0" }, "files": [ "dist" diff --git a/packages/cli/templates/default-plugin/package.json.hbs b/packages/cli/templates/default-plugin/package.json.hbs index f5249e7918..dbba628055 100644 --- a/packages/cli/templates/default-plugin/package.json.hbs +++ b/packages/cli/templates/default-plugin/package.json.hbs @@ -44,7 +44,7 @@ "@testing-library/user-event": "^13.1.8", "@types/jest": "^26.0.7", "@types/node": "^14.14.32", - "msw": "^0.21.2", + "msw": "^0.29.0", "cross-fetch": "^3.0.6" }, "files": [ From 04248b8f9afb06500236e2246b0f8d673518ebb9 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Jun 2021 11:14:06 +0200 Subject: [PATCH 23/89] chore: added changeset for bump of cli Signed-off-by: blam --- .changeset/many-mugs-bow.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/many-mugs-bow.md diff --git a/.changeset/many-mugs-bow.md b/.changeset/many-mugs-bow.md new file mode 100644 index 0000000000..39614b044a --- /dev/null +++ b/.changeset/many-mugs-bow.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +chore: bump `msw` dependency in `create-plugin` From dda7e862a2bccc48fc42a51bc732cc615809ef90 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Wed, 23 Jun 2021 15:13:02 +0200 Subject: [PATCH 24/89] remove the "catch-all" auth endpoint and register each separately 200: for properly registered auth providers 404 (with proper message): misconfigured 404 (plain): for unknown providers Co-authored-by: Mike Lewis Signed-off-by: Himanshu Mishra --- plugins/auth-backend/src/service/router.ts | 107 +++++++++++---------- 1 file changed, 55 insertions(+), 52 deletions(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 548c551150..1945e6cb2d 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -86,47 +86,64 @@ export async function createRouter({ ...providerFactories, }; const providersConfig = config.getConfig('auth.providers'); - const providers = providersConfig.keys(); + const configuredProviders = providersConfig.keys(); - for (const providerId of providers) { - logger.info(`Configuring provider, ${providerId}`); - try { - const providerFactory = allProviderFactories[providerId]; - if (!providerFactory) { - throw Error(`No auth provider available for '${providerId}'`); + for (const providerId of Object.keys(allProviderFactories)) { + if (configuredProviders.includes(providerId)) { + logger.info(`Configuring provider, ${providerId}`); + try { + const providerFactory = allProviderFactories[providerId]; + if (!providerFactory) { + throw Error(`No auth provider available for '${providerId}'`); + } + + const provider = providerFactory({ + providerId, + globalConfig: { baseUrl: authUrl, appUrl }, + config: providersConfig.getConfig(providerId), + logger, + tokenIssuer, + discovery, + catalogApi, + }); + + const r = Router(); + + r.get('/start', provider.start.bind(provider)); + r.get('/handler/frame', provider.frameHandler.bind(provider)); + r.post('/handler/frame', provider.frameHandler.bind(provider)); + if (provider.logout) { + r.post('/logout', provider.logout.bind(provider)); + } + if (provider.refresh) { + r.get('/refresh', provider.refresh.bind(provider)); + } + + router.use(`/${providerId}`, r); + } catch (e) { + if (process.env.NODE_ENV !== 'development') { + throw new Error( + `Failed to initialize ${providerId} auth provider, ${e.message}`, + ); + } + + logger.warn(`Skipping ${providerId} auth provider, ${e.message}`); + + router.use(`/${providerId}`, () => { + // If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found. + throw new NotFoundError( + `Auth provider registered for '${providerId}' is misconfigured. This could mean the configs under ` + + `auth.providers.${providerId} are missing or the environment variables used are not defined. ` + + `Check the auth backend plugin logs when the backend starts to see more details.`, + ); + }); } - - const provider = providerFactory({ - providerId, - globalConfig: { baseUrl: authUrl, appUrl }, - config: providersConfig.getConfig(providerId), - logger, - tokenIssuer, - discovery, - catalogApi, - }); - - const r = Router(); - - r.get('/start', provider.start.bind(provider)); - r.get('/handler/frame', provider.frameHandler.bind(provider)); - r.post('/handler/frame', provider.frameHandler.bind(provider)); - if (provider.logout) { - r.post('/logout', provider.logout.bind(provider)); - } - if (provider.refresh) { - r.get('/refresh', provider.refresh.bind(provider)); - } - - router.use(`/${providerId}`, r); - } catch (e) { - if (process.env.NODE_ENV !== 'development') { - throw new Error( - `Failed to initialize ${providerId} auth provider, ${e.message}`, + } else { + router.use(`/${providerId}`, () => { + throw new NotFoundError( + `No auth provider registered for '${providerId}'`, ); - } - - logger.warn(`Skipping ${providerId} auth provider, ${e.message}`); + }); } } @@ -137,19 +154,5 @@ export async function createRouter({ }), ); - router.use('/:provider/', req => { - const { provider } = req.params; - if (providers.includes(provider)) { - // If the user added the provider under auth.providers but the clientId and clientSecret etc. were not found. - throw new NotFoundError( - `Auth provider registered for '${provider}' is misconfigured. This could mean the configs under ` + - `auth.providers.${provider} are missing or the environment variables used are not defined. ` + - `Check the auth backend plugin logs when the backend starts to see more details.`, - ); - } else { - throw new NotFoundError(`No auth provider registered for '${provider}'`); - } - }); - return router; } From ce41c7cc9b9b2b95e86f737be1601a29638ffee2 Mon Sep 17 00:00:00 2001 From: blam Date: Wed, 23 Jun 2021 15:18:59 +0200 Subject: [PATCH 25/89] chore: remove the changeset for the devDep bumping - it's not needed Signed-off-by: blam --- .changeset/nice-donkeys-search.md | 62 ------------------------------- 1 file changed, 62 deletions(-) delete mode 100644 .changeset/nice-donkeys-search.md diff --git a/.changeset/nice-donkeys-search.md b/.changeset/nice-donkeys-search.md deleted file mode 100644 index ce66fc6282..0000000000 --- a/.changeset/nice-donkeys-search.md +++ /dev/null @@ -1,62 +0,0 @@ ---- -'@backstage/backend-common': patch -'@backstage/catalog-client': patch -'@backstage/core-api': patch -'@backstage/core-app-api': patch -'@backstage/core-plugin-api': patch -'@backstage/integration': patch -'@backstage/integration-react': patch -'@backstage/test-utils': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-app-backend': patch -'@backstage/plugin-auth-backend': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-bitrise': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch -'@backstage/plugin-catalog-graphql': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-code-coverage': patch -'@backstage/plugin-code-coverage-backend': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-explore-react': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-gcp-projects': patch -'@backstage/plugin-git-release-manager': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-gitops-profiles': patch -'@backstage/plugin-graphiql': patch -'@backstage/plugin-graphql-backend': patch -'@backstage/plugin-ilert': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-newrelic': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-register-component': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-shortcuts': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-todo-backend': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-welcome': patch ---- - -chore: bump `msw` dependency to 0.29.0 From 5a4e520d449351d2891239cb538dae66d1b222b4 Mon Sep 17 00:00:00 2001 From: Jeff Feng Date: Wed, 23 Jun 2021 20:04:37 -0400 Subject: [PATCH 26/89] Search blog post and image assets Draft copy and 3 image assets announcing the Backstage Search platform Signed-off-by: Jeff Feng --- ...24-announcing-backstage-search-platform.md | 101 ++++++++++++++++++ .../21-06-24/backstage-search-platform.png | Bin 0 -> 276972 bytes .../blog/assets/21-06-24/jobs-to-be-done.png | Bin 0 -> 549039 bytes .../blog/assets/21-06-24/search-results.png | Bin 0 -> 118514 bytes 4 files changed, 101 insertions(+) create mode 100644 microsite/blog/2021-06-24-announcing-backstage-search-platform.md create mode 100644 microsite/blog/assets/21-06-24/backstage-search-platform.png create mode 100644 microsite/blog/assets/21-06-24/jobs-to-be-done.png create mode 100644 microsite/blog/assets/21-06-24/search-results.png diff --git a/microsite/blog/2021-06-24-announcing-backstage-search-platform.md b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md new file mode 100644 index 0000000000..0a4ae572c2 --- /dev/null +++ b/microsite/blog/2021-06-24-announcing-backstage-search-platform.md @@ -0,0 +1,101 @@ +--- +title: Announcing the Backstage Search platform: a customizable search tool built just for you +author: Emma Indal, Spotify +authorURL: https://www.linkedin.com/in/emma-indal +--- + +![Backstage Search platform](assets/21-06-24/backstage-search-platform.png) + +**TLDR;** The new Backstage Search is now available in alpha, ready for you to start building on. A total rethinking of the core search feature in Backstage, it’s more than just a box you type into — it’s a mini platform all by itself. With its composable frontend and extensible backend, you can design and build the search tool that suits your organization’s needs. + +So, you don’t just get an improved out-of-the-box experience for searching whatever is in your software catalog. You can also add support for searching other sources, too. Customize it the way you want and you can search your catalog, your plugins and docs — and even external sources, like Stack Overflow and Confluence — all at once, all right inside Backstage. + +With one query, your teams can find exactly what they’re looking for: anything and everything. + + + +## Search and explore + +Being able to easily explore your ecosystem — to discover software, tools, documentation, and other valuable knowledge — is one of [the three main jobs of Backstage](https://backstage.io/blog/2021/05/20/adopting-backstage#three-jobs-create-manage-explore). Teams should be able to find what other teams have already built, so they can reuse and contribute to components instead of unknowingly duplicating them. Data endpoints should be shared, not siloed away. Services and their APIs should be easily discoverable. Best practices and technical documentation should be easily found. + +Along with the [Backstage Service Catalog](https://backstage.io/blog/2020/06/22/backstage-service-catalog-alpha), Backstage Search is essential to enabling this discoverability — allowing new hires and old hands alike to explore your infrastructure instead of getting lost inside it. + +We also quickly realized that search looks different from organization to organization. Therefore, we built a search platform that lets you plug in your own search engine, index any information you like, or build a customized search page experience that fits your users’ needs. + +Since finding what you are looking for in Backstage is critical for success, we started by identifying the needs and goals of search. + +## Rethinking search, inside and out + +Spotify’s internal version of Backstage has had some of the features of Backstage Search for a while, and open sourcing them has been top of mind since day one. But we didn’t want to just port our internal version to the open source version. We wanted to take the opportunity to apply what we’ve learned inside Spotify over the last year, address the needs we’ve observed in the community, and ultimately open source not just a search feature but a search platform. We started the process by looking at the [jobs to be done](https://hbr.org/2016/09/know-your-customers-jobs-to-be-done). + +![Backstage Search platform](assets/21-06-24/jobs-to-be-done.png) +_A high-level overview of the process, identifying all the jobs of search._ + +First, we looked at which jobs to be done belonged to the search plugin itself (e.g., “collect documents to index”) and which belonged to the other plugins (e.g., “format documents for indexing”), and then whether those jobs belonged to the frontend (“display results”) or the backend (“schedule indexing”). + +Looking at all these various jobs, we defined four goals for the platform: + +- **Flexibility:** Be search engine–agnostic +- **Simplicity:** Make it easy for content owners to make their content searchable/discoverable +- **Control:** Allow plugin developers to customize their search results components +- **Reusability:** Offer reusable components/APIs that other devs can leverage + +Beginning our journey this way — by identifying the jobs to be done first, then defining the product goals from there — we could make sure that the search platform addressed real needs and improved the search experience for both users and plugin developers. + +This approach not only created a better search tool for the open source community, but for Spotify, as well. So, instead of just open sourcing our internal version of search, we ended up with an even better solution — one that we can all use and build on together, both inside and outside Spotify. + +## Say hello to the Backstage Search platform + +![Backstage Search platform](assets/21-06-24/search-results.png) + +We are now happy and proud to announce our alpha version of the [Backstage Search Platform](https://backstage.io/docs/features/search/architecture), featuring: + +- Bring your own search engine (Flexibility) +- Collators for easily indexing content from plugins and other sources (Simplicity) +- Composable search page experiences (Control, Reusability) +- Customize the look and feel of each search result (Control, Reusability) + +### Bring your own search engine + +By introducing a Search Integration Layer, we have been able to keep the query translation of the search term and filters close to the search engine itself. This makes our search backend less focused on how a set of terms and filters should be translated to fit a certain search engine interface and more focused on querying and retrieving results as well as collecting results to index. + +With the Search Integration Layer, your organization can bring your search engine of choice to Backstage — instead of relying on Backstage to support a specific search engine that might not fit the needs of your organization, either today or in the future. + +But that doesn’t mean “batteries not included”. The current version of Backstage Search ships with Lunr support built-in — and support for ElasticSearch is not very far off. And we hope the number of supported search engines will continue to grow with the community’s help. + +### Collators for easily indexing content from plugins and other sources + +Since Backstage’s functionality comes from its plugins, we wanted the process of making plugin content searchable to be as frictionless as possible. Therefore we decided on a concept we call collators. Collators are responsible for collecting documents to index from a plugin. Your collators live inside your own plugin, but are registered in the Backstage app’s search backend. + +Collators can also be used to index external sources, like Stack Overflow and Confluence. You can watch a demo of how easy it is to extend search with collators [here](https://youtu.be/Z78FFaObTfk?t=339). + +### Composable search page experiences + +Every engineering org has different needs — that is something we have definitely learned over the last year. Your software catalog might be set up differently than ours and therefore your needs for how search results look and how the search filters work will also differ. + +That's why we have put effort into making your search page experience composable to your organization's needs. What do we mean by that? When you adopt Backstage and set up your app, you can set up — or, compose — your search page by using existing components or by creating your own custom ones. + +### Customize the look and feel of each search result + +A good example of the level of customization the platform allows is how list items are displayed in search results. A search result component can be a list, this list can consist of different list items (search results returned from the search engine) — but these list items could look different depending on what the search result returns in terms of its fields. + +Let’s say that for an entity returned from the software catalog maybe the most important information to show is the name, while a result returned from the TechDocs plugin should maybe show the text content as the most important information. This can be customized by creating components (like TechDocsResultListItem or CatalogResultListItem or whatever list item component you want) and configuring them in the app. + +If there is no need to customize your search result list items, the component is there for you to reuse. + +## Getting started with Backstage Search + +We put together [a getting started guide](https://backstage.io/docs/features/search/getting-started) that provides two different ways to set up Backstage Search: + +- Create a new app and get the most out of the search setup right out of the box, or +- Add the new Backstage Search setup to your existing Backstage app. + +Whichever situation you’re in, we have you covered. + +## What’s next? + +We’ve built the foundation for the Backstage Search platform, and we can't wait to see the exciting engines, collators, and components the community builds on the platform. + +You can check out our [project roadmap](https://backstage.io/docs/features/search/search-overview#project-roadmap) in our search documentation or track the progress of our [Beta milestone](https://github.com/backstage/backstage/milestone/27) and [GA milestone](https://github.com/backstage/backstage/milestone/28). + +For any questions, feedback or ideas about the Backstage Search platform, join us in the #search channel on [Discord](https://discord.gg/MUpMjP2)! diff --git a/microsite/blog/assets/21-06-24/backstage-search-platform.png b/microsite/blog/assets/21-06-24/backstage-search-platform.png new file mode 100644 index 0000000000000000000000000000000000000000..bb7e828ea9b1b39715bcc276d83bfd3191b1318b GIT binary patch literal 276972 zcmeFacUV(*`#(;{Q`^T@+ghaxLIo8C0jr>8rd5Wbh-_uWDvIn11W3rRwJlYI6cwo; zL_t7C5(NT=kyx#QAVh=^S&7OB5FwBdLI_EI?{}1r&m)vSe*b;tx_X5slylB~?(rJ; zp{MunbNuMPtNyE@q4Ck~U3P978oE0)G(Nfb{(In^i+-|v@bbZTyADQcXslQV|9NY- z+uAYk=3CKjjyp6c&DulYU)~M+#`zl!jhn>9GvEG4<6VpIciVmA5&zbUo;}M?eXa5C z?;n|#K3eRwxzs`Q?3VHOMU7{Z@T=>_S0bXD-va;jJYUvb452jQoq2u&d~|5smL<03 zpgx-a$4)ZC=l=nXO+P-G|9nn22#%@6kZ2Wo%x-@G`)V`c8qs&Bt}lJmux-xnWGwKzkZ-6N%Xf%)Y+Ki8qm$jlk^do1FMF5-Z-( zmkHCzCOKuSVMzQ?nfPnkXF;{GS8DH zr?a}EdDih?NyS4Qul=pFZ{B*C0#|(J`MUl6Jq-3pGWGY%(Ib|+E!^Pc3>Ef%ZhIR^Y_5eUyO{TTDOYF?8K_wrf73CTb*J&wo7kUx7+ zMi=TL=3S{pJh@4FvVm&-Od8~LvE!Dtmh|n+mgSqS>@|I~oX}VP%ezsFt5UCa zg+%YGw}&FWJGp7(iLdh3cJCamtw|R@|LF4%0?jV%XYSF*D zvn9WMBzWCtzS|s$+?o}NQ8Ckw`T4ESjhVmgp7*!VuKYUh=NcM@ZR<3i+|Gy{{Y9tR zT$^mV?HapW^62~HjR^@kL@MtW+f`K=^ZtsW!UY}T@D4eB>xd`Y70VBMN0#x!U$~oLP+d^wf0n{8#enzg&sN zl^yoKOSt;z;7F+dya8%bzMd`B}^9Nj#kU4_+KJMqC^gxlp&QU)vTF3LrI=y!Hs%Gvevo#e?%*9Bd~J0OdW%k#dsbCTTq z`M9kP*?(~zKk56VmJS2=d9&te-GWX(^uVjr_iQZ|H4cj9OLM?wcSWG7Rkt9-YZfSEu>ZsoO0^PYMI>gPGLqs8qz_iEj-<9d+7{_mxGjmSc!V`og1bxI%$sa+D{2#J3@j1e0gr_?47~pIjPlf=Jc$=1xcW}vMYGD06y<0m z;S63ON7#w^$YJS!<~u?=Vh#fUD|F6hXE>Xra#OoX9m`)sIyg+#&WUk} zRtwuU;5CQ+^R3$=S>Jh=oP`Y7uRl7@wEo-}g}N4!ft%ihk;c2k;3=1ju7Ycv?r^Ma z2~S+=AeAD2S>d~_z^7FDV(2(1Rm;f}$QTS(Z{Dj6b^q5WT;HRy|38_ll3ElBO?AaP z|80)A8>8{6Z(^p4PY+Vu4RS#q?4wzTNrnr3u?~j&t(8 z!4@zK017C~SRcttO34!J3H^P>&tm%cV93N!{qYG7NSdbbYIoxf%;4AM{k1kNGjv@f zGxg87zvi2hon8y-us^*1_y=K4G@s5^cXT;Oge2okcjN1(+w7#$EMkOJ`IrSletkPA z;C-(-_@8fm9lmkiVfG3Nc|FR%e|TaL%yxIdq=))m)HO@;B!e==qLv}*`kaIWP#3tV zZ;IA|IOH<{aTqKX%wA4>ZK^?Oe*+ zdRD5B&v^1{$TwgdaspibWx!ssTo4vH`Urrkaz(?_I|7(~_nW1T296HPRQ|RmydJ{G zJzWXw)g9aTIMZybGnD_ypO^NUZ@iETsk^Zy|3HhYta!Rv+AE!B%0b_mcy{~VaodOy z@Aq<@$zAT|G~}vZE~<{Mx3saHKiSy}f)g_zI$Oor5qnmp08Hd&o}48lLZE_Y05s8( z$+Y;ShXbw`v>Fd*$Db2(RJW$+4W`HDg+Vt;7Zm4880T5=>e&i1XRxpS_+gpNDu67M zA$pj=_zhK^d>a*k$*}&sHaf>(-VSF}ypU;g;D^6q*}rbCQ6y-f7=FAQB2bfUBk&_QRCYr~ykJIkoV{o^+_P!>6{(8%6 z--0sf9f+milmb}2aMj=Nl#>V2b}30R;Q$!bMK5r&fDXUEX_ZWY@a|64OqaE65 z^y>P2N�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
  • cfu0jDduOMBCgD*g4UDOX9)azWY*2#Wi$!5*g{>tzZ)?{y}U!Z69rku^M zbnSkuZ39{m@YNXCc!24jdUxi=tT`}%il9R{3t;a!q3p=^o32PScaZhPyIKupCssVM zBOZEd8~szvA4w1W^uF)@!OZ&$pC95syB6?n)TxQE=O35;czebB9oC-)f01sjZEqCwvwpNJxbODwcTz|6#C#YPy^tVsw{^FN z-L_vuRr@bw*6p)fl4V88_=X}dtzm1@1;u5lXo6oT8gYdZX_93l&`VmS*|ddFvrC87 z&-571#8NyiU=geOXYvCi9+YY^{+esuZc4Pc#)oQI)X{gsFNXOcZv1GVEic@I9#Jp< z(T};@To^l1lI(XgyzxigTzkp&E7s0Y$gkc$xqHQWjbqnBr788xyDIFswkXS}!EV_~ z9O!+&-!n>OTB0|V<_3j_tj%lAORYA!d>V^Ybk%=?(lgfH)7qg`0zLnSX`tU2KZj=UGB%RGaw#|?1EsFOQk6IPJAI?U7OOY3c zvYP921f|Ca_)yEDoqDLSBaGm@MRW#sIm=BjD5$@JyqfkeyRWwk66A& zxa{$v40lOUfynYazu;Kr_F+#)^GvP_F;DL!Y|Pp%!srZpO0FoLhad0=bY*qq4_gf6 zhHjse?Gk7UDUr=aNltt8wy#!hD23OWNzx{2udK1^=Qp}I=$lGMh~Td8>6fQypV@>m zaZV1DwKt`zFQ?KaD@EHf&UXB8tpU+~VBF302*Ubui=1KQvyzIow$(B%(7SLEWfw7s z__%|X;D}kCdrGR8Xd1A4EOT*CEB3MSeo;M!rM1<3@@ZXtv0Sb%o@ogaRe6P?{k5wR zW$0T6y9+7uP%h43`5K~pXt*G!A62x0)###c%JeT(4K#kW@~il-w(#mEN7D-551kxI zzAQNLIIPp#d$ciK{Fz7e zn^6cI|LWA^SebTiJM3_M*y4hytlv0gKCHTMHcskiRFxf4fjVYMPw@c>xG9A6cu;g} zj05iHZ7h zPtk)-l_tpWoT?>_z1xJu;Jq^2<>u6fQp_-{cX(%;4D31XbZ)ccKGrwry(M0P>j?i} zHdUE~e@3S?@{iLdr=}}YD~sh%WCMaqspAUs2oat89f~^_KRWPlTOaEW{-$xg&|Jt^ z)=?W2)EDPSbXR za(}kGc4*8o*u)w%6D)Sbp(ycj>C2urAYk?8v`6SzeF6vP+h@!z<4w0v9l^9|#Xh2+ z@Hf1ZJ6vPMMT9j@mK1h}pWZB%~E)B_5BzuC%QhQk_X**7% zH1i$A_R`U;!d+itwUV% zF+`apKjh=YxH%p5y^-I{75i6d|6AaMJDB`k6+;5PZ z3dXu4DrrfaVYw!5?ui&`)g6auFZoWH;cNq-csY7`UR2cnkK9;De5R zp^+tLUJM@8?4aK~r|#t+BB$3!Rh=lmMgHtyu^`b)1an1Zp|!Fmb0F86%I*Xrlbg}! zU`S-RfTqx1nbTgz?=MJwXVKCEuX6&XpJuxc#WAX9r#;1yXC1YnbP%_L!tx2nx{rT_ zAVk-wzKtkf?%~qJnT{?UXP(WMt>w5w#PkUaU9&VgZxdn>noxli449Gnr}NsQY&s43 zJ7rijtXocvd|y!>Hroa}?lf1xe7b&^)g9+(8Ji#6dH2!t`(QR<1_uvn!JA~a1PtfC zED$U(6boi%zy#29j0h+C@d#f;HB}3*Ih0;6UmMl>jHu@w*7K>Xr=>Ubh*URUcI6eU zcz^_7ATc#WfX-(akN+ky@rw{7xNrgex|9o|grp0$-V;6)*pl4vqFgD&ify?a| zn|M_gQ73$l>cq#?mU8=01%3sn(&OQ5Sx&oh`tHmDj*l}Z8)Vx~ezW5~Z(h@OG~@Gx zu_J+G!5P2G{i$^)An?&qH`p*ivls2K?@`?goK~40;Ig{sqy(y=Fg-YIygpYtol(KEB!@FbW*S}BljQm z%uPqQvisDRu*LOXS(^DlNM5l+^u` zs#x#QR;>SaX7Q+F*G1G?pStH2ZiaX2pZ3^^Hx^|Z5@$Z^D3YhtFze+vWL~O1$<1~h zzYHX*A*^4_Uc|;2ldL#Sc&;ywG?yo05v>W*qRD@u>00}u~P(s zTnLGg8~?G#kB*!OyM2`@O>z2_XRoSsEHk$2SU&kl|=3H}UM_8<3doPMmAe zeskpK70IT5Q$5db0)Au8)o8vx08gj8V*sP#jIJ)+1$vP_L9Srhab+*WAB>>Y#D-o= zr1t#sf0}_}k%M);mUFeFlCwq`}&FJ0=mf^90vBRuc5MJ+T?@F%_GjHpKbs!v`%N{^A2w97WNKhwjaw(j-HEYY))7_7aV>S z|J!R9FVk?xfI8L&;1SZfdS{aMnJ{6%r8DKAcJ!+!Uz~*{-Uk|M{)}Hl9A4EsEEljx z(`lg7e{gA^b~0>i>j?3mAH;821rXWjSw>}aZ&kg0c-{w8Et+mX;L}`vbcX>KfY#y1RY8_ZPIf|?k%|sbOphp1(W`UlX&|>_y#>D><3K#Nh8o$* z4k8~5d^9~{h;h+L!n!KljnFdzlxAL&PY|P|hP45Mya8}XDq5#O@*S6>A?7?Ew)o>` zpk5mu;>T&D20@i)7X! zASP(R!fLG~*(~oEMBUkoittN6vpK5x%XZ&^MA!tXV}-lySAiSVcRMj{y2MjZ=!cB7 zoN+Bm?DyU7QvYlZ?of_#S_`zYNH`S1O z-rk=DZs@zzkbH>?Ik|`Mo6kXtJ8a%1(wcN(Tyx9s38_m>JUb;*oyofd8_bA)F8%1d zHo94p0i8>wxk}nhSINJ{m$cG!w#&L7OiwkQDH2*Hw1fXM-)q0Fm>Vyo2mS~^u$n5X zi7ISYDIapd+yJsxDgY_O7hv>u$cJveZ0$a@gr|r46>zLaOoNu0AZ@Ty^ngxT-&L^B zMaH-no>xB-*_c_o_3W}h6XBShx0j?NV(0)X_BCO z?Z&B%l@a@**C6U@4U7bb0(65vgxbtN)wlO58saw=1zZXZXY+G604lt9tW;zTiEmMX zwM(R>TcJEs$0pmymAmw#EGykhQqSnT+@O&k_Zp-)oVA(yQZ!C;QdmV5yXlrY*}R*) z0nj34Kp99CEV)2r;q9s{%AU4wvSn$SM>G&tI@cVaM23(jFgN9fuZ^jO#+4Hbit0=b zn1uEIn$32$3}v`lPy_*W$KwOXBKm}?;DkQVQBonh_JgB)EGL!_2LO=l!QcfFYlJc8 zg$%6ibiQ}n(Gha{OD%GkS*5iJ{Xa7K@&loeu0!T}sY_PMtUTOxu^v`ukR=0vXy+;1 zQ=vEkZxUq^VPrpmuHlMhB66m|%qS?2)6b!V<2OT0lXDpu_K(a;rYpK9M1bbsM-}d} zCs#j=q$`IyYxSG*Ae)oj^5M~j;~BW&m_AVm&XUf^4yD^z0G_P;QKkE;VP6s+!P`J$ zc5J|2(#Ar0(kRDi7(=O}YrYjV1SQj6OOl%>Jr=Akd|#Fzrmtg`wD;zB+@A|u?zdXd zwAoq;21h+lDPY6Ya|CJ~BA3ONB5uX6gJ5$#Hquojjf?QpywsK1wKa<#5?8zd05mj; zKgA}wK4m=(}4E4Qv@ ze(eP7;yj1U9gaRgm0@95em0Cb*LBYhogIm+Ejkxjk})O)IA$4FRVv;<8wa!M_r1nN zwskl{)yEd}l+4gtl2`jru&-APTPq}8a&^}zZ5eG%>WdwC(OG-sj*2e}iSj2Q`Bl0D z=l!;)1yEn?>?)Fj7SQ-W5=`!oen0;rE7%FBs9PVf{K@_J13+s%J>4k2K0TLw>r=Oo zUV6I(>b2c5Q@QmN0MR4yQPxp`W1|;HrihivHPZovKscX(cxHdmGCg0fFP5r$s;KWC zg>7S7NHxFsswv}aO*F`svT@M`A#zsx285qE#Ypva=heNa&P^M)1nFYv79pL6MF0eg z+B2~Gfv&RlX@8$=J&TXF8MHZJwtORXC#;bg|16b{efCnT?OyFHz~0vnXr+~Buh8ch z)3#0SyeZfqZY*y9#=cSPLg|X|65R7T&v`(FI=jE3XOqaXMh!8fi3cf_V&!I`X?~aK z4x2uVA;(2UrntG7=kWTNPX| zu-!k20{wIllqdU+SkziCEb3i$J?mML_xhUAm*I+5GsQ}-S(3F1OmFW8AQ1z6HNQWm z2L8H8Aa=D*M=>4uc>th&VDa4zkrV=F6Q0k5=462$bgT5yK;5|{ zSG7nQ6!Ctwrma0p&7l{Cev!fgFn1Gr=>DMaSOZ6bUui#{vL23Az_2uF3zQz7K7F>4 z=k4PaEX`yK=%9_h9pIeJ5?Mn=Y`7x;2|)MynNhz4HX6+Un=Gf2y!9N51yU+O8XV$4 zUvlc$MN(rUsMm+$0fcI3M_J6d4r8PGJf%#t zN1(AfjVNSs1AhZrxv7XT=cAk(8Wu7h9zFjVplWTP{Xsp!9=KP|_CAns^(4eP zfE=Crak8eqY&@zDs*Vt!9Hhp^jc~tQBZL;fsgP>mezb=5P4XW?V-J}Cx`}-FG#%ul ztcc^QBP%>dC@hy#rAED9AnWSM)u~6=OlA?*AC6$qEfFA@`oiQ2S_oo=O~A3x7rzrB zLEaZ?yPEMkhE`L*!d+lO4|BkTRvfnS@u$>@74>w8vHE>0_s09TRdrq$ zDYqk4Y(*M|=_TK^YA2;Yq=;Bf&u-v55h>oo8zgNH`xJBKv&N%a3rT2=I{>AqRo&Q; zj+L?jpeX|N*XKky8RR2&=RUmdr%9?FdnabERr^>-|T7XM$O4<7jTU^W9t zR3JM9`Q424Y7lyUW~aUyc>^V7^#lN z+~M)Ns(eC^AUviE+XY)bTf<>NvL2PslET3@fU|QIG4wbRAjB;zOP7z>E1=L(3Mgut zUv0>9!F&G9VN!$ftUevt`<7J)W*E>@*FLjRs+@i;` zb_vDx5hy=T+&g9j}Zyp(!rpHk_ItnMMwqn zX3@D~Z)a>z?UtsWtVlQ=3+#Bq1)(o6t^3NDY5L;$tkAO6)Pq8R)nGx$7}fzA0fBJ% zNwVt_O>yoIhG%*e-Jze8tD0wg@sc6KeZU_DuP|B~91l_kxqT~RhLlg}*DKHKsmQa@wzb*=18Mx4EdML25 zF*71neTwFnu?))j9r5S}7R$4`pbYv$KxIHU6A)SN!B zp(Ndf^82#VsnJl-Grg4SBKcKNC@c~m(P2J}l=k5t<75dd?BjCSYX(yzpf|f1s*|<~HnSxhAux z>4SZd?xFk}aX@#){YfUi?EP&&aYk&~+Po?PBE`@-HTO}(RX&z}BF)ivL zj{@Ek$Z$~$zDlUS`6IKEf%0zgOeMyZ(BSA4GCgHVIEH~;yw|k%Xq|UNU5w)0atbW> zlx$kzu6J}-pElDquNV&=CV9ivh0^PiblJBML2$TPtr3s5ff*>^$2^~IO~lXx9|dcV?in!&S<)!RbczN&Q!c?P$n3t$mo`Z+uo zMrC!B!0iAr-J9IJJc-MnW9(>bZdnL4y?qvXsin{{fENmO4s4(tWb-#UD1g=@Q`}|8 zMiy0612*zUvDQl=JJE9q9_~4EI}4A+d2VG zKHbr%hy@KYJtM~c>)tGo{{NC>VuH!B3Ic_zd~^9jM5 z={*qt$ov5xHOO_x+g8f&_~KIi0lylUU6LI#fVK#LEWpu;_u1cZ$jx~Dd(LCTkJ z$r>6JHa3hj^;W`-q{VQ)N2}hFoF@r#9>&;SCJzlXrc0gzb8e_zD$7?SAFlhR7OLc> z?Y&2!8w)Fdyv;V?+VvA#mFb6vfAcyaZyT!b2V{7KIdTBESNOnbb%LZ;`NvEuM+oZ- zCbi)Xz@z@?9_l`@SH%}6=1k^&W<;`zf#k4W(E1&1G{O_e`@6*t>d6ha3MH6S&S@`Q z(>cPbVZWgKyj}?W8?cJe^(h*jAvl$w3E7Nt5%49H!(h=<-PFKQQ(ko6ir8UwtaJ2P zNJ2DZQgpB#q(-D^X8{9xsQ3z4r%etzh+4LsdA&TaL5ldt{!w=`mYGz=0#r*!y?ium zL|trOD2eU?x{=}I!A*RzEN2i;PEYkslY6&eXf;(P1DNZc{o`Bw6m!gNA>1fvWrbip>)Fuv8MWuc9DR6VM+l?>K`-i04zF-`|v;Z8%I9 za{Vok$Jv+=pjdCAW{U6a`bK`e+vS}_%T)H1x=??<+JM1w=hBtRQx-&o{*s(tqy2&n zT8Q4$gjQgs2XMIM&pf-bz{2>j$d}qtz{-c27Q|F=ebMLm ztV~`NeWc&E0&H|hHUoFu$xFWcI>{QTY2%`jBqkPK6k&ZX_*7ON?2|D&lD#o+R6Pg+)|oLLhm#y#qt`;Xsjsc7a2p zS5fHasBy4<$nrFLs#IvdQFYXemebFU{`FWo%SwdaMV2l`>uiMTA^!;ZFcfQ>KvUI& z6-fsgvd3aNNu*@UTsQQwE0REL(Z5u7JF3;cyZ+0lMf%?7v|5e-zktL#2YTDuPok#tco-OsMF z=BQ{D0RNUs1M@~}fvAwNxR;Ht(I-38+MvE#oYKnllCse{?tm7vDI{u|* zOh8D1>&`Db1ULsfur#+$y-Np;Hc>S6cc21~-H%gI_vs{+cvnY*)_rV80@#@V;+2wh z3GNgyCc&QVBzsx(3Ex6d)wBxmuE&jVg#qjyc}}CC%@;4RypNAR@BrLqh`c3x-L-XeOK&o zGXn3{L3{z0@0V{x#Gz{orMRpS{Ly^Co$eF3F69~gvTov|&ijioOSITvskIe5F@-s* zZ07HzU1V-2CA1Cu<-3xp?L^ZA278kZAEg4%Hta#GI-v^@!!H(l7G4oEumRGCn5A>{ zXyed@1pLn}r9ib2u4D0;nz^qbM1rNY)ShZ1rW|b1E$fu%K}2>Xk{6$Zo^2`)N& zKwBAi3UN?7`?w5hlnvNi+ScR<7-VlG+zNP~&Wl293thd{S8IAH*jcX%Vj$w8!{NPy z%V)4{fu}yWXS+Lf4pOIuN2La>2Lo_i{vMpIFPitx)|PfX;CJ%hGPhiG+>FlH2<*j! zZ8CS;^hSPw*jYa8nQBk1PS&GJWivP0S&llU#NysEc60ifqPk3!YRMeYZEIc--ZfnX zssq^DT7R}!k85D+rT$}g4(xOQdWtBjhy&aF^%$C!0N9H=QozXHA`NODqU3gL0v@pm zuVS6rnu5EE#&DTmdur^vjHN%D;+9!__Z{AEsadWy0ct2}<@p-4VBqrOv58KtVm&A} zVm$yZ?nn?F$Y4d)dg#bDsz<~cX>UDwBvnjSK8Wf$FL>h9DAAXsN>>{dp~78RjscOP z=qLatJeCp^SAUZja1`*l3gDIHd%0n1^_O2|Pv7*Mu?~2zwdR6Az;)rr*`)PQC|b1! zDZqLJe^fw%HBtqXHTDL|JKPJG_fMOUblV&J*qYJC!rqV`(m^g_jux#!UQh-fQk%W- z@fK|u>;GM0WM;MjB})(+1f^53?05)B` zhu82_4#EOW=qOmUp7`*yAZSE$yo2<`SpV`x=^1i5G9XI|CP3=Id25{tu#5u>hxP-+ zThTbTP`WO7%xFRYw47kuU}UW!Tnz$?{ma;aOQLn$YZ>Ws`<;CCk6mqH;}#iw941)mBF_zT^!^A4pKHEc;xvUsff_ zo0WN{NR9IXvo$kNGv&iyT?z}l5w+wu)}m0{zDt56XZDFpEJuEE4Y8PpB{Z}jnIo}} zqZc9`02KyooLB%er_`@Wb7i$^Gqk(v;j=;TnTLP&kT?gmy43*)olCzsQpdvVnOvY= z0C@=MLW*L4un>&O20@6Nq##>A6Vlw^?eJ;>jAH$ag3rm?)9QB%{lczq?4FT3BNDPO zdr52KD!8xPI{x3!45$fEcqrl2Y=h5J3=Q1$HV+t^c?=d%)FwwJ&bCp|Jy}+umNLA~ z)g5U;`BCD4vXEW<{V?W93G78;Yc~(3&8xL*R{}jnd-{1}JydzlTp$sd%14?9CuS_8 zilcDo0BTwdC8B7wEJJ}N?aM|aofEcV&kJrkbWoiyN_qAV1c!5_clY+APQZi__`CFD zA}D>RX}Ak6rhB@IEWErXlsR4ln9F8GHPlYvMH}v*AhW&ef|a#0J&ucgasf!OUfEnb zR|^&)fiV_`8^iZyv22OzZBlis;Y$G~{FMG`!OAVw64UOWoRkwzUkPncrd$*9OdbKdU^>d(_1v03nk0E?e%MDU#0UdUhm(p@k?}yi!P`&tw zjb;o=hU_E2ssekb>;YKZg`PPcIyyt`Jr1p)H4sFoK1n5a1r0uUAswk6#;~FB2L29< zDfN8Q7W@itA%_BQk(qA==~;|egkU`6=TgJ3!O%&+{bNKZjWBTnv~8ifM} z&{whqs27936%qF^hPllbjc}!+YPlkdKR~jz)r)W&0BBc~Z%Hu$E^R$9E<%`um@U4{d^YKrz_Aap=Qem6er5q z&(8OwZe6!r&r-?gb9s0RdPw89LjWZ^LLLOSO(9m_E29VX_N$)^(HUj; zC|vef(KG4;>@>B5)3gX`MGvLDYC11gG${5*!lhxD15s76@6f#eZ^Ih^&j%qLz*d>F z#0+rz((9--RALwF@)`9)Nr1POvyLtpz;>}C+|*UShXU5`@GFO`4wApBPZ>$bjZ(a$ z>or^l0ki(_i^7LST`aHr2`v%_iU9y+%c6yR+&}V+O%HgEujLO3lG|PP>_FOqY@FO1wO}tc>?!hYE-gliC+X|pu>8f$nqj7E`QQr;x6@iQ0bLoTd$)!J+z=41;yBFFQGol09 zXDN3i0tKZ2@f3gzM;oN3VC0WNLj*YBAE-!mNOBr*MW8Ad7!jnieZLDjAY^$`5laDX zh4WN`Tq})}ybVEw+jPzUAA4^C4(0mC4?E{{+Dp<&Qk05HQP%7gszXSH?1~5>SqC%Y zw2*9dWM9TosceOz!Jv(-Lzc15D9d0l7z{Ip_kM($>GYoSd*ADNuj{@3I@fjPneogs z_w&8K_jmbxKi~U#UJn{{@=c>sz`}ki*h8T}Jz4;VCfn;!N6w=4!Tw%l5J@!X*|;ZQ zlTXhwpXL{BWg7QP6i`Bt_X?rDyv0NibIwo?BL<64$4`~Jo+?6Y*NDga7EOuI_ge_| zj7E@a`X`Tyk=K4T|kAdVMVv{|>6}OB;5WxM1atU^4`OQ9`{P z=M}pVkqWrp{G?~jfCQ7DO1+vE@*%Bmsf4?xX0 zD+?%ESS&|)&Nn(%Hw{nQLdEdk1o$%wi{@@XV9-m5*9+nj;Ta@EXC=7L^lywim%6onWql5)O!yo-JJO?LS0Wc@YBb3QE@uHZ@oPq6pUh|vC z3vxL`e$bW)iMvsK5cM5YBuovloujv$XZLS zMi>6DXY^5-PDUk_+}}O=keo6pqoGU`Gt#{1@GE+0T!7OwhJJ2VEOqfPc^X2u) zH~6iQfVGv?#L)Z6#3sPtGoxh>|9v6Py|gF`o}0oUz2}&;H3@&%L!k^H#@AOuq(s?~ z&!0a+JOydM=T)0Tcpie{=SG$ER&0N$3N(w{67CSZIS*?uE#U3|`g~I%$b5t&?dHhD zxx;mGP~0<*cmi1TIoEi6T3RNQX`cc_6vww@$kewx!s6o6=1I&TX*K>x!#pE?<#Ik7 zS82-%uAne2uI{V?mzx!slW0^)4ndW7^{Q>vCEvsA>6dEvPpB6+r4Gl|Tqh?bf&B(R z|HAX|?L%^Ax;o5pL1<8TDZ1>?SZs)3{YnC155| z@V3FZ&^+E@cL`MYL!sQx-n%P~a%4~Xrr=foh(EH+=!ry61B3oY|OXnJaPV_o(n z&s-4zo*M40RFd1=0+nSTVE0a8jbDM&gHJ8 z?-E#?2Qcm3E{w323StC`VKz5h-*83<7a(SMI$9I54XKoglv9t$(Z&=>^_@D7kbT|? z*E(ucZK#a~VA!D-1e+Ys0jR7`9za}%0#Cjt@SuaJ`W{kdqa^)xl-R@+RIGD`|E!CB zMDX9227ilWyjTHk>7fVgT||+8Y_Ru7S-~B$(?lRp?)n65js&NGaI(gY*lG){3R##E zFC!66eqQKcJlrNQpL*yCXhR|C41GwwSh7+n)$}H^d$wQEKvuv6K#Ny;p;qGl9Y16H zMyCI}x4ye3;RDpeF^~volHI_W0h&IXdp@QHCiZ}`GN>wy(dKd#=lgbmfKm4^4j6Bb zYGXoB6;Zd%1R*Y$Oetq7+!(nbC4w{q%nHlX0ObQ65mn5_{ZN%Vxcx=DAGCEhv_%H? zzBd!9vQ{w_FF+;(>gnZB`$#o2%`|l-#DJ}fD)K?xH8V#Z2YV*0Qc4@g1q>t?&z?i& zKd8i5al>A zy*DJK&bBzGRYAU<`evrE(U3o+T9Bq~sy*PU)S5P?5K0rPE<*im@A=;U1b@|j0s9MZ zO*7`GbN9b7hJgg`K^rT~r{)lK0BQshFfQB-c!**U<%JE%Jq#k@T+n9^;WL(?2x(~~ zrID$h?QG-83xa5&LVn&tK3P+2FevWev#}ZTjL$NtP?_mjJmvvf1GPBN^F5?EF&F~n zxDs@Z z!ky+bA!LG{wVxpYg=zo33|NEK5@I^Q(&vLy$WrouZ*S|bSLqgHL#rO}<^*KhCUG(l z+6Uz=kR`O;9TEx%m;0_={2Ingjd}k~8UhTl^CA^gSBPXw#v+?2^v&imY^Fu&=z;Jh+a3JpF!V!Z*QwS9j7mU+l`KKKV2Hn-%9`Gg?S3r5(6an1Ytc35mC)hXQts+}h0 z&qe?k(duuCR}n5R)m1f};y#=LZ1n|2CS`6^`GN#U5qP4&1}}i^U3C)<73sUX29Cwp z#I^Q~F`+bWVM0)4dasyO&jF_v;2b?L3Mc4?scEE3Ps7Y~Is^du9n_1v;ifgy;+$1Y z-OSGW>0yKImv}iRisqpF7HpvcSX9cZ)P_O=q=9oKo2Jt~Lp@`&y&&zG*e+&9!vg;P zs%mKW8gX1EELN&w$`ji7<}_ZUO9&T|`QllALCe5EL>MFUD+m3lRd|^}d_?q+?9ULE z=(2yFD7G-u$A9*6S@6!eQPkYjEQ7+$QEvxO+9mkd(*yFQ&;|m4vh%nF+C*gDf1Xb1~TXN61PPCt}|S^_r0cGf`-1Q>=1Ou45IxO*hVruSfP; zj^~SK)Tf}!k(_U!XcjHTxH}VzojmnKz!?elJQ?@wnKR2p;FuAl55;=;n7V zn<+!ib}=Z$+8kA;-&e63-}QB3KSH^{4uG&!ml~tJo8@D^VbH=_XC z^5QPU)ByXw2+GT3#Ej4;We!5PV(;6<_C7#hFX}~9FjpNvI+p(sA5e%m^kSBY9w3@E znWd6Dr~{rdH&ewk@t!-=&^7Vu%7>tW%~R%sOkp##8j$Ba)`MW}X+!kv$F5YpF58>R zb2FajnZGw^$IAeR7C#3%%^r|$&(L+5g_$p`>KKP2{V^|6gp@ghglJ{=ZGr3dkE$TU z5E)cgucDaxNr{yCQibhql!z%XgHseC)T$;=6GWY7EP1pgp87MNvE~1na0?3|-r592 zl7cq)Nco<19jGLOAId<3n$v|@WdB64x|^tGwK){8&okbNAkofm<^t_?1d2!V+r>uX z=h1-lnrwcvIq9(hFXqp<5gex7|4kpD9d7)g^f~71<~Ez>n_`!!%~@U`8G$y{0&5M> z7)=|7LB=v4{I|CXa4z_??{}V{pe>z4h+y7E< zP1pqS%9r}gAV^gdEN8WkLpkadm+$}_<(LH|@OfN(%aCbkJU!{aw8dy@g&Sk zw~=jpNTq7`m3$I~=a?~Or%^{MD%>&Dingf3^P$V23GD+!rmJ`C(re^&qVL$K)XC*}*@I!mhpJD`%TZRzpX`bLr>XMJ74pG(`GmCm!~7kDO`!c%RX!p4xF!=4Xm#KHd5+ z+fp14om^28eUnzv2;_FjWv&*}m#ebsyof7};y+7ovkOovo0l5{2z)D=)?C+@K?@P3 zPxGmPd&u!Xphx|4c6^|U%ra#natlWoDCv0B01`xts67aYTAk>HD3l44&iaCxj5`)@ zc$r@C>Zj7J{7=$tdR&_9InF5%A3Qlu2`Zwt;gX)jr$7HgIwDt5y>|ZDk;2 z51$?-LDI}=9emqi`(&aJD`Rg77Y;scZblMQq5O7aSxS&_ofrbPS5BsFen*4U&t|iv;tL|) ziX)eEq?H~@-vVU7Bt$8o-bzrRjzw|==$!2>E)(?Mpr`gKQLH-1E zBZ&qrMZX%gJg|ZcpHEGz31=d+`>2xqjrBJhN`<$gYwHZ|gUS`}5X{RlXZ)0Vj&ouR z^3@2u!0mC5N2|x?xM{>eFz`qu($Q82X*@;0IcKW;Bn3H38ExvnP&YiQ*nhPhdsE8L z8v%yl(|BH>m>b^0IF$`Y)etcmSosI0)*qPUk39j;!-mZS{`_IA3m=R)qkpy&#KIXq zLNOIRW7#CJ7mc>RnlsxFQ|2zjTFkZQ)^>)Pq4?q`%E*~{ac0HOV(S*|i^%0(uXdxzj(M1J6jsoeu(QYje0XK*WY(YjmQ|gnPZ0!Zde=C+SSKXtnl~GhG~vD z7^a?c_-Dk7g|A84Xmb?mJw08Pgf`;-YG)8LI))ptln5TQj@J&b5yC(c4)1YMJ2N!6 z=6nuVy4ObAH585N)jfgCv7_bYke+*l-IvD|y~%$5$9=~6&%M*@>M$**3`6cS>L4TO zHq`piuz&J{#!qdtsh-i#jL*VAuVpRtO@S$g2*4V$1y0= zdl?h*=M!J=`2%O;Y@=cOGlmeSdg@0T&pA3&U&z*pL?oU-!PyNYx|J?t@0{B>A%YskQIO75wMD71fD}<~pA8_0C z$rjMBm@D#4O&UUr`h7(Vq{tJQTBk@>eE)ApFtmol)Ewvyv@qzszycO=B0@c25;OtT zxf^3HIit_w=U2ljv$+Aw@sGtOfl!oFSokIWq5=^t-=8f=A3dA@V$~150rH)Xq<~&P z5y3V0x%l^=m_6>rA%r521CU`V48HI6N&h$DWX9IvGjM?!p;z9V#SG_hVD7GsdjHIH zC4~K#aD+qo9SFhtpsTlmC&v!+$?+VXxUM;)`(4xodD@o`+ZB+z4Fyp5^7mkyp~wyL zM(&x$nvit<>EFxN|MdGJ=AJIsb@Bw1whb{lJs@>#Ju}edo|GqMdVg_oBC!IE4%I{r zy4DV}hUzDWZT7?z&T8ho7Lnp_{ONPL3lIKlkX!!rCT3=EI=~hk&5@w1?&xxwp|vB# zK^cSsc(2^26N5Hge~!x#t&?{-*(%qd~EP7Yejnsgs0hGa`CV`aa;!t_?M*j(>KysOztr5AKeW zC5a)qKz`7?k>7x+x#o2GMUYr~#8v{XTKcEfEluiwwQd|u(U7aiKsNIp#@6j*7Iet- zz|DZZ=Ij9Dnw~uuQUekO3EI=G6J-=q8}NpZUZm{=q3JbT+d5kET`!%wFXT!r-(g)U zyua*e!KW%(0P$yhloHSL|3zjXQ}K~XlU9254EnW^cF0~S%H=*oYMiz{0L7wz7a=Eg z6k9LthyMOg|E}--*NFi?GXKryUA{9acvhyXSqJ{XF^#x zgIA^RpYG(_XU9u7$$fkGPe%`yhw+_QmZkBXFgTW_u`G@6CGh?6vTiI(<9i8wf4r<4 z%hLE>0^c7m>&CJ)zL&uF$IH60ERF9a@cr?!Zv3Aqjgq-6_f_+0_N|%pRhJS$%Ds*2 zUvk&Ihr)qlyGOYYVF3{d-eH6N~j%gy(k{2A9{VLYe)E&mT(z4OPRfB(s{ zAQq&tEQn=6e7`U_mTknMIF^^j^3qt;g|Dy68nLVq%NoHdfn|+gmB+G;Sk{PTjbN3) zvPQ7VW7$S5Ys9ifuu5QABUt6JY$KL60+PV4$>Ck-phIil^*w=&+;4t%{MK6CU4QMq zwJmE7Z5iGfD0pTU{|a95YwAH>H;r;FjdC9=t>XW^_15dTZy@U4lm7zR9vA#KM0NQR z-QO#hZJJouFQbv9m9v&uAJh8!M#fT3SVzEr|0_WjI`<+Gl zLvRUvs?feIQY+;O>D&FxAC(wvllh~RW>$KHzD;dzwCi*`fv`4tp<`jk?%fL<3?CWE zQk>uA<`uPhJBQf&C1R5e?OaUqV9AZPX#b?SnQ4iIA33&BZ+u}irY+kPC=3MmaUSJw z$-vjsrpn-3n8U-O4h{}qCMTtv$gT@tA8|W0urQLs&A+g-L43*@4J|On4!8P<#mEpx zV$tFTagOh@7M@U3BGLyQnL&j~YW~n!q~NeVNo;F?$J{Kuy}kXtEqd+3kD5j-Hd~bZ z60{0k*(2VX`@@!LlkUaDBXL??WPIi;)69i2`rvmjrY<*j!HH#8UfnxCBeibfx{EI( z#@Q{0T62TM%!#m<*Y zvut`UT`D6pQ>H9y@e7NuJ4%nNugkUfo}H2XCfys`kc$!%yAqgyq>ul(f}N_;`9~lh zXIGO-z2w3)mRw%FNSP`0VXV%7cWLoA7G8bu7Sx4vzZ+V7`H4epCziCmT6~P8tqzFllAX5v43GlgcElXg|YB)=k(GHOkT7LN=;2=Kfm+X z;jIDJme#Ka|I%G3262s_|6yx~8?mMK_c#TV78}rTdlvQTrC$d5ige84ye<7uS-O2& zwcg_S+QQ$?pZ4ZoI&0w|*7w5|t|yNc-gVk64c|@jRBg=DADS?=RMMsBMT_nG@uwCE zP3Eh*bnNi13$wlSD3#_T=e;E0N1N*+M-pM5~j@;{a+0K6H*xVw@;!0*NX}Lne zqs=Dx9|WEP2|RpJ;04@^G%=47kCH`8viJif{d{}+Ld*9qyl;+MGBCeAt+!b`Yw`64 z|F;plEluQz5AX$xr!2n4`n6nNTrGoIi^4DuIIsRsV)ip6TU}W6E*(oZcztxZqhRrh zi?5x9XN5u+-k8fQtp}qVd%nd}7GGl{4DA+u$1D(fY_z1zHasc#># z9_?Db#F9XkWw9g*j%8Ua%i@~^z8(D^tQS7TEd?s^db#`8zY`HIJCeI~aK_K@LBp+d zHH$j*wL@E>|E?cyZoT4YwfW32b5nb*jh}P8utAifSAdgO_quOF{Cc$FUv2Y}2W0>1 z;`{mcpl>oZ+);G1&Xb2^`C!Sdz@L zD3(RBB!Q)aWvy5i#gYV;4wkjz{|}-_SQ;b;M`!V}vs@I%a@?>eiRBP#NfOJVSQf=M z2`n8g+lpmTEJo+yzo7*I#QuBv%3utWBuv1s$;`G{bU12Z-XlgsPWung7zPi-l?lM>1bJJjHKwl4H zCR15IIrVtm+jta;Hot%7_kS{XXUkT6>bO+cH$yu8TwzX=_iGuVGu$F40`OG}HrZprb3l9`VO2M5!4{jhPzOT}VV1#Ny26BFYJ zho>KHZEH(?YD1r}6rc8RtB>2cV@E>0zV*S1wBJ5OJ$`U@>$NLB8$48W-mJXXeAegyPqXUI>$U46%2%F$xw2}_ zb)WS+?(ICb>$l&eBcDP4Q<%u4B9uAM&XuXmcvP!g<;WfhSdu)40ORr`%yagwmq z8hzJK^J2?qA6wP?H*Zd0ii$+hd+#7fHE}=Ojf)D~y+`^3eH;T1V+oJuSMWIZ{S86K zJ`U`~010PrZ(~5F(Ksw3BJzW5`+V8d^vDHaVc{p?UbNvq*sqfTY&QT->RV!TEc}dlFJOB8U~Fm~i{~HK@og*;Zg9AXt-+&b z^f-CHfM;S9rK^v1XJ%$(tffv2%j1U76T{6PhDNi?7I-3G$67MNu06M< zb01v!^3Z>kuxJ#n7nj$SASHRC;=L-d5GKK_+IMyuCe9CdLiM8trlzyWwGMomol^J@ zAJi~~g5DV28(i-sEF22UPGY(A>YLbFE|= zYrd8*rsfJA^mKC*QlAc1NL4*9VQLa|p;twMM_O9?DN^h+De%MeG^UyCBWahGE5t7^ zhDmAj(hKUFW^|JX!4zr22GZp2lgwcy`V-r#&^!Ki1B_YXV40?|*VbsJtOKrmunt5t z^@?&4igfp}!lLQknEvdFZH)vGf})6owYIjlQOJoltk)rmR$f`=5I;?+Iv>l-jyYOo z-NdTXwF0aQYYWG=7c!27N4p2)L|ab1eT3P()&AG^)2C0b<9l3nsmGvu;NaBEfi+oR zRcmK%T02ynmKYZoH{jgc-EArQu)}Jno~FfAMXeA1~qBnws>U z_ZLu#MBw%Ban6VuJ7ok)D7K@jd=OX7kG&_Z1&oUXQ6!rAe5gR9rqz2c^C6KoGffr0 zNJS`P;iZS@ax5mwr-+>wVkP>fcDwJ^ziQyxLZJjTW94o)ANivdYHO2lBHeV^J0}^3 z$ydF+3L3&EBJ9HHg&ViGq-?gqHNFo?_Zz+Tw#q;sn5nC)qn#y4S9>ic`_yXNX@#@^ zHH7=Lp@DU^QfmaQ-fI02`Ppy>=+#m1?bD?#AuYMe_3UJ!4~8gA@v9Z(yhLfJNlAM$aG)@c`{|)@E(78dD^4q7IEl zj}0`Y+FW!w6m33K`xdt$v38n#@K$_+d#UTzENy#xdrivKk9vA~72v^zj*9CqPG+Y) zF{G@ehBG`S2V44jyB^e3-)YUxysC_S{d)hS{sNo$%5wy5$J0a3toAJXhq1ILpAJ#q z0DSV3%+jqlvGz7L&lTWw7tatsKfl`{`9dq?!Y^UZ^?6%v?0L~or+elXS^aTBz;FxA z+48U@s=_d`(Yc#^br{YV%DZZBpB|_qAz)VMQAegl;Euj$3Q{b99M>V0fQg@*-RW>I z`WcfzAaoLyS;l34<_B9`;6-z=A%h9X`{n%#A?`Ih3*Ou6>*=lJOFp}0q9xn9et%!{ z-YwCif8NmFpJQaAR-2t+#ihaTW@e;$%k{ZY(ed}kxm9*LyU0+FSY#I$n~92wuAOk@I)RgmZuGQm za#Si4?Rw-vt&9*$Nrcgp16&*2H3L1*aFLFF7!VHq6E@yg=aOl1hq=ol{1xBLl35e~ z7VDioGluVhRzTwIuv&8@cqTTVDi(v%(@vwa(dDcu3yScHzq zyZR?^YS*B459tP&0}*`|>Ma@PAI*Ld4zblxKC z_v=p|r~NQXFSrym_fCEJk~IE7aOGWMm>0$CoaDnQRd=4FYvVhIlS+LHYSmUS2L{wv zz*lbFhfrA25`xF$D+Mbh`WSLmrV$dBfty$1wgon}wzi%eoZu~cPvDtW+`)3M`9kc{ zKYQ}7l;B-p_MV8T*3RwfYQul@5bWbkQ41b+qH3F-48KGK>w1(8$ zYcg%TQ81P-$`1~Ax7kCR=x`@$5|LZ~%4X$0=l`%s&c02t-?zov$w`Nv;BOZoI{PM6 zSlg4P5bb1WYAPBadctV*b7q8Mn%fcD__)jV6Cjor&3@3ArI`At-15rdDt~xiNYC{F zi|gK8KW+1#=^qF zY&c|5CS`5F_aDQK#gPstb^+@rzK%DPN~PX}!KN9n=&>GlrTa!3dofbTjwdV3+}+(t z+Y|Zk2dX8#;Ejm}$=OqUnx(1h&a!Inib7nN>jM!+V@lJB_)!o&rFhi?d-jxwo}?mB zgF!|{M)lhiJ5nb^M-G_Db0$XxOkc05>dn@O3gF}8ljz`9+Do+1+dWh$EZOx)89Bi0 z?ZAF{b9+@|sgSY+P012QY#|UtO+1NS{bfyh5>~t^y^7XbDTys@oSdAjk?lD@ws5)j z`TLFTZf-B`#Br}?#y+sY=LchiL}6}j(m}R)7~)KJwK59Tc&5QsJj!Kz^4UOV!tU`v z9lTEciWAyeS_pyPncMyEl@1{huCA{1^T2}FDm%3F`lHsh{+Y0RU&7}POXWx#1Nq-$W2l9J*~%-ninvOA;nD3z3gLPW>M z0cpgb>Swa^NUBBdy%xP1N(B#`WyyVZ#aC=Xmg%0Gkt0{kzC1E{ zZ``ITRDAKuBL;&}uPi*!`hnR^5*k;?Odub+GJz@1C_VTW-gtT^CPegB-$qKyZ>aUEh9157-g4(JN1|9lThPZNWUS6u7JNFu9e+sI1YZ@h0=&XrE zRkXFYhsUvqk4{4`DGHNmX_)sF-1iamaht|h<@ps8XmUR1!wwLRnU*0WtfZ7l&J(fe&fO!R*lr`eRI1x!40ubV0O$f-QTni=}Iu^XF# z&EjW0WKIlh-&{MW(TTRYL>(A-G4sJ}=EJyi{MdEQjT@h$z`}YG4puHk)wbY1rbglt z>eZhfd2a1e4{OsJ+j)xXbyp~M$%LSbva-anwvpCVSSJKR32U2q7dE6HuGX>A_XI1as`EjOLpU|Z4NSz@BY7b!xL&P1V%0!(#kw|1OrNcyR z5CqmKxD-w=R;+A&3!6JUzRy1nG!1F}<}0THe&yRP2Z2lmsVdrH1>b#DGSFw4V{BJe| z8{q$@=!qETXJwtYI?P3mj{Z0Vva0@oK%JrsL%^AUz)^|vZ1DCfPW26|R;_Xlz#z|5 zOI$JiW#Cn%XNA{EBlsl^fxju^Up@q!uS~<$49-c>u(1*T3AD>;z*s+}7{NUw(w;Xp z_rR01aR;n5t0&@_igUp%(LIN^B3lyN8L;;?Xjd_fSAxrJFcI1jyQvFgwCbAhfP~ZO zk>t;xFZXBbXPW8;Xg39>BGh}YRudyOS({zSpj5c0%7%cP?nUP3-Mn@y6i4yfb$adu z0VAj`&=dP?q9e=i9Ntp=fYjbk?Pb{44;3*@@|A~F`dX-C8}%#u`}`6<3Z-UmcsqWzXOI)wV%NBdjEqcu-}Se(_J_s9aEDBdBzjv_(-;bR{^3?8 z4%e?g%FQaJI@5E9t#&()gGg1E)}#86aRh1mdl*AWY~#j_6<}@*iJ4~{;JgvJS$Gp; z<3~mEAQ2l*wkL*W3EGSb7gn9WOpiCIU9xHjU5WVG3w&33fCBf;$)r@N&vUDetdjiuzCXvU$z zydAbwytCRf*{T`%qeI546>*|}HAYh<$ulD(FL|iKG`!x)9F=?LNKw{@+CK9CZMUD{ zLhLbUy2rzL)vUr$R#w(#Cr_qpIJ|~Z-eREAbe2G*4}S>Y=>ZRaMzu%`tEaoi7pjcI zk(sqN+Qnj65eeRo-(33_cGCc(uTWii z;00pieM~RH?#?69=f+h1C%&sV`5(Qi^zIkDrPP3t5L&Y%uOr*`eb;ncPfyRMQVx9LxVgR zTZPb!#?|g2HjY(M4ZYVL-;VJjSv0+~p3T1bVC<6m)@&F0)SP$3^<;^C!#ysXM$!Zv zotvv?d#2X)P)$vZpvw8NVh=y(0<5fZ6B=&a zcbSt)h&@Qt1x+!mVckQ^h27|>i84v8uPR96K=K7xo z<*Ff*APAKCbcC0GpJu)VgkO9|lz{P{~6C zxrWuA<1o&2u9=$V-~;?|u;}chYD^0nBu`CL+p`q8htwe`hA*uxZWVH>NO}362C5nw8ue3F0#}0@rW`EpV@|zIh|09T{d~>ajk2o7^0z>` zA=MG!J9*X?SaM78jsPB4R1*@gQ7x&m9B;VOqMEWHoF9hhxVZp;&_t zQ}*g^ZP(ynn(k^v%0m?-YfkH9z-|saMUIj>Iy!E;xJY6n(?KlxNnh$p#?jIveMy;N zLqkI^PPA1$1VTCCd!AbqN7L#<`+~$-3mMMbrgR+!!;I?&Wm740vy{qaV`JlSxjTeO z_#g>|6t#HdWam|yo{} z+`M^yl15v_d&1G!#3U>_OKfm>c)tA68Df9^`na2Y!!a3R5!?u_-G4hfJNuJ95N&$d z@XSnMpl+L&k%7ShmYFF@VPEUFAG{u|sR^DM4&3Y3PO`=p7800;H9pfULdkP#9{4R^fO-Xn>)UiC-b{GUsNr1SIg@CNPfI#(5nnUif)u3#z~`_lwiO{CNpnC|X1h19i

    GrGPRTf*NhQ;jO6<=VTlQOnB0cNfU!g)$})!|m$bKchwdE7PWwMiwVt+n+aNLKGo ziU(_cd`;PnfA$BKzf#WlBFIWD#G}sMCRjx6DJv`MiE%9I_l9Re0DPV!Q43{4UH`ik zZ*n1RuMa``qT)Je{kiAlj%}3JgLhEc;+uiEJ=;-w+jXEJ;g zc+;x@O4-6iQN5C_FVw2AjyFQ+KELc9U?HC-w!C`ZV{kT)ZL?7`4pzMf){;OJH?f6Fk)VX_4Avf$n@7UPb zxH^X-{=pYgUJrk(xV5j{R<7}0ngU|xHH5z&1>w>`phCL0KAOE~aTdHhEp#s)_dYEB zH2ok+v+1mapWhbc;`CBl5wMsR5!h$i7RDCEHLo|g5^B_gXzDDpx=o%STA?F?9|OaXCX818nAX2Z!*9iHY+UuH*w3+SA?r_|UDkH%)i=h}f+sf*5CA zqAo|duE4$m`K6n?`)RITtsCBsj*i1e$K9nEcz~T!=1gzBUz1F#@T_+C%M6=td|&Oj z3P+U`aqLf7R%u`d7z#WuCRJ41|OgYIaC>*kQ zh@Q_Im5lAg;v)uMVNPy=@3^TYV{kJ5dH#Nw|Zl6?ALck{HCwu_$ibbKce`~Y&8++{Y2=L{$ zj8TOSCHXrNk~|ew)I{SXt-}f1i|4ea+{*3d+bge>xKnNOa1-}6B3yU z(Cjf#bv*dXTh$9x=iZ(9rY0tFD?`Qt2sQ5_e|-y~-Pdj;;Vjk&%0OlRu83_;ZwVl{t>1JbT~Ubqd9V)bW7Ulq7c}eQT_@JGps`vVv0+^I4 ze*@GXGbgy$5u2O$t3+^k)>Io$XRoH;07cP1w$@|4XslJGA9cSSq-ns4N}c=yRgRFz z;;OfoPGcfb`3ak^b0&Z?@>W3G+&UPjKY%8;mRE@Jr`jaiYHAbYn z@GI(waUw1vI7J-t^ znDH)mdMBt|hybwX>X|X)%~C~$g|V~K6J-3a8G)sKf_)ERND$6}98P^h%WwS32!#!I z#BL38x1>1l+d2bqK$}g9jLCDin?S%*Pnk=URe@`xl+troTJLjKenmBwm3QvJ1z2sR z=uyMnKPr{C^9{_hN-8jb4JD{u2MU7o_3Q`7U=gosW@bPo-e&#lkj*t!OyP!Tobj5x z*|AEoBvPPWuh*?6ftapLQ!L~I8{cLvOy3@Iz+T)jTAavcZd_615l-d>vZRc-}$huITE!OtdrZG>UTCTw;w>_;iuE-K?o#(KI6|T>#%RW3IGW5IH^8BTPaxmCeo~ zp!5`d-O(`uMDce`-C?sBklH;(#%=Swg~wzOAT>no8<|m_Et}hS^;)b7P0&)%X)Sq24~5Ks02V2Cl&(qizrtI&)kqAmHs zC}VX*l-ktNB%G?=8y5s82!Dz-zbg24S^}ANYKx2BZm)cID9tHqNY&4*0CpeUHv|Ap zz|LzLJl);Wj!-i2^S2OwKrp6;XIofXr+`G`{uezEL^Q%yC{i{WAeHB4oMB8l`juCR zLTIiv5h4jSPaK|{?X8aa)YQ}SB%5`Pud$Ps`hvEEEdTLr*nYp)< zzD6ou`(#Sgp7@EEaQhXCz9T((q?%9MK>H`iWDRdVPXOjhYW1}|cX``5Qq&PTl=x=bzn|6s%-Nd= zgoC5Q?yiXJ>|7|~#?SWm+kB?>V9@Xoz&#SR2!}s)1bh$H^?F=@9SQPQS=Ujn;4wQh?V{zvtIBh7 zP7aDp!a+J*QwHN|@I*isIgRe=XkRA!ix*2=<&*ov0`D_Qdu3H6^~-qls~$DIJ$-V$ zk>qX6$-Lsj5J5!*nuF3NyQs55J)htC-F4V^1+|L~ERk^HU|^!tj5y4cHryd1wfEL6 zfXZLkIqNjGDnroEwjO47CauYBu#mzs0uHH}eAW-f-$O-<4N|vW`(FXLP{ei+aH`gd zsBg)!f9aBT6o0DC!};wy6W_Api}>B$;8tzUG62N?Bpd`eSp@|mUle|WGL37KpvF^C zT6*gF0lNclT$Qxb0cmLpg5Xc?bR-lWkjhasqQAh=0N=XB)&$fUidlz|!_Gh$KZW|z z030re`5tqhOo`9csXfy-OTc>7HxD;=6Chv92eFUr5}28-zYn>=?22t)uf^?8k^T4;@APXK<`O^E4v2^b`A!ysn~yUfyA zC2e?+Z83tG-|!Mt4N5I7ERx4N0G456;p&QJ9Y)S5nRU_D{>I6#upgZhbPRt#3mI45dxpsrpNr zsJMF|vFw>6u=~IvT-kFGc=u`FHGiln6Lwshdwy z?3(i(v*vOdrKj3g!8h7Ll1$6U2+tj>k@nr>N%+P0Wl?rS&w4=h@pN&ySSmzL*96!k z*}|KZGkUI#t$17#yYHld$PU9jNrrpw%lbyzr5}UyKoy=1QSKJ^ci`{63)$^p^bR2B zjn)rglOXLCU$>?p_Z2|VO3l|ZM$R>~o`!%y-!vT8_!I#2G&acEL8AA~D2JL!toc729Am;NLLC4*KUXuV#nm6mt0|DTR=?7o68@_2zymL;u z48#;R8mpO{0Iy8V=(%&vnoHe*5!dE>l_5CVndpc1OVZoIh%iGOP-&u3yT`|KD&oW4 z?||%F+_EKr!Jko_KGYI>ljK&TjW=6}-*MkWqQPYRE5rEALRI7fV zD4^HX9=JP1f0EeP*!amBCWFb(KPa`HIS7gdhg8mY#aUmY%^Warw(lB7H?gF~9Dz%l z%q=M25q+uTK*5@c^}E9j_IL`cnS!dZiAH8-V)j^|8zW`GAt9;N=rgY;KtlFJ!=Cmg z%^w&M@s(?o^LmUwJMaCdaOcQWU7gPJ%O9<+YbtYp88WC;YM4A!p~yK@r2KRU+wJ`f zR>U%jlE@!2#L!(gf;^nN0EklyP;!fUcYpn+wU@MnLQk|){Td84KvfxOHWdQqYM%AJ zkD6Y&ZK$zP*TxPaP@V{KHE|x~O{vNT(=NV3R>rQnfAFfxz2aiK{T34$5YXH-IXStj zY!KcWY!O6a4g3O5vKh(&?bwx1h)11bu&*0%%xor@Cx~lXXU<$lZp$|hn0KRFNVE@U8-Mp=r zI@~-xTjj2xyaZY!u{gjFB|B|l-4DnI$KE(nrIu7LrSiyMMM$rm=SoJl&YrccmyC?~ zEN4La=em7`J-M&%*~n!G8UxOu;S;F6eIAB9!>OjXwHAGV1+0683YCspAZw};nw?zA zub|O_vyF=jEw`kv_uWO5pvbC(VX>#)D_Ka7GqFR*dWpPX0kcxJMqum#NK_kvkilPl zc2hz49icY$ZjWn6$H5|b^TF6o?F3&`jLP;r)p(Q=+|c$J?=*iZK(WtXh&b8`;o!Nx zJJttp*BKf-ioR865&rDP27f%~pEUuHkR<~=+%~$V5p+eaDCf}P+HOjtnZpDai^=S) zWH*@-VIxeG{C$+Fro|NnxA=8?kvdY+TdieTM*HK^<6gr9)tehZxnABX@}uPj z{{YT6z`jjSwmDXwyIKg+1NFl&e*X(JlamQ(!Nyw&C^6g%s~z%QUBE+X*aHkjSFlVw zUV+zA;n;~GAW5L<;w#qZ;4Vyl-cC(#dhKct3TDsXZqlzti|(S!0L693jvW^b9AU(M zXF!y#zLXnA9R<3~+ClPTt&X&L1rh|TlCLkd0rH6CwM+G2?LFUp6kk+UOJ(K?IwUAkREiJ~X#opG zfdr(t1Vsof1PBm9NOC8;A(*hg=lgx0d;hriKK}L2JMYxboH=vmoHO&byAep~hoC|T zQ|g(Vikw4wVI_~e6XS0_RkgEgq*ZQkBHyeOvRR|yczT?wWGnu}b85T`c@WSkr(17T z9JiTFHTsR)_fV!Irrk9R(7j#iNm6z@Cmk|1r+7G_(>czDKSTm^yivkFE#w*I$T_OY zKX&TwGUF3AaaBG9N_(~WYsI%G9&K=Qak)y#-Egww;b$0{=->pb!VP9Qb}`-$#v#zw z@{Zeo9E&tbNs2;}fDqZ*8u!KvJ&DI4fCC;jYiB;upI5`qCdV_*j@ud6id#vSmAm2o z&=*A6lRCGY^*>xT+yhq6SmEso1T*u35a9!VV~b4IS~#6PCIa2`(zXHk3z<5eVTy4``bE(fYJ43dfu~M`}Q+Ao{3XAq7Uft z*|kbcb2w0l4Edq|%CfkOl?21~rTnJeP;c^}_h%pUrMUtinqRs+vD+rz$&lm$R)#j? zo1*Q#@@27#VCot=exbvmfmmX8lzW3u`@&)7wu6(s&3kYiHmW+m?`!9Scl{mbI*%M9 z^q$BCYu|_!=F;()4Y}Tu*FGmUx9tC#G&z&KH*6=OS2x!rvW=-{klJUS{Lq15?ZSWC z0&E+=xqzIsYQ*dM{Ce}`HfY$@9VBUz)V6L@gYUpz?y#$Ts3iKLYv7Rj9Mapf==5~2 zZHeX)tV^u~eySXuP)GW56_Tp~P8g`>mSmdf@@POb5Xjj4=t8oETJ4!}lzNoae#Y4{-Ik|~ZId=Ra4wI{v6nWuAc+b>y|nI-A+R73^&pmaLOemy`(lktA~ z@bwH|cu8r*JulHbaKbdnzsgU_5mUUF3|2mUa)~h0RSu%)udmk_7;Ee@bSc-Q#se8? zD;-E`RBr1yGhx#{w%7m0^%@t&0j4pCmQdmmdrDJYT(heqls zLJmTl#D1GOJ@f$x{|V`fC}7@0V=pKD>4(ib5r1)h67Kcn-jm1I_MnlE|Fqw6tRYdZsV7m^e$|u4wsk<$ zDGkm8+%(+&dn3^wc+cAXHgC(zzw<&Xszzw2Y=2e?BFTMxv<&ax9_rkp_qP|YqosW+ zdXDU}JCax0$-f@*cj#)nUQWDE4UIy=r<{f(#XJ--&q72PK<``0SZu|{661=R#vxsM3yc~Zp?^(M zi?&TF6?lgoo7g+{?Db=>p=10v(dv$KJq>vQHPpFcZ@i&=X6br_a{h2YZC(s}QELXvWVpCFc2(7(@hQ+vHT z#z%lF{G-E|7j@mADV!&S3%T9Nct2$C9gccz^Sy?t+;P(5RWO)D*?<4B;aPEvKh^kY z`6F5H=NXg=q)_GXfDr1Clf)g%QGc-Ky`9s#(F>0U?k!h0y|WD$6%z}*E!y7xx9fxF zzNbmD8J-C$z*J^d8XM%Xs+$yHkBePD1FTA~*Zb}LHr&%ZV)D5R!#pY@H)BdQLEP-a zEYJ!L`P$XP)m|$$k556N#Ls>KQ{PV=AjWfy{Tuths~izLey(EMW`M8L)bX_f{Kxbg z6oUg8q+~VdfXVGcQ~jpR)2CO}DXvBWE;>}TPT{w)0Q89=jPdnhn~l1#5Zf(-^aph8 zVj72sDn5;^2YDuT zHk>=&&XlGzfbPfx_dwygOzOJkc$+?P+a?1~XFE`wp+kHL$-4t-aHCa>A(X(yl%oWJ6pvs!V zcQws`O6`?!t0k~|ffFPyFVFrt$A{f)jJaHMgZDQ=UC9(sq&4?qY88lwit$ax-@8kS znLazflv1-F|6Pc9sDNIh>WcWdlaqpGx%WgB(LUE!ML;)(h>43wYDy$f{gv;E0o}w+ z-t8JtFo11mJaHkZ7|T_Kz-2i-1MERZu3kQ#|48_=OSz1-hJJ#Yjc~L&?o(6k_z8#v z40e9o{J4XSs;;J&7E_mGBlp&w`ZT*FTW1^R9~82Qv>2!_iws%}4qh-^T1kJ$o00l!r5<~=3F`^@_?*ZU33&-+}V zwp3SF-IN6)W!%wUi+CRLIA2igYAlDz+*8(^%ahnP<@pQqFT^#F3kO@^ExP2{HQ9c59N0w5c zpN~F{p|j5KSGfSLaKChW*kV#8C21NDqvHp~8B^l*m9(D9nB8^W;Sc!4W1l&~X$)E= zqj+(wl0IfflV@r(4~2LiXjRb-dA5IJBGr*!lMwyMfiW$^m`2v#7Mykl2jU#|4k#2> zjZ`wIm2se8*kQ(OCB5TPJ%T=hxX^Jf&{dC_{)h!^=WC$EJ7Q=;+R(}%;OH(R$ZVXs ziVgjTKK(_*2||wr%rToRwu)unsyE~&b&m8k~rWPa)CVaY*CGM zUXV6WiQ)T~^Gv!|QX1Vd5L`kSk9@4eAW)gjd_g>rm}>rX7zlGH)p!*dJ>GfU4_`|< zz+k`c8_WE$QyoSx2WD%&1^ST;Fe_TlDAA&Zoon_V8#4u3%}g!i95$ioF?6@TS)fiM zQQfVi*cKxe8qg6&+KP$i`+SsXj~M(Mec&co?s(6ls9^pBCn@SUZ2z~Ba{LeHooTih zdo16=JG&ocH+xYxD$SYn+CDDb=#SZ|$PkNyigNJK_;3a|otVam(E|QvOSU?CKL(Hj z%e2y~)c$x-YF~N0{WmLGn^g>Ku@)x9m~&$o24{yP50Ug4Gy3&R1v>($_`{B&NO9+G zsC{cV{KM{mu!anUwzK|k3!ty}(~1r6v02DtEaas}PD}km1>f4k8n-IG@6w_~Ye_2p zz%Rxzrmem(M-+I0x!E5t*|5saP2^QJlejA3xlxP0_KEVgV$@4UcfK+8EeJRx!3L0A z1;mm^U|gVTW=59A?Ctt8!S-)Dt!kkXDmpS3}9+) zqk0aVG4hA#b(tNp+*zFceq826Qy2lkpny#W%V9k0Jq#iKFJ}D%futndc`cX)2ZlVV zPmbDtZ)chFW)-ZDp8nSZ$XQqo?Fod7uRDB)v}iLeGt_;4U8m_3-~b84$E!654Vugw zjWq-2-^}RMDA+`4d-TYq`**E_0j=7rvHib|n45H1eD3`cLvw~eAfD*8Zh&ag>RRB4CBar7<-#l8BWOjm_A z+U2hK6I|Al*#T}UD7cvap%-@iNy(6?S0hAU;mEh%HFumMPCO^>#4(*d^BZgr&z|BJ zgBvx?LLE<9CPZc@?YFkjZMPme*#UXBVem8xhAf_yj7=iOR~!Z+HqN+12Ptz(er<7{ z4;e{J^f$G%grAPbs}v_b_9k?C4B>%5(be0VoQp`*Dvq44nC6Atl#0w&ut@I}1*wJ871o;XD0dIsbRt3Tlft&%-6s#*DZ<0|PbAkl5mGe@ z6~|_q>Qw6jr+`3szT-4qy>(ue7^lK?RZ20UK#Y#K0aI=ETxW8^hUdi_&f}>=*?E63 z;OT1K6X$Ccjh+v96R|1uiXmTcGD44`>fy%pU)8e9PCPWGNmQ}OrmpEhX!skV+TXu_ zudrWJu#t|(%I^Wi z#SiV=fRI-(*_1%-T9@eA^mlmW4Lt=!N%kgJ%T^ZCZ5?$V#3nPha9A)1shYC!|~dS~8*ctm;If8V8=^>ya@iMZ_@ zqRyMg`8qWkC+w~7c$q<+KaA>Hr>CiSLLxWc+Z@o@`Q&}gkZXMv+IrTxTi}WA*R024 zuiiZ{(fD*KqvCeC{^HwkM0=D#R|qXob8RXqNBT0i)v?M9xjv$NFg58x0TJIy}=hh}@54X1j50At{AbVZ} zep@N8t{gPm9}L%NLUwJ~Gt5!Bb`-ZlhKEO>4qO*7{5RmuvYMJ=;EdLXidQY$X8bA+ zbKQ6#5XzcjKUw7@A{>i^?_pD9Wo5TsfU9@qH~AcJ`Q%i;XF6q}l0$~)>D>4qpzYt} z=Y394Sj%(tijbJt7V=#<94@P*Bv?~hJ3zSbdDSr37yMKm=uGMvFV1^0K-|WCoBV*I zNbtGz?W+4WJ+ouj3Yt`!y`LPXMn*(VTD!~`fK{Bd=k@&c5z%-%=;(L;$%{+0BF zv1`Eh>b9xiU*aMnkBo{;Y2&Zj9GN42A?wP)Yu46(?$Qi+mnKIpDqv(vap8XP)*ek9-il*iMN0!tSmU58Jc1E z`u^q23)P$!%_==LPW}$+3OFWsJg;Ny8c@J6CwyNc8eK!~wO8Ouh~O4S!SCJWw6w&* z_=i1X5-maEQH8#qUNxDsO6~txC}m^;;UOLn(aT471Y$Y{t`CtxRO+|oyyiU zBYFG;wJ^EkN}L#ut!!MXq!sUTqk}O*r)1*lzgZgP7!Ek0_j6`z{Euw=OtwP77+C^u zv~T&fVw?Ch=dC*{VX${Q6okW>dHxt~Y1sOVDZgDuy zMu=8*XSKWG!I(EYrzd@jOv8=5%oaKCX6Z+-B;clPV8&X4nY%EaFZB@q`&Z=a;$mei z5%71gI85c+-Y_76amU=ncM*VZ+I&R;wTqLlq>@&)tW(w`OTDa}boJPbdLXCLtE!Lt z?n@vcSX|BP;GAqcWcZTE&WY*r+Z*mn@0LHYLXTInf37s!SJDAz3TDt67Z^Vw@Kf*?W&8r;Cj@>9 z{-TUuVElx@Pr?6}P)3L-=N53eg+bCIQ#-64lKO|E-6y075s;B{is%G>tx8|UbQ@1$ zCi&ZH_dtkeB|&5HOfo_WCV-DY!oo>8M*6jssqjIbLVWYUfcu=5zXG}lZ;Ha?rl^Sr z%>}fT#QNj)VUI}Rah4%1C2I-wn4$-UFsnm^GA;DrnE0mzqC8%>Ft4>(8NF!k->-%% z9UMa|4ks^?3j=aBIOawZq`y>4~hwV7w;hAIW^`Vl=djA>E=Sr_lRY^xxxYH*QurP?71G#hzNA0K_SfuJltV>mFggcc~s?SCr&66=-uMP1c zNLEwQ7<3JEPm0r{O>VUmd}iRTpmue?0Cn^Uj8;dhALw~*8^nbPwN`GR<|>6eMpv4< zCY^*(ETW5RX>7w6LGY`a5;G089*k=kn|P-{!W$LjvL#Cn8#Izn?Fbttc=8z0_U*}0|83%)CKl7Q@uW2ZV zcuSt#>prI$@FzOG-e`4G@*=*@utTjQ)t4`T+~cm~pF1m-{i%dFsr}v>MxC&XYWxjb*+$jdS`0{glFaLj|AsdcwSE66W3> zCvV*NNRJ5K!$poqWG~K6n*@g7zE>etU7{c*y-zx)7?bK5DE4>b8Ny0MuTZ~QY| z&|fDq+HqNch7`qd##;#XWZ#Q7>N~U^A`6N1jP@S8rcy7M7(mNY*XWh|oLII(QK0)b z0#D)4!eS$4H=hQ>{E$9L<*%ey|F@bM5MZN%)~%#LlW7?BiirjNSgq0$x^m?vZ6`3W zjPd59-mzYJm4TjCLsS8KOB8O42BWuhY#KsnG|~q|PE4jbB~wZayS*6;V~m;Nqj86= zHiNv7(K;C`M+c3jaxndg@4u9e?bv~h05d+v-)S~^fu46D$&_e*uie7x-B$e97{}yd zwT@}e4s;6N8uhLBJv-KhFkcb12ggaCU?~i>wy4T87>0JNJuMIpD@3Nc9=6Sn@Pe|ClIw#J((SW z#uC+1Jt-w#_Op<>JdJ?<*gQOfS~>Og5Xj!i%g@j6?zO@gz}d+l#R-v}J1Gifa>~j} z#)tXdrY6q&{XiaT>J*v6V&m<~|0`y|eYZSk&EW0V4)rHUeu41|jGqwrDfo*reu425 z0zUY&g%oZ*YcToP zvBxH6H2+fGsR4u6gk$!*eeA)vW9o&H$M#B`DKO}&%T0K{yG#~YCQFx;OZkQ~&a6M(4<8EyR zXI8oKs2ch@a+@V@1$nS_#<|yk#f!VxPV;Vp7c27TwicS^a3{5fn3$U{83vbo|1MY^ zlh!iAX$}B+lTO6mtd`%$D&?o%sR9>0ZfP*6@mZVWlJqplHc*)Bl=X2M4nT2A55PSZ ztYTBum$d3ZWA;U+QFve02lnI5R8bG%KT;2$ZYq=GbUg*Bl-}ARP1JRlz?)`+=bGAV zzoLsGJVm1rf(me^VT%>FX~UvfJ2* zJZ-X9*n=T)r?$5FTp;z+NV2Vc+8g@T`&C#j5iL8PQmid<=Q4$7vHb&5Uz|?+mSM35T* zTU=+@KESEZ!5L?T;y^A7<|tQ^_w8`8BA8JU?j;I3D8dsqRX&3$&q&g7;YdTj{0DOHU=_g-m|npW5tN{Ak`+Ihe%&s_faTpU`i8 zBJTbMGr9Ts`R@i}&FJE%#KpyB=vHpr^yR#!uhZ8EtO___*#_Q^8xSl)aNY`!U~wqf zyQRvbUT&QPIyb?yT2$rN$$8<~QcIVghuBoU9LE{@WsCg@k6&Q?0^=tHehU7gj9*~< zguqY1UzG6+jGqwrDfo*reu4250zUt|vNgo3y1V^>O%!-SWp|h)(6S=a_Id=-+95VtGJ+^&te&OH}n%+gM=a zy&^~&ZeY|-Q)0+W-_m3qaxQ?Ut7Tvf^u`#*T*baCwulY9k+f)8 z^|^$q{w})@5WHJm# z=PA13*HQGG2dlD%)G!*C8$KQo18LPN19Rh}-O6`rt5kRrG8sp&Hsll$Iy3?u0-wcK zcuG5A)?Gv^!xwJKRnlKuJyoUC;w&YH{0~@dNll&#F_jZ%V9crubKegiMFmupj5Xc4 zR9i_Wi2nhhKl0-l?49kWRWE)cT1j~Z&>GfNFV;^TSd~?y$uCs?ih4fVsj2eSSR!=) z#T4j%S@o-A{cgKeZCy-!0g`a&{;&c&1oUmZ8Mm5;O$Aq7H&)Y>_RYj5g zOK=^j<$>gc87yA)$+iDkX%)t;cYHIkf1;D0HcW=zk_YVJl-2^2sqh?}p_aTJqHfE| z1{mBCS9UJVAfda1W`mHz025p|6X_nLHrQ57(-N+nk!n$oJOT?GfwEQw!Uo7VO@; zwI-}RJ2kdCF{b)nlC{2>WT)bryYDRb_evKYO-(+bFF^CnqSB9)fseovC{H9Wj_|by znvIVvI9kw&5bZoeG_5E(`m!?lHm$&OZIQ*|kCtJbO2oa_T@-ooSP0|2nqMha_1*%S z<~vKTdq#v7qig9@l~ix>%7t1FY#^i9(SmO#7f1VyTKlnu2n}RZ-dlKu$E};F$jltm zh`hIuEPto@WxH7(*yOFksHBeJVj+Xk`m3|>MMNzV z^n*b1d6m4BayrA3oEK9WM|uj~?5IX=R~rmqRO3J;>D799dAVw^g?l`sFXvvqbVJ9q zpw`|qBItXPiyXDqtj#OMFuM5yjW1vZtbJbL-HTy(l*inH`j#w=3cnD_vF|8g4EW>? zyjDedebDh{57(l+3L42Ownc?Mi`XchGvTBI_bp9^*S_yhfey|x^P(wdhY^tdvH>)> zynEmi1IHBxHcqIjR6C>&9}YBG^qBtfne!75PtC{H3dmqNDQaDPi5>BV3Z`QAf(j1y zZP3HA^xbxC*elPgIY`I8t7ETB?b=FLFBHRuLbjvSSLAMyGyp zu)6m?seDjdg{V1auksq2V`@dzqz0%EoZdi12u@?fNFG{`qm>%+E!t3%q7Xzdsm#0D zjuZdwwB2jGdmDooM9+bV84>Q)XKG2{YxA-v?oF6|m{S1=6*r^2+zF@_w9! zhQd?c&9%R@_fi|wzjob)dq~5|WHhPjhh?lD%Fy#w8MHPGK2s}H7p7uF7iASSz`iHg zn)k{#QoBWqJFPdnQ-$5`e*PSkVLyqugJGJ%`Pq{aFH5mCv$mE`N`Y zwcRU{kclms}zk{)rp-wZPsIih5EgMfCu3xe=va>tqbP+!3)kel{ zn~71r5M!|90FcYOM5saA;*+&@DN9LzI$6vzo3V5AIOnW??j0-mg{&kXH?t>{Z46ts z=NW9%tQ?tdwr@PoZ3yIvb;Na-nt_B2)cEB$GANLEqTNONHL-39ww##Qj?nP|dx|=6@64RJl-8u;CKyu{_;a)PU@2MjtZ&iW|3!)sGRQ}ySsOD&~lJ*(;jw%uOx&ItO( zW;31pxo!&sw*ef|;boDswGX@8x5v1SGV}2Cf8jsD8WW){?9q04M+vcETd~>Aut|NG z3L}KrG}{+1VfCI(m%}aeTCq@#Vz!l^oo(Qm&yJJ$Z|sqkWjAO+Rt0u+PBdYKBSwwx z)P?oLr5r-bbeOomG-X(GZiR2KEL@dO<|MLbNs4>R_8Usg*mD=lZZNx1K~q=CMZVVz z=&-DOf?UKa`?4+zf=g!2w3*-LQOa2DKkxTvt+I*or3s}lW?)_F3`qJM`B7`N! zl#7Vv41m|%T%rMXML{X)4^ww6(GbrldzK8p->kc7W$Aw{-6PN>&L$3N08Pz*uz~UO zV19yHS*2)euzgR=>@h3BRO>4&~(>osga@>|$AgH)Vyk8A~LIWt;4stKtl~N5>0c z%jU&1r(!04l}%t}s%m5Z1jBmuf3b55fEFw5azoiQas_@#TFZd^!r~Ve%Lpuo ze$mA*ES3>i4*jBwUsx<7upIhD7r(GrMqoMgi!Od)v5dfS=oel5!eSYL<Ivp7Ugwym5W}Zy&xyR~bM1q!@9@;IsB0Iu076O3D@&2h9@- zza2iAPyGHC-BxgDxUsbDdxm3n>y^-=C`O@Ixk&2!SDSfv9!&C)eOsM<^xmJ^Z5o(O z>pmvS!FSqX$%~YL7e*M~WC{dLD#t+jhn9}fgO+M%<*2>nQapqipb6mMkU;g+{NRh3y;QFxi8YIkCJS;_VJ%7Q$0mv^ne!;iGXE9ED5zc#aM*Up9}(dp|9h(xRc`kW&$uxtjx@5#>+A#VerdK@@j9-tgvt zMx{z}c%`Nhx&~I^Re4ow-B7My?o}dk6pA;$M=>g$L%CNMk)zOx#z)(z*?zedWaK@g zTIDTkWwkQkPFLH=QG-_D>;8FZgJoVND)-N+bY$r~;#M+qwaq#d*ZJ0^r!tez(w|Vo z`q*znCNPe1rQ~S}2vnIqshw6vg1j!6YOd^x3l~kFa1i$%cVlRZz!naat&UI1TckvH zy;~UT$)Hhn2R}P6HWS@2HIZ0Nv8wSam?E7yjp*=nf4@v;BeoVFU^7GS=vrFqc0{7^uM=eHzYqV`Ha8Uock*>N27ES0;s9QIN#G zX?HDJ3(=aIxXU3U^tNNZMrWZw(EjIBMVPfgFizPYCsIaOyx_vDd>V1mCn?^;OHdN(-m3g2x+U;OMG zK(sT$6j>n-t&^3ngc9f_kj8##@s8qaPykW~<4HJ!oWKyPYUrhuTi?P=0TF|AvFiHo& z2+(*LBqyykCA^J{Kr6Z?8&gQgP7kR(M^ywdZR_hvi!7oOUQ)tC|@_yxz&9=k}goOq|%;$Tg z-OT)Gi6NA+p`mwP_mac#VYPh*Hw-tun#D4=plHLEOSsxTU&DY~?JCFuL%800)>ub@R=)}=! zZBq}lwpe?;A~%9DM7qy*6zfsnElpoOYuCVqoW8Dk2YffP(f<-Kl7tmmhsnBXkc*P=!k&{S1T3T{N2II4q*o9Tq_PH{4_5bfZ-Kz+wVNL?1vMtbRI2_3|syz6V#IwU^)k3;ue8E~>W z;>f)YOaG-&obQBEVOzNX0>9|6icsIyB&7+_krXjvV2Rin$_kDgilcZa!KrONO7yWJ zDxm}a<+on=sLC87x6jTy;H`|vYrM&9z-m`qE8tuafK^WyLAJ!J;e~(HmaL_`q-(u^ zY!ZT2ur=9uVF`#NUa4wjDYgxy<#GKXe$$aV@<4^8h*lSkq^4-IVoxkzF zE1vu3kKBdw{MvVLK=S`5IKsJ@dXUMmMX#sl_&Ca;ym@3(v@%qk;uiNjfa1Xc0eERz zb|GPX%8I&le&ZP(GTQ(&F;WLz^LXd9c9i)7ckU zE=%lUwNuzG3#_<5+h?U7#iZ}c5Byv(K5)rQN9u2ZOL;Y|%6)CH7F>#i{-^os&qr_1 zogW-Py8C=T&n}7;xVVdJ%N-+D59JEvl=JcB>FuS5wQ$g@fAJJ`b$Yntoh56L5o)Xk2{5>9%Qr0`Ne8z(3GG`w-BM#697^GaDw!F30huDfIB zC$ZgDSW&5;6(BrN)e&D?jBhn9pZp`~?$X5v%b89t8UL3uot^RWs-7saa!lY^xKfeQ z7lW^q+w@#3aJc#?^jiQsBP$@g?8U%}(X+0tx~Tp(z?0eEw)8(2NPL0PTf!o%vC%qf znmOU;;Tbx^UH_dNso7879j@77x_6UYMnoQZYSdpRepW_GaFZAhl6?&dtL~=M)3JFW zgwG0PyKP2eOu4phy6ycqK+rBPMTon-RSn##7=?!dlYw)O%xrEt zoRw9U45p6<=g1k4E;%Bx3g_W*WqTB|qEdf*v3>zQn}mF2|3wfq0nTaJ_*eaXq^etbi-`&$BOPdhzmBGlBbHYBCx^I5e@srE6uL2Yza0pGqJK z1p#{5W9)03*mVW4T}qbPwgvbUtJHN&6p5s@VwbMPVW&4|U$(-Ex@ZR)bqWyB+zXG4<~A<5c&B?>E9? z4}71w|9L#|g15iU)9zeLZ*MOhD?6i^!xItMFSX%Ycj8J$@AxeCuehM9X_Q-sH~Og* zelx$w?a$HbXCd+?i;GqB#>pt3z#%K2N42rtYS(Xi>pPovTb0D>0QQ)r>>&2l^WF3! z_arf8yYaC4t(_udg4#cNY-1`>M6KY@-ILRUx?NWuRL@uCt?6?ST;=fCU*%Sj{w^XldlC?k zz_%E_3)Rfd%-Z5UD{qQ?7T2xHM4!8e!h_uF+g<20I}?F#Gu5Q2)}GlN$MVj}&CrOu z!uPlkM7ZZwkl_jve)AyiaO58Kz{zR}E0n$;U!)Pr-Zf)PMu`kAV#kYso6LZ(<@?%7bZr*O6kwS$aEvA*JHU0zSPK{=R1?Je zCc3PkBo~|6R&oJCq2DLT1?GE*!_`C{96;YN+Dmz%;V`;z0cI4_?s~+;q_)d1*W5um z(#Y}=`B2_{=u6G?63ecNzmmqT#uTLt6|2^^QBo!vN>q!ZJILAm(IdQ$y<{a$f}dqy zkj9D+3YJqfIbWxA-{kePzSD6ZkbHeb>g{ByNAE9fY?^DB80%5Ztu1=}tlKk&sCYG0 zCR5ulv_skSVTtAW@HKg69%Q{Kya_ZPECHLk6Tu_Z^?uPB;qgW%ysH{&)UJ>dXJwOSEJarzAh9LH!WR!+de;=M|qm3 z=cJm0KVkAGopu!ZOl5BN^pW^>+ncDClKcrls(#yTJM?X8YzgOVFh0u745V#ycMtkq zymGM&44|+{Xk)IGhqvHqxXb*aSD6rblc@McF#UiGoKuxfFsa&VJS~OAotd5d25`pEEKq=Z$$ZWt9;@8%TE94y7E(LA|_&Y`YB#EKTrXKmy{5a zN6qsLW~3`tn_LNaMgVM+dI7bwc<-s?V#8cp-qam`XjR5wzxck}XAy&YxvkwJ?Irv~ zq_kBEHQ9*#vSr5vW~e2B<;cn+j+JtzbVl>)GRa_UPn8_`etS<>%Z%~T9QHZPyMU& zi>6Hl2{oOxJ5zGUZgY@okP5dPWiKbWQPp@fqGOwHK%cCx#QyD6bHT|C5W?p%H@qIGuE+E2o)W9T zkHJj{zp>eVo5rn|F1UB!R)lQT=)>bo6?Yxd)qUQT{%~gQ4{~iR_Kz4!fP9jXqYJ18 zXHu4b_+E2yTGZ;{%YE-+$p}?JDo#cCd|ol~5M0df-30mF?aHh8nW+;p8+$niry+=2 z9Nu&;s@q6Qz3q+M?Ob6XFTCaJ#VPC_6gZ(j^}{yQF7xOlbB7TsG2;vx17tEAr<<0D zfHB4Q-$cgEXSE0E7NU(WTCrk1Mggc1xY3jLY4rPVLL*k9WfA4$_hqaV*T#cL|y-yv$X^JRrzra=5!bk26~c|u4oxKR8r_o zAEiYfUvN^%83Ehm=JQ}D@~0WmA;PShIb0+@&F;t4mVk3)h@Z4@pLe^fz|!dpR+r!X z&MiQ!N|8W>?@7Pq66%;4@9@&%O$j!=|5oVP;&bL|IT*b7qG?!f*?!?u8~18atB3g7 zFjqtO7hmVC5I*LXVeB)P9Q@CQraAN#-fk~JpY+mX&+t|KY~hIam$;;tm3sm0w80Pw zs}g}zx^gW5zbgW{$n?q9djcbU6<#*#VAs|af4o>g zrtu@vs<(Uk{WrfzJG*T&*S5_hoFkPuIKT?;W$Eu;KamA{yqt@$FU@nlWGQ!=Oh`pDTkI&xoi z2>B&u3X~l#@?0ZNPAL*e3UuoN@;h^b)VSwVQ%i|;P6u;xcp0_dme(bZq2CW}LFJ8^ z#8wbF#j}1J?)Bt%f3R2>y31f zTaPhrJD`(Pg0Tnv`_NZE8aNiCISYTqy)QW~3O~v}cqndE^KjDh;gNF}o*ww#^oh#U ze-z*llzq8SC&}PIu)dT7+3=L^Za0#Gw}bx95^ue67~WnX`Zix?ya)0O20!xb!Ko&@ zKdYPct<=T+Ya>p|cge}N9`Uy@#YfKOML-YWmhQe~VZ29>a~7i6#)G8yUl0)glTCI*Ay+@%&np&KdttYmtNzBGxbCDwZz>>@cQQhPd!lV+ zaz=8XtDB{iqzQS+Q?Juv0J&$3%J#`q}02_@20->3sT7h*V~U%>A^a`|B5a z)eThmTQr+_mHOHceVI9bY2c(QmJR{2S`qY+OKF#SD{b%}4Va*S+igj)#OE);AXvqR zHGdBA8N7En20tG-NFu$TVe$vV*w{7l(cc>Y`M-PqJF{q0=V4#{08;*X?eUa#zTT8XE+qfa*B_ov)1 z>j;z-)0)5D6t5(0Ax6b%2y!6Clfx~$rCwlej{Jki4&sgkl6!i7ANBv%i ztfGOP@wy(PUh%wuNmo}VJDoxNAN#5?WH?whlX_xSjgFvnJ5XQ9=ksi$j?DnB_;KBX zN9H0VNy6FLdGiZa>M*EVse#6X?p0r+euE4bd*_=bG)HI{`>`Ckc8lSW?k^zC%i*Z0_knmb=;gS5Nxs1ulFB5Pnfij1YX{j3) ze*@L(xFt26sVB&9`U=r=QgU;+0AGyj5R|G*`tVi1V%AU46h}i;Ua1o+bp70y&*-yh z&H_7+=$vZXH$Z&ZJr?k46gvYZ{P#~kPCk{fT4>FC83!F%AYL}%S2NPsS{69vQTR$B z(joG!-9j5i<|ZUz=wOAWLr!`;v>Bq9G8+SxtWf3@66|ksGcnJ_8VbL7r@C!J2*2 z2uLd&A{)+S0P#dIZJrW7>R|P%8wgE@sh2Cxd?OiY`H)9TpmQ{5O}pp9`T9<>zt@ql z_cEe~q-mB&iwB(K*0@4_ihgl%lqv)ZFAJmfGe`S&Mi7ICb ze|Y*IdDV~P^?bUNRy~El_&-&?U&@r(q%_S>p7AGlhfjqwAlC2i7CE2*^NLg-+m`vjXn2s9=)-#mMDu$ zGU?8v8x7LxwUWh>S{D0+^7Z}S$71h{S6pm9HY0PTz2S8XnRV84sbe{g+&UU>_)d+Z z#Hdl*s6B64+)x;-clOV&_4E`0a`_>BDwr`DzC-XfG<|Z?h%rShK*VHX(Gxz|Fi)wc zo*Ldd*y)H=Bc_TUKyw-!x<#zGRAB4^*>i9x>v zlSzT88v~kKgdLR*A&RBMQFL)OutEQH6!&O zncctRw)=Pg;uo*o{(RJ%@s13X(Fes5k$!Q{-P`K`yA+8eM`zbv?2v}Y2+%8U_~{&m4KiZ zI`zfxO!LT$`&I9of#+NZ%|^b2ukd(HeVYYj*Dzr`6mb9N>m_f~2zzDl1=yC-3wa|z z{N3xucXjTutr@N@h<3cuVgUu4uh}$}@+Dx<*A{CJHu9WHcGDMbS!DDIVD(amOYMLf zc!9T9=#+jw5vZ^V9^bB5Xn(bCkIW{~R~0b=q8D}(1SBvg7EHhbj1s$s(7%@suWP~n z?z06MHJ7@Y7(S@veHPw4>lbqzzpe(h^8p4P@{vJ24|qUmiK&SRGCGE+%x^}WsGn;t zPhY<<@W8S>2d`q*hTKgQ@+*y-`y;>#lc|oMcv);2iTbi&-hq^wHvEL)ylWxr0GD?{ zP-)DEHj^r#0<<2wRH*Q~D*`szEEQ(y_LW$CBs0q^YTL{!yVHEO?hEBFI&Zy%2Hxui z+u@Sa&o5_{)TzAw0>PU0J^SL6GSP+$bbzTt7Ol&tzrF0)az69o?D=*BQ=FNf(MfFR z!&IYme`ki8&R%^WWoCppW+DfBbS%-H6YzksSqZDpu$&jZV-~$!`u-g5Um>TWL!MzQ zHX0e-8=e(F^vTHz_)4nYwT!W=Qaa@%Uiw;VsKuK6q^_kNENp(Vsw&&0vIVq(3Xvv( zGAS{7v=NgxR-)D;L%IT{`ak`Wn^Si&Rju^DY!G_X>?@{T&C;r9IwAnWN!~$zdzOyW zvOYBNypo$S(#(oH>XCN#4i4;XkP?1wc18k{w8ir0<3Q$WJ+(&TY-KGfUwN;L8FlS- z&At>;DFuJ`Zs+B;`UQv6l55~|vu=W0pTZq$L#cb+B0JubF3`WvWA^GNO*k%Y^^3Br z2XpVybc}9PF{-*6#dRsR#1rnV-@P-<%@zRERWBMEflk85>tnkRm`VaSsiDv6PJ~Jk z)e@|H0)QCcuK*;=J$#Z%T8Q@)rsgdU2sjc}XssFQ*-uS)98gmwY^uId3sN!-Wr&|9%;Ih6HG&6Ni##Zi`@U?zu{^HMz>Q33V^KfVDjAU)b_wTt2V@zyr?rN7(6M~f^A;^p{+ zNhNqFDfY&La$oP=q&F`cK3q5`ueiF)!YEzvN&l3q>>pX3K{xVmzS&nL=}<4dagRa_ zfgzXDSvTkPXrd+4458olvT}`17B!iq1uGLr1G}Yht{%<4#tgjn&s7^nj4!PC8v|`k{j0#4efM; z$0vpFk({0Tojg%DNOGV5S|ag8L2LTT)5mk`ROszD#(i&G{&6k4H^G1)FK?74alyPY zd2H4URjK?FL?9S@h+8Jxq6~!`hSR@!+u$ed1>*!yy7kiYT}%|pV)DnH(PK(Znq9Vk zxAPm(P}Pre{P6CX8`M`(8M`8%i?g1Jw>|V8TjL647tp@ooyBzFY?C7$p8 z-y7o_Z`}JmGlsHDvi4fPIp=TAHCNgB!CPrxv-jSKGkQS()Nb^gKPDH<7u*A+W z2(LX8r}EJ%J9F?$1J(<9oHG~(ovp8ATy8fqM+|jtr5%*WL^K^4>n_f{XcBC6<+X{+ z^B(CNaXJS1Z}*@Mcc}08PUCN~iqZL`EXwNLUsP38l!+{b-HSa)xZ&esGhr{#)EuDn zFhby#KEa?~6czPFd6QkUcgu;`w_Qu!y{j6hQeJ+s$!iy7x-4A!u6gJC_38JV^_|9K z`s0dch6j>**0G45ryVgBMO6~hu>$g^=_k!2^PW^^9>wk>x2kFg)0;KL4l)wIb-kGJ zc$m;u%8-$e>wO#Eqkf16?qD$HI0H^UrnWTnzz_i7a~JaCEJI z(lyF-ptfc8w-=H+X;(@JUNs*&swbK}!qfprn>9Ry{Ul{}&PIAdD=(&Px7!D)^&N9MG#z8as< z(JpAhtRMDto@-T#NwIvVJ3yk0$%DGP9H#omars7zDke{xj9E%Wg3pUeuOVi!kk5rZ z$C?N!o7Cja;BBI!6XVRWW+JhXdBTU43!e*kZAS6$C%HDL;5n$epRs@*Owd~etCl&r133Hjuwu2HUO?ok5j;>IWMllwnd+^IF} z^VMY?dpLQ+q^fAUz7^kkZSzC#)GdtZOAX*xDmwR8AXm4Y`*ywI8?RK24V{*_!2!?d zsi68~z4g~T5}u<11vD)zgugt;i@ct^0`x-GPtSU%_v#|RAzqu2);k96UDk2-N2@t= zw~(%jhfvWxxaq4CoHn0}bqHA%T9z90%m%;hHU{NuX6)F z^OsJYtJO>{cXMdb8(+?87Ik|cw@;oIDB7egJ}h@xe%!1yp|$1ew8q<4-5b2~lH_dA z?w}=Fpe4b?6zyA>f{rFYt*&0RfkV9e#Fpx4Ng!uZbgPQ=T^|hx!|;=%#2`r^3gT50 zrH!#$%i0?VdI1#g$KJQ=g0`1cBsq27)kw6vf*rHbIA2uWE@~wy4){Ro?K9@7zN?PU znDnIyGsr-L6S{YY@83(>>fLtgsmjU(N@}M7V1KFC0&n%kr>CMssGFZeI6FC!js0Zf z&KJ~EE$+N@>|E{lcPC+~oc(J#$FSk;y11C2-mB43!Q}n`*}KJdo{INv2qnYi2L_7{ zv>XO{eSc_^_Un~(N7K)eE+1BYXL-5rs<99UHfn8%rB8xZ?mZb9l2J%IrNKx*hg$Kh zsF>>r6aOSDbQ9k%+)l z2)=f49DSebQM83)SFHGU&Vj%>`5im6#z%$(+rPT>QvRGO*C^m&-pQ6e#+63GlY4g{_qZ{2Im`R8}fs3i$UW4HF0)!B)s$~&Aqd*RXaz%2yk+5D zA4z{L5m~t*wMxUh961V(W6Vu)aZ9E7I=}^Ga5$09;V&cE_Uu6Ae(Kw;x6B1xD4x5g zfeo}9g&ca=DXNYt&_u1o;F;a-ryUD6ea1yGPr2_}%BoQ8QI2_V{-JIlMksvapws7& zj6O{%mOc?1;i|!Fp(dz^1DesIPV*Evn*UzU936A`rQwM00sAO50A`Q|PsX{0_iHWG z6VI999<-EhF$1hnMWhaD&$*E&0Zk`QyQqrMyH_nEDqbPw*8uMvkd>=lRs%IakVKVh!kPiZ12n~DW%aOxr4nv>ruKX)APSypbc3j1mN z)#t&pUU$kPEO%F2%l>xFZbYPW3vsKzUW#ezmvUOnfkEJxmTC^9hUFjy`0i7UxuWG& zoubcTfpMbcxC4hYctu($mQJ$YW3Oc%Sz}p}c6J@_I%^dS2w@L(?Rbr? z&dMkp)cCrWjaL#*_?###*Y9t&4?v4QJsBoAWV5aKLr~JvkQKT=TD-I#TUA^fR*Qr^ zUS(-hRyBB*=;`zrncG>%47t-lI;?^sxO>_*wN*BNKzTedOLFa>`{h6`nIkGF=7tluLS<}~pk|o;eP<_+ z@?b0o*_`+vN8XvnDBS9j{?M)WfRgRzYr7X~mndfC@YJHuYJ0^73eaoO%vB+eCkl{y zUw-kwn}2zVD|sNqCeRrSSt-1g&p)aD5eGsb;WqT!J-9K9Y*ToO_Kkr`^;>>@6I7|F zQ~Kk*O|msI3Puw8>VeIN>-*hUF>Ipd>`Opu0i1b~(AqENLGG=Sl`o|z?iP)nz^PTX zdtO!+cAKC*28og`r>mv%L)xV>$3P!J+q}xH$=0jl+h_eQLOr`PG&o_bUoNA-;nrdg zaL~wB9S}@EwK0U0R=%aA`)K5UKIgUd4s9~%4EDsAUE6Lk-L$aS}rCOTqvLv z95X87nj~;Q-|m$8SH!DI%FC%X@TA%=raVhEU{FAF>9gVKpqH_UP)%L=1ZRh7(>*^s z-AA5%-jOzSg3Rc1@O4z)nIjU$4f?uSgV)3^4zUGU8c};VsYN9}FEOC+V7PS_p}4Xf zCw*l#g77UW-X}5p0tiX^E`uKR$J(-3Rk6V5xUi+Q_*V3b0Np|F`h0S}8FQ)^6+B** z`Lx(eLhbxoYy9T1w8(RoZ`2OgP2T5CYVlUnirvsII&87o2dgQTrYNr>a$KGrK3t}q zmB;r2KUG&0s;@iXEv}n6&aP*-t9TjxV zD8weXYH9){ybs8Db~M8JO8U~w66DIc|Ea(7zKL?GO%jOPD$47si;6tVeWWLz-1q>? zEuPxo9arBikl6(~fQO%?ql*Y)VQ1BzSos#W3ovNUR42^C*m0+Uoxb<30_VbJ{lcOt zC5{%h?dQ<0c%D%6;tj;+d-79L#_fOZ*B|}+hIzQPt0(J@13T#4{(2?RL{ZOelvchB#=Y1^NVQf{nH=OSM4)7aY#XiXWxSfUfF0ViKFP#__&(Fyj?>;~KoOM!w ztu)BVSC|UGcv4Xyx*frd-SfQI9{T=HBnYT&j(vLnc8_?)%N-JO(pxRw)iGRHGagnz zm2I+n8zPhV9oT1V*&72)}(t1p%rk=MD zyX(Z5P9K>a=r-WDoHV2eopNeakPu1eWQR^5F1XpBWJWmIB|qgV=Y5e7+JSwtJ)N@D z`VGS3mR^W=IAuz&6v10zB%+_&oC1j0HYkHY-{?w4`DcQWrKRJJojFcAS&w{ttg1_* zWFHhEn%abn>*EY4I;ucH&KDL&f(Xk9ftJWC_soGQZe(|7H~MKLZSVo<`DU#rW;$L+ zNokw$cB9k2ebSxWq2_r8u@+QjR}@&Ifxc++MkA3dUKDV)?=b#I86TTHE+?*WpXyz(O|ZfS~wN{eXa1 z+U+F#Yb2%Xfy>(0XeZOmy?0V9BE0FAsT$`h$EZG+x!Q-0*V_+yihA|>vPs747`{6X zBxi!&B;imWE7IPA#Kd%(&uj!9=)hFh`nfp6m2WPA)Y>4Y-SxF&(|$bF$l^Yl+K5jO zeg7Q{d#19wa9q#mka>G(rH?ZRQI|FZp7e`)WC{EU)9^}9*#1enCFaIk;{B=t>6P|& z4=GpFfuW9g&(y#F^v%$9pM$gwOMG|IR$9|Az3>mp&Z3B^IM7{`FVLHKvaG5`%>p?4 zN|RkJF-H6HI@(2iu#77suj1s6xJa7$3<=u@0@l-@jO&*dyM?LzeYa~A^4<%A`-ald zr8RsY$qDtcea=+hO}3rIwtq>5p5?ZeSvNU8tFK?i?lBak%JE(5?*8j=y+ve^aBq|R z%7>BzSDri5rvsVx_iaEr=L{?4gHQ@nz1=9y;dmpX=#)m@`BOq#u?dAYM*&I1iB=qp zs8#-3k9F~@rerK#0JZzRy9MmfmLlJ6?wYeUCYZy!?iM3U-|tiK*#(0633QQU)2I$w z>CF!1(TWN9D&37?VYFA`#N?r#=B4HJ8j!uN_u>wRQgGW><}VppYPf`cn6T>p7t?pVSE{B zdbz5;0bKwn)}%|YEFztbM|$rxZ+S1#wyV<$sKY-FO5(1tox}NFz~f#Af!ERs#)oq< zr@*0yJz0mg#LXTxOlT9Q7VM4cf&D76b71L+RifW}o6|%5Bb$zWMj!?b-l7Cd^ zC}{i^&?)7p&o7Hy1diOtyu3CH$m+(lDo_Epf(>4;WWQ8I_)S>!f>8*y?PDf~FmZnJ z#@#-K5j|nbzT4Xz=m#ZQ&nldZtVxds9NJ1T^eFFUo>G1SOwaSMbn{fkNV47QY1&mZ z2pP(nKPY8_Bxzl4X*i79KfBbO`s;D_mkjnue>58&3IV;>o3n`}uJ`M6dMRRY8iK$< ztrKSFjH`jPl>M$u8+TjQ5vrGy{I;FaT_-y8`QA)3Y2^O1KV@#x0K2EA_X{rVn?d;> zI6FJtpbN7*R9ANum7Q8BcAY--uKHm$J}X&;%`CQ_-a~T&7;t)*x~#*4e%Uuqjk`^n z!JJZiQkv)K)TfH+)he{SY+yg+J@XMK9%N-0o@rXN*C%ZQ<*%jBvr*#YUEW6@Tl86j z8CeGMWOX(LyK4asRI34<`C&U)u`I?*%?Y3w17@tg6$kd5-`d$t)yOCSL~q~=baJLD z1HyK_BeKDyhftT!56xMID!Aym6s*gHaNADoc9>9Xk!-`l5GdW3I z@kTlO_Ifg*j@^+4zQb+Sm_uJeIE`fRnsjB%^HdO#m8A!Y|9J=*CXlA-IbLyl{Z(8L zn>@ycj%On6P3$d{Hg-ytJ#L_<_6dD+Y&>*XnVo>Zu84>2oY2rx0r_kho1r7wC-}!OT_DyZ$U=k?N%D#Y*CTVOsU?-t?YrEPI!cE1OZ{a4`Tujto>v+cdNntO^N#Z zr_7qtR-dG~b|4L66)Ik5*u8Su(RqC5x=D6s@9c4OZ{)*}^M|5qpWNtSb5lDQLhJcF zEc_0{#KWqiA={HX!$ZxrP&FZrg_2AA`Xxm(Eu)o=Yd&GAAwws0Nz{?XyVza-|p8$9{(G$ti(tVqp9USYWV5;^gDo1ncy&NLJqL zfzs?=lr_6<7v0d3KI3ifM#5Ey_9F(AUTDqL#X~v@tT9rXFE6GwbLf z&Qh#sHVUIR54Qw+JsGGtO~#>;zg%+Jv9eC`wS+)xWUF24lg4Hgr8el@V;}Ecb3j*9 zv3cE`354UE<_O<$`yx){W!kI5S=gxv%UtB;^=Hs}^=$$eymDN!`$SY2-6Z5U9EvfJlQoVfonDr!BCd?Hog{p;U=ZoW+)o^PW4)$JV!eze zn{g;V4_~S)L@Cc-Ma@j#PdJa=3TsxfDYEd1j|s`9o~0KL%*MK!ZL_b>q3erYheDI0Pl5e&Pw0!2i~OV-KNuCK;jwJj~Wq&{H=(j5HATbZqdKzCDa@9qj{NUf+4K1X4CC8L-n ziF^_w$Q*WBZu13HthyW{s`WHPd>Z$#St-{8Ma5#Qd&^-hjE8i_AT`6fx7Eo{-M?Is zMy-6vnvAqhEmZvzZZ&L%t!0*3TJKgBd6O57EgM#&M;tpyW^X9x9ocmLg845#rgOWGp+YNlu?Tpa(c09f7Z#O z)Lr+8AgwA4-H0rhxyHi~Tzd}8zkGx~#Fv$sVfxyRC9cUL+iTEkoVX28A;=ke>r7+@ zGB2HhmvL#GA;GA5_OonAhbLJ@Q$_Z&Fpbk#rZuZaE^A!Jf@utqe~7`Uuc8OX8m;|~ z7(xp*g9Uyy2FE5pj_aqZ#X#YrB|gH^XC=oE8| zG(eCm3QO^ok8kkOQ+?MpfMl{R^(Mv9!}F&zG?QlQV9X>g#h3N?N30nUma+(o{sGAk zM+g(08n-ZEabL6sEB9(@IzuyQi!f!3zy0m_P(q+ERi43fZW%^8u(<&j zU-|zKVDDwd-&EW+nkq-f$4K_;;^q0Y-ft73S0!ru$u<*bT$SU1F)mLA@2%A8sAFb( zosUj(D)LqHr#%uEv$bIbj($;y_v71ry{oagv?|(=tI>MqUK$S(H;D9D)`U^jq7R+B z--drUr{v_TQ!XrR%F>|^w#2M)ofj{olG+akD;rAhR~E~F%#N&$v0E@tpq1{ zuULm-a~(6zkT=A{mSuokE}W#lHZ7--sZGRBsZ^G7$=f5erP$@Q~KQM5( zjm2gdn-l#tugJ zn#-G*q6GGa%?3x9=yekfHptT1TjkU(|LAcJ)BRFLN1|l>%H6X~xGTMq#WQlkZQ{u| z4DQvX-p1;TOTANkSbzSapRzLbmFRYa%$F>h(qH}ft8=c)^fYydlU~EJA zrDAo$k5!W1W_IHr5thMAZ7~@s6zV@Pbr^GWrmYMTZWzoT0M@?`$Y)lu4VOFh9KYjW z!V_AIGYh>c#O@us+vSLjDbb~p3ZtJ=ZZ1fVbm|-Wk1=(1g*&N>5j0iy$}nkJ6wxJ+ z_e!tRi0ANf7W1PAbGHbZRe@RgMiD&?R~11q!veF$Kj>Nk9t}f}J7tZd#b;HxWNXS{ zvkd#$B;<4=TZ~;4%Hy1uS>rxF6zg7$d4Ukz%9dbEL`wvsT4(|ESVj=~OSw3LYR4rT z;>KeP-snyJZvNIx@?@(Ga00JtvOd>xXU!zHUr z^h%$j%P|s?P3CBp4oNEztZ|6H9*18`9glIg(IkKk{^m5EVC)cwIEQM?Uqu=HP@#OO z7fJa9*$ufV(Ua#~zZ(yK{9@!(q4m3Eem+ddUUq88mn|DiPIh`YOwOG{r z!MEMw?L|Hmxvzprda(-|Rr>YUOW-*7q=E7=1fRuSfP8b8bMs#6{f}HA$dM=Hd_HvH zkKf<;8I=DU@L&r1c#O1v5?y(1cFoB%S5L39dC6<`^g)Z1jEu}i7rM1;lGV_iPoJRW zhOh09Hk%xLfA@{&R$H?+HO=!Y@KJ4VtMV*A8-7&1$KUR&^|gWI$}07lx40vmIf$R3 zVITW*uF>TD0`$9xACUaU;x`sQ5cqz?b_o8ZzwK-hm)}OR2*q#m`~k&pEPiA069PXD zfAhs}EPf#Hp z7Pkxhwwe)|c;?5W=8pWm6h_X>KL)O_II&Pih;zym1bvAuj$RnQ4OyI~w59t(630F= z;Hwdfiyq)Vb}-W!G%@DPEHxA6Tvgsx2@KZ^OOF5>lOoNLD*|JLUAj)fx1 z7cm+81E6}RXy-zq3xA*LGW0eV2`)CTcX1MqO?<&wj;S&bl=aHgd|?iFx#YqQBz16) z?7KgJT__v6avW_~4r^aP=7R6yV|dn6QU=ri`~`FSRvBe&`GrY^KRv+!JRu|)X1 z5Bwufa@SS&ljkpgyR_IZ5f&ab8qX>F^#|K-%x^h-O>MC@RxL8h&)Xb1r3y8q<>V>l zFVwQ+^P-9e4`C`WRxK%<*)jj2)3`e=i2MbkhzVVoTU7FNRqWoX#hNa*tm|Szsd$Adkr*~&me9%+p{KZpV?u_koUmt#*UiepAPCkn>T3$)WKq z=C}I6c`Uz;EV4$-Kfi8DU$(f!;_2cG9sCuxa%{HgIK2$KsOnZ^6EIpNWsW z<5>0v`F~dP?A)|4jq>T9MRQa*T~UW*bSDj_Z~8^?F1cdw+pwe-lw;4;h=o~13ORK9 zzj@u;GN(F=a^L0t@^+b+t~W-9sny=DMnUsvT$or?!yT70TJG8w{COOQ3o0|=*aZ!A zq{CB4`)0{AUQ_Cq=YM=n>&K66>ZE&taK`&%+piOuowlr6PG@Su=idYlClF9#<2_-?!YoErRWB zV-(!@Mbd$*u|n^r`=hpON}6xa zQ{F|D6ilXA+9?)b&RTYtlTQjTf+(c^#1u|}?ZE(h>T+-l`G_YsE)vGe#YsOU0?C5c zVTbDNA~p&3Oga^dmDLlZXx5}8CZ*Mgh314YD?T(9OC#aDnVna|%)4E_-}i6bx3?JI$gXC4mcNi{d{lk`2XZ0{C}$L zw?(V`>Ee>Z|C3wtb4CBJ7Rm31{6OFvY$J$p ztK++O4y;^z1O8X?mW_{2#5ES1ItUkymrMiPWQUxJ_N12 zvEZ3GZY-SoFWdi%^H{j|{VzOV>q5l0^v2@ZZ#WjAU?2QXg5$;qNU06c*Kk#1swwd6 zzDD8Wy~Bx*6^o~CiYJYaOa^ha#8BlrQRNJaLRJ(DIh5vdb(Ywx|Kj>(20CDPLc@Kk zp7b)4JU(Q{WQJJ4qRTbVT*PR4M-eFSabP-?l^6PDvaQU>d>!0pqR<^%iaXI;tB;t7 zLSqQ;W(&E9?Ka3yg{z_?P${!9{rKJ0lg8#5{dm6Q+G3b%Z6|@CG%4JOe@|vSm@PDe zWyI3T8m{{GMp`d_qpA{xouH54#pr&*HFkBy8X5iMHZm#8g4S)!ura@xW@WKat%AnG z{x@vRSQVR9@1Z=P)KZv9_L9Xu#A56+(lfAnjM*W`K9r>M%ufUXvjsovrJqd3X-`Ix zbS@J5yd3-xMFaGcE#8;u@4nc*@hTR-O&fyU8GVeZ&ZCbO4X_9b0ci=C6>2)DHI10U zRk1D!TH&B72mw-WJc4#2-eoEh&H(@Lg9-t(A%$nroeRB_B|7!fN%~}_RYfR6o_X=& z#cd^LBiUfiHn7HX64O}~+{JO_Ss1xh@lgfh%qzkqTC<9ycPF)5@ zjSNOBi$+)Mdd~`tHb)K%qEGbRb3)2NO{Y7>sjVpHVF0v&nR$kTVn(bFvrJKoT!LL@ zP%|ol((6SrMo7oZSo*AfxrUi?1q`SmjMdua?Le#SjyCrgZ^AarWRVfb_`-HA@@GXR zmBy@0vr14Y>MC4Cq>tcc&j{nuo+B@@{a!?IEQMhtm$o84CNoSwXvMa%tO%YafVc*9MXCc#lnp%Ix1#R{RG;K$n=ww!l&`{lY=pDabaqL zT*??qf&mP#eE>fQ1a9~qbBOiVJAKp|SG^{!a2AyWLupS6KS~0!$JZH&AM33rA@DD; zLka=Y;9u=SG*>M6O<#=#Yez^0nt2U+U2nZCHUV?XajH@x?8W$#pG5v|hu30(Y335T zozCVnN67?~_cuGPd4e>HBn5nYklET}3_S#iVmzb+& zRb6LU%@zRwTQL9#iPE(bx%}8!B;L%20nFRT^U?Vvg2g^;7F6SM~Rkjk>Hm zj+o=TYU2xm>NCoCGAvNo315-#ij+g59@JO0(E&ysgVy+-Vy<~3R`YXZWd z@nn~OZE-~1!}C-%0HGL8(@!CGuqlLnGh3bghpjVN6vP#xH)4muc_8sZKCfwN8T2TN zUYXR848-Vu{NKd()3w~%3;x0#z?hC<^~F`U_}jXRQ(c9fL`BjjFdqR4?uDAxXO|yP zHx>u>Q)Pl*W}w&PwF_nq_abVk5NiYLE&;LbG$X0r9CoyGL0OHuP+rcP>T5dlOa^y) zt^S#Q>R5<%91mRe4A$Zd)}v$qm%-J42I2U+IBfa-R9w6Z_~+N`RHxePWPodP zKTD>+1A-!T*J`1K{G+Qe$Ik$)hT9Nb6UAsRo~lV;9~qtaasXy5n;D6IuzWf$Ep5^l zwzgkYW#e!XODI3gB{$xsji~T>GM+4IpGrhLQpQCb46o_T$s@;V@k;?qY1C(~at+nu zPevh-gE2q?0MH#!Q=eAl>#4}Pqd5zG=ASV8`Ou%RepT{^s@9x_eeZotsh+_r#pkXL5+> zghnz|lgps1m4$vTnyYC$JL)*z;~6k@%3Z}9Dg3YpYqo4<}e#M4HbYG z)=b<}>)G4cLfyd9Gt9{1UCAYsggRBKS9B3c;n`XY;2poUr>$|~Y88Mk*x#3Fwlj!y zk$C1e&3Hy{;R~p}nuGa76*1>m>RUU;<3eYv0A4b%(IGb($3@eyDj*M)Pe0k^4Mv{_bA8v>fa%ATo0VDTm;P{P9=G?2?dKS zKWKrF3dso#>6)UsU;qF?8HJntZMD3+zStQ=8xk>W4KjA+OV8@?B z?`KTV(uND4(DnT%*lH`|6nosN3GvwoRN#CZb^z^}+Ce5v8Z*UsTh95J>oIyFuF5hB zTg3ya+8BO{SQOl=_OL%Ko6bmf`6FHpZ~(&@-NYT{rxJ1*uf(V{Fl%y=Acw%+y!>{y zCuKWN6BUPHcWCeEjA)>wyRl(F4q8+%uSqQWs8y@qWrBLL9^iYn23vIH$hKQ=M=oyz zgg=Z0+%e0OoNkNyyl#%8CcJl1!t5WRpJ8qhul;9Us0bBRpRSk*ynXvNWI`MbL`N=4 z=;invciq7R{y^h@R9EbGNBt+kLcjAC zKcn#*jwNXPhT}IJOA%Od_Wx_{*t!IYxsz*}zjMGpq4M9N9vAk^mpJC+O&dxobo(fsmLOZ*On!5T?MLxHbpp=VOy?e0Ag&orH$L z(IT5SQwJkZ1pxs8d-m;n>IcDD_kEni{bnxaI`W zDos;OEy|X7&POM6dKE-7N`2Mq6Zy>izWT(9@Wh6I{U`Yq{d{m~N{l z$x3ePuLNh$sFMo$cbnx4_&Zx7ia@sVv{OaC3A;d})O9^_*yWj8m6m(FQ*&?#N+-BS z>H7(O_92|&a$oZMwG!p~AMimzL05wVk|0Qhf4OCkTwp@Qo++?I>DEndzE$y3t9I+^ z>L&U(5Dw~bLCjqe5)wlynwqiG1WN{Dv?EX80k1%|ahiZ*z8yCGG;%rAaUxaFlK7ys z-l_=E{c+{9!MZ!oo;`a<62-n5Z@u4JQUa%CwQYfDCaJNo*|E}V{Z)_Og)zE(NQs&w z6dV%9fL)lh)SYXWby#TVOJjAWsxXit1V;ubd8B5$)QD2v_9lC*c(2#h8)}uBNxZ%s z?$W2G9+t@rRm#zH{N6JEuK3b*-w)$Ud${xDif1f6yKf8LLM?})-@beI$Zy@cbt~DwKvB@C09Nf zZ^!ayHfy;I1jAi(155+)cOnrO;~vae>b0S-iO?fKAKi&!4_-}f zC`!7ygOxuT%1A!B{&FyRnk_^>79H1)xwCdcj50WA^YrP{Hx)b(&nAMuNYk?Mj@v@_ z#q36ND|QLZ&nSzh4>IQm+6Omq4MK3U9xic7{kRioAgQ&7k}`XqHTq1CTxTtZOm?p_ zA8F5#YZ5+w{P?o^tEsPEy_y0GnuVbjTlqqH=8ET~XHeXPORs1AIcXu~fubSi=@3ub7i%!gG>QFpo>Ql~eyco3`n9Z|u$wx!|DoA+X|QBlX{l;VOy zIq*Pq1jQTnbc_2dshg3Jo3{l+aO~X=o5RDy zyZa+7HVGfZ-3tho4{r2Rf$EN`ZqS^5u-f_ss%%4fE>0Bt7y@vY)>#$pih1Z~4Jy;Z z>lNl>X}NuN)B~Lb3}x<9w|{XA_I=&NgtOy>bsxI35BoLC*F73Ump!W@ZeSJDU9kmPZxfl#K zS(p%GvR&-GSm|Me)H=vDpbl&;Fi9@&lk0Ea3p}tEOx4OiqtQXBRVSU!s0;7e zHUie3ZU9rUT+_~3S9o8xwcQDq3(BTEI~cg)69k!UYu*9sJA}}f3d(iV@EnWZ;0ndd zjI?FT5FJu<2LRN2jHyzs&FN`y9^Y zeN@900bCCk6yF{a9Q>lx@Ak8X;}hGO#+fUjL3bAymjl8>=hM#0ba>%o4}?Mf>HN5g z3b?CJ`&7P=rvNqtf;#^y4u|>9TzeW7wYIL>*`je~#uI`DcQiCKbd}AkkE{Q}V7I^S zM$pEo=e-qSfelIe+=D9*)N%QnORSGI|L0(xD04M5^-r#a)Sf%$S-`Da8~e83?l2T@ zr>m#8_Wm>LGBx#4w~7)8$lp;(MJ2-9@|j?^@h-;S5ZtJm(LVg=_cr^}7Kiv-R%`=w zDMxS0@cXkC1rv{tUIT#lKBIBM$kVgy+K3(pY| z7Jh4e1oPRa6T}c~c*^3=T^yW>n$a5g_TlC$5C9Bbna-V`y)gXu!n}kF{Bgb$N7)a> z7@L{#=xdT6dEZG_QdN~+Aqov%mA_^ML<7%hEAYrD731=c+e9EfeE1ORw70dbG6(WA zTv1kc+Q_$?p{OW1skGT1(1?qeXnC{hQt>ph2ZuXP^)h+mdp!T2nRS|1^2 z!CR->^GpFX^GOmkB_ld6PVbpVokP#F;qf=NkiYq+<)mp64y8S)ojDK}9Syu?(T?IQ z6bfZz*Fc^g?UZ5H^{D)MGuWEW-OX*8fSLxCJ}yjo7~Zzf*mxe`gw}&70^lQVntN@V ztgP&c!5zHOToCQ3MxpU)?#>rYwu$tO;C7$C_Mq~3m%xVQ(A3@l57&a$*4FRj>D|=P zVJajp&Q}*QL67+G;R6KK1vM(n>#vFaLh22ExVpN!`_nys@HCX{?%j716BD7%3s=CK z;MJfyqaH4A`ZUqyftcAgu@NDX4&?8*@uua=moJyO2A%T|-*>Gz};5Jn&7v zRovW$LTawzt*z&MIv_aiXAYay9A2Dxa{Agt-{)(zsUh&2uqP1_r5`^Y@F%*0 zz1_ftj1}FPo$ezof4CFdfX>rSt`BN0@Nn`cZsIdA@>mWru7HU0)xaIcc#td|Q~}OW z+EX}xe+Mq~;xh! zun`Xl3riBWj{qT^tG^lqN8fX+{qp4mcqZv!5img5{SOWD8${oSXN!Xhf>x5R%^SZx z*P^=qUAAEgxG~TK(w5;5?$@u0L3Jx`Y;N5RLiCNm>nYwqXumgHzdwvS8K_Bu&=(tI zG*RH-W~&;&-WgY{tpVt{qYZ#$r&4m!qCQvW?av0a$S)f$A=>9~$>k87fAy71>Z>oW zfT9k8W_&VpFSe$8fEwr>Ja|wVNIM=?rdd9xgLC=^ z_gR$4qcbWGy|K)}+mF{;bv0cElq+cc_Aynqat?G6xf-SNSL33sIiy%n1 zcY~%{*#|EGN|}Ln;9&h4Km^<3!KvxvpnaFmJ zg{@PGe=JO|dJ`bTjs*^Z`tomizzZq1*2bzYZ>+i41i=lU@2Q`6J$giGqLg8X+l-xZ7fCbk@k zvUhgg3-7c9C4x3+j^4X>4|3zLS`8_l^u>}8u-L7S{(x*=gXj{9jqh3oMOFSwov-n; zJunpi`PL@`l|Vcfq`Qf)E{J<*znSG~+Su4N2(sB3c-Al{_vPg7mEhQIL+k}JAi~FS z8`TcW0d_uZXb5UO_wlz~)QpZ#Lk-|PB=B(66HnF3mJ4@ULGXq(_n+wvJ3#S6jqQ!Q zx~x9l1wz~SL`O0g16tLd=VI-z26|85L#8OH=@xI?{1O9$kVRdNK4)wUv_)5W@7^9b zpRELFB3$=SQHKR6zl=f5#qhYDbXg8sL2w{%a1$05b`Il$LX<&B8m~7h58Bxy=8@90 zpl^>P7uf9EsQCE<@`fZC3B4~*=F4+ zA-1tMUI_A68sDJVVyo^x0)~o`b6$l(+adpK57x)JS2umuK^H=z?&c55a6#ulY`qZL z_5alJ;}JkppaVieLaX02-QwedOcRx#^Nz}Zg5@>jm={ub`1k-cD-=%gJqrdc0oqVj zv=TG~z3V%UaIeKU*74g!e>JG|Hu*-Y3m3M6tr)dE#oVr0fI=^aMXT*wFAD0De^-so zY3CBcqQ6&6K}`*~rMZvPBBPY>$jZrq=Qi(yhoudSX+Zudt5&UAX0Jrp2M$*#y!~F+ zeg@3Vz-jsS@`HneS3po>`+Q+%erjb?QxjNqCHw)jz(D+>SmOsZUg+D->_1Fu*lc6; zDkS+PeeL%vx)~dxk~5{HrN5#N8ni7iHnV99FkA3(y${&94WtDi&@At0wbkOwpxk3M zHD?!Ne9*B}UzFW^wb*|iuK@@^gS!qMJjexrww3sP&wt{VFT;;6)Fhst4Zva{CGPwS zOtyG!?!fHIwQCDWCJ35c=R@H)#y-Yz&3+OxSqHnCuv+%~4WYhs`wy%u`*3>qi6fD& zFDt)n-*IrgSxanrwdaody|1hZu3s;=eUn|Kabb(ic*#3u9^T-l`-jd8-C`Zudh?QW zysZS<{*kVl*4Nn01eJ%Qi01Ii^vPD=%LE%QRZ6T6jxq{z~dL} z{lEIwYJvYW(KTTEf0(4Y)!^S@{PD;Y_ZI@kAJ>)~`NRL<*r|TWZE&(d>NgmRnEVD~ z5sKfM@e>rk!T1fv5(Iua`OO)>!T1S*pAP>!HRHdYVsH05n)!kGe<$QWO655E9oqc_ z$p1HBsPik8Pc6I*n$};1rt@ly(M?dZ%GTG5cHvSaUFxdpe0-W?baf-GYU+?&R}HvT?(i<-PrO9VZu4VsE9ExqKn3~>6_-LcX0V^# z+aV5mSw3y7;cC^e)VQ$5{OJK%t&C1nO{8pG78g{4lIhafJu1mptUOUd3c@>}Es9X4 zh1b=;_u|?cv~gMTf}pd7{zTCM*TkIc@Q?W7JbMFhf-WR22DNHSdfxcG6^i9C&Mj(X zl+Gz9_@pOh#rsgOyQVKq?ZV?&7?QK& zyNq33vpey((Fjehrb1WR>Dq%%Mdxi9NMfeWk&IqkU!nVe zeH(9nL4l#ZzWy?3YrhYXPU#3cFkYBeL@LQA6sIA>C$UI!Z%j31_#V;0#zrdp-Mhaa z=u{LK*96(ZHOj|zbaaM^L|xEH?Jo56Qq?^%lTJziiwqLl5xWl{kw{xRJ1{5ug`$E& zILMqDDm)dTy4pH8=q_Krd=2FP=fLFTUo+!6;(flOfiS6TCbkqAk>%&1#npd0*3b`M zWvz;&WHqcw1`@T=Vu-wP`yw4U(17MLNgDRqF|A}{nwjCyh(3v;t667eL~>(+5OvLb4z6WnpOH@PBx6RpoonIwWH0(wIBGMPE)gB=1wSPPv?q94QvB3%hpV6 z_7D@RYGr<$R26jU(1WXnjM~i!41b{v!N%!W8#exJ6<$K}Y_tBHY~p;#{V_l?iOqua zHf9srrD(rbNF11^44MU!&KgaaZANhwX6i1=To8O*>&W;P2Z=KJjxxGr-1lU4$@KL) zQ;=H_zTgPJF6m8spP_(vfz9rJ{d~q3Pk(7(e!cS%Ld?iCqRh=0t&Z|{UO=S+z*MYF zSIOr^KW#w#^~lK0LLKOj9$olR4%(8XotZvJ95WX*xv29a3Gr_LLFcTjZ|q2RBc{D8 zE+WB~y}pXCtP?L|qE@W~;`{_AhPm+`OFox7Ntsj#147Fdq84=&&sAz)&lT+-Rkgtdds zgQt)bB-ZnehPu2TdvYlBY9oA@ma z5g9wcqPcN77k4qSo!MP^Fd&U)sal4*VIIb=Y!c+#b20Sq$~M+F#yGjOX#L z=A+m(QQw02w7yUikn}UJ$XMo({W4(}Mh7(}u*=(}(+v>6*9A?3E}-Ggym1wUNmr1e+|DROUg>lLmekQUr_D41jOtKZCX&N%P?Oz+NE@M6;#2U_ za4!JHfN3H1Ol`cG$5r*2iKnap2A0_c3;K3xp~ga5tsHnN zf#v(%$~3SMdZ?y z!oZkXm)`zLvT0vSle6`_@KT23aP%62r($mNAX7e9x0m z@Av2Zd3~Xd;9csd2nj9<{w z2M#&|mubHGVg8}|nnP!sk$YjP~Fyu5b(mP`8{ulIW&=b*L6 z);j)H&OObnD|V6t<>4{k#CB=4mY_e;Fp<;Vkrj=Op?8R#j-536FAd?huKv1YUhumA z`)9N7`21rsq74fU^8auP)IXms{KtGotN+cV{jU%E$2>wi7J|I~^{4Z%m%x}u0{&kD zV+oAk5cutKi87YJ_zi*IE|(}{35?$m`0a9uGM2#j4T0Y-mndTijNcIW?Q)4SmcaN8 zf!{8dC}RnX-w^oia)~njm%&IoSr;ekVUTaZg0cd+)*K1uwLgtatF$p9oC4{>ue4rM3Q3T^+u8~b9^liE`RuGJ?nBb|rr8DLk z_$j^haJn%Vg$i?+{5k9dNfnOM^0IS-2Ya$h2@^S;#oYxX9_eyP67FAhNGk^knHt=O zubzZ>mq{ZA$(3yKCM1cc%9+;Yky&W>y(~1J$GeyMF5L_ccckm%_94DUIJDL9{sG74 zc40Vad?xa+?P(-h1~ObItEg=5Fv~AeEbwBCzp|`}Ks=B{e6%XDHdfMnaV; zcq?09nu8zP^frA`c$I}Zop*Yi>`VyGca5!?E?G|U_?jI~D`4DNnOTUJ)4d=EK66K8 zRt9Y9q$jw zZd$Qz|LBZKjrOtTnX)&@ILBy}$I=}$x`sRymo>4n4prlf$I}IyJx7Xmj%yuqo zlDjZU)2Dk8J(*Jnk!gi#>ap-GoJ*>B?oMJ4QKREcL@!aVy1h+AFJe8>7^%a zRTKhK)m||*l^ZAkN6yj*YjJYC*Z6yl?XHfO*m_ot`kM3NP zpFGX9kAw`jxy;B}A>^=vTy|H0*Vrkh)Bz)ciOp1HGs&<@VLR{EGfEQ9jMlO`JagkA z`_;!%-~a=$8;glKXLU(kAhA@W21JtvY+>k>we$M2Q7ixPng;11T;{ zB)#t?FT3DZKD}?~tDV$aB+u1ORY9tNKUzl$xgFRJRq()FzfQWZ6Ztq-mj1L(j`*;B z4dy?`1kc{3Ob$UCp`Xst?}vh$W2K#TEu+2?Rr@A$VB}cF)Ulhv3a))NjCCZg85ePa zhY2SB)5Q2sjYF~A0Jl8A{c-uK&6*nY;`6vnM0z-nyh{Q2UAn6ba}h!>rUYu5Nb7ob zQ+0*XMZJd7Mo%UKD5=EhUJoQps%9U};v9ug=>?f949~@^+Dp3(GEc}0KRPqp!8)%> zx>pBHrI@f1W$*D^7U6yPpyOE)hnV|Fg&`xS(`dlayf6Q#t$8+a=Q*Nk2*l6iAN4|L zlGKNG!!xtjLMTSe z!(O(3?Yx;^v09IID1RBy;uI-#6V*wtCs zT>zlvdsEx1ST00m#>BQL24bhpGGGBRUsV#7GQri@Tq5>p(0`&ec)ZuJ)NLZ)Fe#~( zC7Br6(q&UTI02%besaL3OPcq3Yab0aFLNB5gG*d~1``~c2yqI;X#|**_E2igv~eM^ z#fsff5`m~{4qI|fp^697Cqy$1rlM(lrP z9va2X%BA${h3tmF@N<;$7;4#<(vD=%O2*#Y7zAnh?72*m;q*#j8r&l`oNdjSrngV4TQXCB!$N^lvvNt;T~AzRPT|UpLbYg znv^QoetyA+M9sJkJ@3lLo+7t}#KOV!SJq9b$a<8d5v$H2YOH3Ton};c*tYf6UI-N` z9qkzy5SJlEzq50EUix3IU(wxD^=-%gT)b3(U`GK=d_5t~eS*s=fFX4`CZAt!%`|r=rJN<=UUm_e>zXO@t;n0^8eFuO67k#)amz6M@u>X=}f2CKSg7i z9`BzY@_+sR5)exi@h4?0QN$8OEK$Ut2rN;=pLi_Eh$V_xqKH2cSfYqO@mP`(OBAt0 z5q~1EL=k`Du_PmwC}N2s{zPDjBL2i7=D)|yuQg=aew@nV_}hEWQ`Nz&lDy;H z7ay$+zI#Pr1OFo__Rl+748+6Ue9sZv%;m|5>InKQ`{{CDwRCHJR2SQA-Ii#rOW*4m z)sCH`lmFtWgPZ_?^QlGuq|?P$|JQt@mOk%pP}+@TuSlpr31@&ye|ELwN=sv9qScvD zK2JvUWizuCFL(rf!sC%Q9KPYkr-b%y=iy0oDU4nm)!bFw`o*&}etd7(IoR2=P+^dk zSzJ*ub}C#c>~#sFgR%x}Vy%FbZj2?7gLqkf9`E=Bax5q6c z#73DiZ$xE7p5PU4AVKw-n~*T&Z#d39`J^-8T(>j|^;i(ouH{G!OwNZ-8aQz7W2F?6 z{J|Q@F12D~QOHT5Mc+zc=$<7x$y~u=6_GbE?KcM;=8rKooL+c(8Oy~DD%ma7H#qq@ zA#!AoZexoUT2xe&`v#=_++FpyR`>k*G00Ee{rJS^i;Z6(lY0x{P?C?!nu; zvJAJF%x4N*6TKkh%-{;95QXc$KJf&GsXH_(*VR zn5@Oy-Xw^KXOkq;7dT+)N;>dkRT_?;po{I6I$1$`Ys_@y0)GxoXLG z0J+Uq&r@SXJw^ARVg>545XC{IW9TYe&b6D19-yB-oDW^8q;oe@AUW*L17R3jTM=N6 z-UprI!9u#MiSP{Vhj$L7mcqxL?wsawNUabh`d7bGV|kG^mh%Zae9RVanJ2iiH$5{G zT*z^rOk3f1`jDJlFVJ8ID+;BfS_)lF-7-bS;c#h?DIQhXbXhzLBTIfZPt-nIi?{lT zhME>FC`kVs=XoG(q7`SeQU@M?VirlK<6>%!T|oNa{@EdD9GRw44%JHV*-!H-d5EY^4jKXmz$ zmzR8cTH3C8^!6-Lnx@Uh4g9k|=lkteNl5LFru$;$NH$tjC35Gc=eiA$rhEY|hs7~k z3*GKxxeG*3T74V&3*3RszS4VNC_SNs1L~m`Aak?v#`K%DqECS-=aEA7$b`;!%6iXA zoyIEnn7YZi-S+jy2W6(O#dMMY8Tp3hzt z6zubZXp?>8-?M8|3z0r(+5Gkls1`8S;?x#S4EnTgge>-`CVc|~NU)d zw#P#sly0>v0s+l7rE_?o+S%q4ad#OHr0-8T9jz`nG&J-Kq~>}yHtkG8)ilsCsT=&! z!RzsZE`@sO+a+8ZTZWw{^%^_i0jq3mY*J2ZSy3pIq?f&j~WdGF`QQ_gIEFN^f=bip>=Zck;3c@5m4o5nQ%R8O8P!BZ#AhK84H7^z+jLGW2u4kA3%2L@w`SfyZt|`+ zXGX=1v$M0G<~<+1DsC;j^mzz&ZFCx^+OE}5IK7X(V^05GAqw^6}f98oU0Jmay4<^yRAX1&D&dAHkLs~ zYugin9#EVjSOg#TolLd_+&ckrU5l9B=^gW>2~enOi{z-k=gwX)nSEEqv&Ja|f6SCUZo!X?kE(s(ZTUd0o2=(fzy9%_vzCC8Srl2XfTz!&KO4UD;en&zuJ$SXdi9hZGo-aIyJJ+~v^RJ!rrn|BDPeFvZ3yZE z%YskZI^OS#JJUr-U~?Jpykgj4r;9N2Hgf>Q-v*7=KyVAtI)74atiS{_u=~0njCz?v zfCnT93FfmNij+jJxdao#v zF#9CFH)iGQ?%vb)aCAHb!=HN6850|;(Q|*p7`LR|(^=KR$3)-IP|f0VN5`42DY7dN z;tw@U?Ck6-+X@Q{fx)FZ^SsFY4rp6j zMC_PsjrVg1X=?#;lxlsZ8v03Uk&HM)7IaWkk_4B(PGe_h3{)mSiIqV)39dVuK#-hQ zqaKC<%;Pi~GKwOHn_Gbt;coV?plXPCfK)|sc6JalB!7<(;8R&Drg%ZT3QEUz|0OqG zSQ?bhh9wPO^`cZL)->lD(~X%b>+{_B3>4idQ!c}s11}90cA-0rOl)??ZKHdKP|(F~ zu2~XY1agt1(6SzBI}>e{qxA1Kn(_>;sF=01P7``bNOtBCI+?=gDxpnddfta_MyaJb ztn5dGPt=MEFJljeF?Lt!dX#3)0}j!|Cl@c^G?jfm+RspPCNZww#UiEnb!7uK;#?_v zzCey~u?vlDJ-+Xb2O7k3`<(hVtz~+)x~Art!*M8bkW|rr(kI(0Dnb^2O&021H1y`> zyOP)XjBIz6-yi$7x5SP!);+@>6Lsi1sfExz_8I<3Sur)q+VRwBkg0q_Hgc6Z=}tvYAp>6TYXcvM#k8Qpw_j%{yq*dM9=ViVrs8vmPj@ng%sF@_ z>bgv(KXGJH{jrltS34#v37a&lCroBeZ0FJECbi9y#-7s467qlCQ$eO{zud$y ze{7N>Q;)}2nqp-7AeQRSKhs&VvWGi|GBcn^Pq>Q9TqbtqVwj57kjosY1bwHMmNMLj z9wo)fj8L#Hl!7u75>F7xdrf#P# z>G=tz+r41H1H72u`AT+q9$Hqm%VB1Z*p+gYqg`EH8YrDAWb$m=d1#%T#!RTho6^1Z znA_3XQBW=^A$Oe1=;WOh^3wPf`Ybg$P#hO{C} zk0YL0z4pbZ&-XuO^Dl0zFqNc+Q;5O=%Mq}M*oAt9TnwTgH(wS%tF^K6^B@%_U(+9w zF9Bn0LxX7C(qxz0v7iN%ox}?!m$edRgps+t(mgs-S{qwR56;|eDy~~Xt+_h&`PQA4 z3Y2@b@}cRJlIF@e)Ze-T#vZQ4GzA&;X$8ZX!O{;1Yo#;$_2rVAZV3+A1joom>FJsOkNRdQZiLZ%fh)b)R}Gvm12>}yI$MCd;RNv&##ZJ z8qGXMvr;D~-6`HPcGz!t+1yQH+q|$SlG;gDRM2-q>E!#XHJP#71xu$xXd)&PZ)*i% zx@48ud{!kO#M$Tk_0-6y0A2d;NyCGvAk|{fQ3bgrAy`S^7DMIwDGBwOAz?`+YP;eZ zO%tfj5w;(~G^xrEFkC7+GcK746#SNKL#y43ooeekRc1fpC;+eV;Ytn(_`RW}%Mv_( zY6n^k_KJF$L1?7*`uEbnazUl9_3Zc0OMUyN>Pd)bvL2h2NjgbwL%>_6FQ77sPUGv> zM_@@>alqC#Xky6ERMrrVAn!TiQ11C8AF81Q;wb{lkgr3d?bksBG)<_DZ(m%a5ElT`NT z`o_>V#-p&t@uY3ax=tn@bAW=lYT(B%Pn9G&Qw(F)+7qJ6`lJo5TAC=lL5+5HU z%7!wPiH5nEYueQtK=n)x;e(=Pnj#3_C#%=<|B(gT^+%ce%z4 zvm6DOz+>`V4AxoHVC^~s-=8MVgr*}C$VT;KaQW@02xWK;Jx?f7Q{1$sj<(8_%c-yf z$@8h~JpA=NGb)IbjxAx(Z9Wg$(5X^BStp9um9+%LuKHMe$MgG}W5HiD@WKD=z;QR%X#s!Vf8Oo#z)p?*W#vqvpLh*yHmEx?8`fRzo!n$$4x zbSIZT6pBHm9*~ud1a1j(E<^;Cj(HSAAR1AWXPc=GtU9KH+}QARwwb^RN`}V%?erp= z_ekHJ7&bq3EgGjZmNLCeI;wQTT-)(3$@l6{V5h2z5&kXjG42N{6#qRtg}n44S<19Q&h&#?8>JJiLoWN?{E+imC8WsqH{o6>m8w|P z+=J&Q63>i`3ZkN7fH9MH&YA))ofrg(B~IpDA#e_)KU4!`@%ac^gHN^yY3K>4hvjR| zu0jbwwnok3Nw1(Tg4d_0kx4HWKfBK`dnJ}JLVo)dtwtugCXGNq2bxHYxPwMhtLK7e zR;H1PX&Te+Lr*8y0ez8+d1u58G$ORgle_$CAzVFXavlE00!x}!ZrQHC{VlMC&+Cgm z#<^{0POP>Bg*yn|PDhH=333bWO02)U3z2(IUf;(*2EFv-{g^K+f6c4UPFQ@DDC5yr zrx-KzJ1rBRDbwmX3X>@n+0@xbcVT)xSU5GNy(ikbiU2m4^TMCVa*vJetzCY<4OEFN zDiw_tG#H&48kfz@q7Hdbay@yOl|w1I?%V18=(I~V#ww5=R&HjZX0T-_ z(zUd!vRRfkE^X>0NL3l3wG?LJHi^YZl83D5jLMRgy$uLIZ37eHPS(2R-NK)LHi%l; zOiPQ*w1;4gs~G$lR?E-bj|agNVy}C2sYaDyZbjQ?$T{2dJo=C-UT-L`@WDG(%+>KBr$xCARS7)Ha&B44~KW zHtL#`JhCp4W3}x}Zj!Eg^@wIml-7!umV&CVN5dQIGVNXiiUQ2NaPnF8b}9j7rP0_9IgqdDKt-xB z^}fe%2OLZ!b!tmiCRHH+s8Y!>qe1qVIo&;|e_yVSkJ$j-R&WGfmHY~pnz_PF9jBPyB8B+09EZ!g0fOlCr==xGS@hR z7xqVS?DucK6OXI`!5Ed+UuTV!U$W0i&MoY)<7z@|E8<+UTdyx)%;GU%GB8Heonw2r zsyPuef$0laujQ2`K)}lXx;M)7!f!q9hi=~3YHo_-1LCmH0&V|3)Ek3CaAhA|TwS#{ z)>tB&---u^c0;fI_u(HON4BYApFyYHchSKCp%OuD#2z!-?70?qi@2A?#nJaK7#W>G z#=P;bY}j%tBLzFb5(8?fSXeylgf+KQ;asB*ECzqcXdj&GrO)fdjiS`L#lE)QU4}Vu z60Nhba?c#5zu&*UXaAtUTYCw()hu0ri2{DlnMN42 zAm*unmH5+qUw4ezhn5ivrywSn9L ztF!prvYO!E(8Z3Jfdt!N94C%r+qME_AdhjEi0n%BGyZ;k{QTPloB@#fLn| zgqN2q@7qZln&Xdcq7!Xkyzt+*Qr9H^8YFQ=RkU?>s{dL6PGCL2*aF02zjk&-I1yOb zHk7DkRN|?(Xs6!%o2_T>jjaz@sdxD>D+eG8J;HIec6Nzc?@`6ibu%rOr-20Rv9-0e zaHb^#@q8kYtP$C8SY6EA7dho{(|>byzZ^iw%5HTEj2B$zqP}>Kw^%Kxg{(UlPAVin z<0Ne~>pzi}nVAFzE+CGc_Zx~=`)xhPnjRoF#+eMZ)NSbi4H%FTckYbRLdbm^+R-sv zQ>+J!f^%l~vf*+TODE(oj5<5jrLiLc>_F8M?eiRFzipACY!QLE{7U)U~2BZY+zt;i;3&l zSl+r9U~@=7W*Ew{iKiCjrsLs_p7-v5OT9HZK9+L}0m&2?`daN%#6mmQD(L+^fr`_m zUmrkGKL8$SWui!$F;)CGUjf?cGK4P#rty6UFw=4 z$_ZNi(|fzW-{WzP>e~OQ$L?^XUajCbpU)L51;t+@HbvY-`{v2H8`w48_#)?^)R6N?{<=p7M*7&g;aMG6I7fWZ3*9P+~d@z1iTui`v3{qC7 zkpuXeCzQ%wZS+-dVU9oMb=-Cv@znj>@tJ99m%5HAiHX(x`V}vO!MNqo<{&TpW@~~a zfv_?WyrPu?tGAQ*ORcgHks5QNczbpF%Eag{$GOg;ZZ7b+6eK82PI9<$@hB_PeZFR< z`s-JET)8Zu{2ul3s&YfH2Fs7t9lN6AE@jTz^gwBh^9R@R2^3!DNWTu4)*H2%%iooZ zd^Qh!wsU8l1jV6(b}xe4lxy|Tq^k%3*&(1%b5k9Msjm748c#{#*-&i9S}TDn+aBy+t6DOwj_axu)9weRkc#uKZq z^j+_#Q~u^0F{@i;AF{PlV-~0P-XjUTQ^`M2gAZ3dnQ6_O(r1_29k)?x@E2Sr8^^K= z3k}?SobU3uvc3#Z$!%-uS&5fB(*GDX$&IynM-I6APt^x{H+r&}l3*Zf;Hlm)kU*P@ z4rEztavqt)$|7b9x0{_z_do{tVFu#w@SIM#U)%4Hc`40dsA=ma)7Ct15LrkQ3!7IO zd^WE>dI$4hhc74j0NFlvwVS_BIp^3$8`ighW*);Ea!^sb=iu0}@|KJBf^) zrOTfL1Wd?bS{-&J~p|q z;_jDigM)*?zPR$Tz}1)iUh5qA$zPIjlaqCS_d0w?M~Bg|BYT^``dq%|;_^fl*2W@F z`nBD$Zxc%JcfR*JyBbOHuD4R1^u8a#T{+gMt-*3{*}?j@5|^O}9@l#pf7mjQpseoL zXg?Ntv;C{gK}{3zcs(1#xw^PydF|stS)ChWhV|ygK@m|=-A}bl_}f_%t@VWg^7}}( zMBeM!!WpV8mQU4?6Pkz~Ma5-h{Y}lQkT+q+jU$HG^rQ9Hv#X~#=EpfjkDYPPiP8A= z8YQuDxy;T>FYj`eZ_E@mVEvGpz$vijDkK{yc=uy~Q`zJlgHjs1&c+B_!4~(wcv@g< zv)}C4kMbEfr2bReS4dgYyoJ=-)7jdJI6pNVwKgk9%iBqrGpoN(XdAL5?)mC@It{i1 zN6kU+PnWXtoSLAfWp4lCcj82PNl8hJ{8tZU6G%1 zs8c~oYEcaiJNujBaCgV3!%9R5R6Nixyl;8}#DB(W6~kGNb=jq#rcyoH8uG!gSv{*Y z+?q^Nyf8}q(!Y^?vq{B_iP7=Zfe*K#P>}N-3FyG|g6|hXH#jZXiy3d;IaI=oWLG@^nnAF=yU|rOK8%{3x1s9%(+hQgRWCZF;vNChL zkuZ=LXxZD`+%?*@+b-UrERjQ3bA(v}-@vgY4{dGYn6u-G)3u zuw%Ker>FOk$71hu-igW4+9cRoyt_%T#%2A|wiZ!nTuiNbty3G6$Eb_$gqlW7eGp`@D#z`{#AZH>~Qx8U_RDs z?^o5Es>|}A{N*~@SJP;RLRa=pU^t1%gQu)q*A*VLQGDjPxluThG#i)z#I> z`GUvwX4IFVNc%{wr_yYj+Jg8MX6Apamat$Ro`36K{_j@!_a*qxulOu|;}1aockm$2 zPq)%X*ess~nzz_H=m8pC<7M$pY{tMMK&9YGYXIB9K2=!yEbbN*SI5E<#Pb;T+h=EK zR#sLP2}m98evjqE`^)vj!ow#8L;ey&2{Ogx5ekSHdQbe4R_<( zh9Dt|0~n={aFP(Z$N7H!`~PJA`7PwXe>V3{|I#=9fad=#Jbuo6@38fYCc5+f zlr?_M6~}%qZvVZLTbT}vjjXlY@D~@m@xv8SFJ8Fbu`l3j{i|`OPuLj)@2jol^yaG* z>HVq`>7<;Lk==bRMi19+xg@Dve&ofj^XbYjmai8MXWr)XY6~l2Pn^~o*5w?wGb)&G z8%-?n`LUtWqNTj{KOT-f^u|j^ND~~R2Mw`*VEC^ud{q8XVdGh}QUB|&f4z3|T~KQO z!#BD&F7W$*_{RTEN8V}AI_KdTrboa`lp@FYo_))#m_aT~=()Lyt+KPT^A0wojZ7x@ zhbfj~XS|o8OkJYo+_Mbx?Bm7K8b77r%&|korFc9AcI^t2V9mAECJi-qunxxYxK0a8 zzOIgx8=hB z{WcHO7jL7DWOhnutE{Z-r=8L4NjJ!?EcT%Jii1aBEsL6>`^dNcoRSiSiR{KUkg~n_ zXxRi%{52_GcwBd}%Hv!%2dqbxIbp!4T?NLBHE(S!kMPL3iA8MwcmUg6?e~o~U`||( zD*N90`STvwT;4L1kZ=*lxd!C-)@nOS;=qcUJw)&xBCL5Un_N*0)#xU03mp~{U6i_F zxbamI$|2v2hs(K-)`K&F|BR-2h`-era(=Z(G4h>?>n zc$OF-@btjc_ieWjAd9Q75OcD!M!v*8!`oTg@%aOQXl!8NBOX`MckrNx!VHGD%)3z% zV-`u=@@&ZW-jEf%pa*VX2ZP}DR^GZW`*Lsr=x0EOwp)i7#7a9VBkJ0|XQ9P8Ifn~Q zyxF>$GXc_d`q+J^SXkNZito1sO)rjnz=0T2ds3M+RBf9?nCgCg6DVf3A+|iY_uO?u z*7qJ{&?`JZ<=ji7bb*aR$u(j_czSwzea%4|6sjLT&6BGHVV;)-%?{*0D`Bq#4^GEv z#lmpDk459$=%V-}zc1O4hfYZy$p!z&!$^NrLw~uDsc*?FfO**+sykz`OnG4Lv2GtG0A@?uW<8ro414 zbRG;T0k@x1S~$P7W1k;#J*Kpv42KEu3N1XrDocklNgwSU8)!goX3S19%Ck4q@c<+{ ztZ$g(W8@8Bv9=S3dc0IBK<)H=z{aG=tzH=R#A7?;$*Q6I=|#JK-Om2$Z&Q$qkCHyL z<~Vj@APH!yE?qbS&on693^y(a^-+!o4@q5gW3yFl%=Q&eolK75PXl&M*iocp!E5M0LispG>v6|+5Q zY=vB@LI`~^zlXor**laj-XUJ@+^=z>*x!NaLQh{mqMsr+HP&F^<<;>O=SA)+H37|U z{ZxT@MSy>Rd-dqg-j~(G$-Gr^v6K=9`2r2Rg2QlL=`n2ALQjDOHWWtxCa|N(-BBl- zXBXAk?qw-nUF_>qg1k|sbTkaNXoB>-_w~(HD^Nb zGhw6F)(wdu?8xz?X6AE!FSiM9m~x1)FHa<^f1Z43=1-rgb}Y=#*8>8>Kb`tKW(?Gb zVLO6u>#-vd_&nNE^3m;iT3dv>Cb{zuU$!1f9EVcN*;XonuC{^T^>vfOA*?CrH8x7D z4bAj|z|JC982PJE`cPvlIP`!HXImWGqBg+u_ih`OJEu;n@n#82s|H2B{}{BAn`fNL zVtDGuX{HMiCaxlZXY6oa{A#8&INhhGr$^OMUiji#pO4Ig-=s^xJ`YQ{TMGSbAXRI0 z_vPWvVh`Tt4pWFomp{>G+6FVB)yjH1YWM&4E zyv%@k->MFUzAsPtIPs86R>Cz_o;)_7%|<&{*w>}#N^Vakmv~QM&IQVj6AF)w=kXJC zGK)q|1IKa?h$J13(y^{;NYdc&7G5TFre<t&6sJFzR+bxDoB_nbN?-ce%#BQnj^u}=li z6Ytq*{*iHSPYy-x2$@t+%)QNO`Vn``x+$W>1=K6Uwc_$W`Fgqbc)6R((eoRr-#MRV zej+f!%C4SV7gp;uX0&#e{yLia=Bo!lh)^qS491|mkOxq86R7+DVONvtR z?@WhBVi z-JE# z_YnUEBQbtIl?^gpPyz43?$?@nXFG0PS*L`J%CNa0Zx?2Aqi!H#htmZ^BYdd%*_%m{ zj|tD2BiD&2Hf-7!G4Pd4xCHJfM?Q#jH;28+FLqGuPfevT@YH@2kYI&x#0 zpR!o1@$%&=c;`^-1=WkAW8Zt-{+8(dxxHPu?8~oy<{czNl~{*5S};7?jaJ15CJb;S zb>fLS1u@}O67Q&k1lgf3Nn+I2QbW$j^*w2quR6UlJ z_jK^tZaQs*;^Lg)FT9rCoLh2kx(Z7dUE!8BS@fbidcTE3(5s@nkFek$M1n+2nbPpe$h(eWtGGw%JtD%vh{4VW2Kpanx#B zjqZpg*KRR&cv`{e6~*R9$vCGvJsWVx_HcJ`Hg&6PQU06op|LsToVE$k`4BDlIc?-D zL%>i_Vpx_^q+Rd=+qZj)sL<)5X0kh1PO|LvwKJ_vG)eJ-sEy2)$4V&JVGiue>;k(P zLt1X>)KI-mlU8-aH|K*mZ&GDr=D_A8e06PJhBw|KWS3cv(Rhv{{)~ofsXFh|!PqKy z#X~bCEjm($C2XVKoKDZ4yDOXKM9F)&jBTyVu&n&l)7jp>t>dh0AC#SVqc@}DHZIo*vP34_@&FlBZG+^KAME{KCYz)?8wM=h>yYMzxc6Ha6@ASp^ zx4|xTmNod+KEQtA@Q#0lE3J@!;A{Xok&Sg*EEf276x~_iY~vIlO~@IcO!liNj%?ME z0u#ZkWKq2l|7mb~EJ@L*ppH2XG=E1DrZb=C^3i(7!GnK2cb_cCFdF6TGc5dWBguT| z4Z^gm5T^YUB#)i;6zrm%K~30X79WnlhULV#Vsr)+laiu+$}_w>C5nd((M~O^R7Ii) z^hB#pD)H*C8z5Zo24Pf{CO_9=IO9(zE33O$&HmpjqNG7Mk(K95>(xz zDcA6xrJk08@^rnZ(mV0t#lLoBQj75t#{hDA=!x~V%P)?vI*5P!sYiI{z{M-d({|ng z-i)@c{azo;qi^jpV$M~$aZ$MbCIq%5+1#izJAe>@jzP{NLL9PymWY%0e8Y)9NGnV> zAEjlt=VW$&6BejGTfCp0^sEzq_WNXHSr7aav?a4?33+l3wY9IWuYD{}+qGnBye*;o z@nNq!0%8K!d%c9e<4ePBqB1b;EPLIisD!mYF8vZz+s>@USXbw462C?zHKnkEAT#6_ z{~}~`%bdF9ug&hol+;x-FWlV%nI`5}j7Jn&W4lpNW-#Ul2nX%0KGRa}fq7L*W0O<0 z*y+(;k1mEWvG|1{uaksTi3x_DPp&Im7~;!a%wD#(>8~W7%ahwj2KP^thkvZ9t%Vz! zIEp*=NxMihLL=IU0$=RM=VNIQI-H~IGCt<`x1Lp>VoWCAn_R{Dima6_nWkn2hL}|c zSzb}*i4?9$4(^@t63Mi?6mQJ2HZ8l3fAmoH3*Qw1_vgr~uM496=->dk(0Z9&r?$;T zQ};o<_A1)TqJny1l%cWl{@9=I1W$A4E4(<)76T7PXn9ohwU{l_g~%06=wj;ma6xyY zGea#ouI*zV7-{B?wh!y>8*;yT6idUj+lw9PcY=T%;Q&!Ay{fk%cG~HR&}*>)*V>CU z&CWY8r0)vEQ)jJ+o})$18}VVWw62NfT+5v&HzWO+y-BD0>JV00~1L*Nj*5{?DCTFR+|Kxk<}UN z&z3HQ4`Q(cTZzSQN))ss6x!O*r5zZ2(HF-cF&+l}mj`+MVwBPbprMl84`0?iAqjiu zdD2JJ_ER`@*(-5IXG43NyhJPe_p!nTWfY=IEa-zUwPKMGyt@GBsADX{@Hz3&%$Z9pPvSV;AZ7_!^r~|`mBb|U(-SM? zYcmxGwi6|CYCM*un2(~zw)TY(;~t2O_1kTYhN1LJ{;mRe<}U45AL$n>&61)Jy~)JW z>6WLQW50AZJbCbNc(Oy`=2m`UcrRbglkZ$-#Q1->%`s#c8f*l%=Z*<5PnG*~TXagO zk`$;Z8s|jE)wj31qExNvIq^e}y0`Li&i(=vvM`hUn9k3ilc#x1o`P+vHixgLR&i!> z&#SE3&I=@;oZ_E~46B*6I8G^_Urb*lsRjc8H>$b+zOz6|_5m69dH{v#8EnYHIkUa?qRZ99aot(%-; zK0m%Yb4Hp_>qT}*KfAevi$<_g}~}5^ZC7@>FZrP5}S9fcqhI|DSStz zzFyADk`m*Iocw%g{TFA20MQiKQ3e%QgOq91f4pH#nY|lVTrTBIYV3EQXyYjm;PA}< z@RuMIYVv}@whP=$!KPs+1|7NzoS&Z)iQM4BJTfLiIHLE;Nc95bO3B5?Dw?#O%w#|c zjRc+_a+MPApJ$fnySTWV<_`aIN~A3`aaGs2T*FQD=A$gAGL`gi!G!|ybnKQg^B%w zuU2gv71y3O^*JpjB6y*h^*FEQ;@1;v?_RkS-U;rt6p7%mY0Ywd zS4yW*GK_rlyR!yWmeNL%6<&t!-mQilm#!PfUWYySgB@PYhrft*P4-E0WK~4ps$+SQ z7K{qvo2X!^yxP#i{?~F;o&T6YTeZ~FaYJ8YS4^Aj&5I*af_oA*&ajmX;U0qbIgUG= z`?B`(#5)la`ewSDd*M`R3j{t!uZUY_h5XXS0S@zuJm z8tV#QzSNos8HMBodu_iVfhn`iCuh+@wf5N=DO2`yq8F9a%)uInF;fn~TzX{OH8S~% z#q!lFE>v9f;dsB4-q!smVl47!bTf@ba3guSdiqiszO+VTj^2VDX8n+O0ZcLKlvgz) zeR`q#saCLjYg?{>u$L z-d}EU%o|LV3>J1FKDQn{bH_ah$`(V{v!7E`kG!>2Pd&8zZHdJ_(s6hh2xQfH~8=yQKp^G2>NXmY9gWe_fI^=Km>kLqm0M+ z)d7Q;Sl=8%r5r@e{+hSTi|LDAh_wozF>c(=!)AkY{ow62mRB7Ejxo2OP}K=J@qW2> z9s6Dxtck9`-mQCk__ZG#5%R&n_Ws#6aHlRn^>o6^(w z-k)1CHB)xeL51UWZjsY26BIa#iq#zwAK^A(E#!4d)|IV&sNl-P;*LF^kW(66 zp(x?n)jHOF>m9;jn=8X)vJay?W8~a^#GUK^0>h?AliyewH7X)ABdX zLO~{%E5+hYvel?(6aMkf_IWdU-EY|&_+IIq7kNCa<*sIZl+8TZw7hkP6T%ols_V`f zI2uIJ+;D+annw=6VIU)GUIbnf$R27l$GgiXL)8O|z7I8{&$w ze8tq79%PBQt?3a!M_)?jkwcXg=@}RZNqiz^M6<-G4A>IJr|v}Tz0z<~66BSpAkNq} zP?#ENxVx0DPD-s zM9j$gxCenmBc9OK-56ZeTfuW4#h0%0pvt~k+t^XZgPK;pFZ%Ijt;OmA2SQ^z!VfaI zFS}Uoq@;@w&qda~MN~UJAQU*wUH0wlu_pXA!pg~-?8AW{H4p8^5#7GNy9bAXzO^#j z4?19VCZ=(*k=1Ia-jp*!9Mgsza>E((20_qcfAMQ|F|z?-KOejA^d%sYXUZ`T;zN!S zgvVaI;6j;R!&RkfXIgCJ4%9VpBq;C(rLMElg#%e?0&D-axv4mo7b_fi=8SwK(fmV1 z;cbus@C%8U2*$AWF}6Etxi+zd)lgtYEd1~h51iL0_Yl2E#8t)xXPPWlTf#Z8+vnPJ z&#W%cBdav6jL!`p4%e~eHAc>?&9+yIuH-Ne+}gW=Sne7 zF^gY?D!XKDLuSw@95HE!eJzq7h9&1)R{pJsLX{INk7PwKK92Ax4fPz-`C(=oh@6QN z3m?-74GB5z@B8jY@>qb4mECQaR4?-FH7}Q;!UT~(Y-Q`y@qU4{zDRTkSN`vG8iK#%4_kiME}WNAo|yigwT!j$>uz=IRPO>zsF#wf+ur>xy@JD__61 zzPa^|{u(33HY7t@n3}DZZs86)tyjQb`e$1iriC8kpxy(2QhMgS2ULC=Tun#b)qj*x~TrXApX z&11es1uP;oKE4XyC{{If36ZxF#%^D39rPW`v*}C_TL|X-6X_~B;pYm5wqiZ6MALcp ziEl!g^1RZE5qvysnqp;RLvHPkIn@*udkaq31*N3tH?`e)?!{~RAIcB;zyaNgK#T2Jne20}Q)160T49=RFM7FA*tp1W% z+?OF;+PZ=W3QcN`_gOS=#$n&x)NxI(ZxKX?4+x{nC9iAQyvgx}YnN9pX52F3=GGcG zIILD~cnS-Bz#Rz>!u;rd-CtmOwhxF?K)bEyJepUSdeye3xTwhRwx%{Za_@Y&?Vq#T z%ld!Vdk?6lwr*iGdMtQAv4W_8SinL@dbOfb6$>a;1Qeu8uPTU2iHg#jB2ADg1_)II zq=S@5jTi_uK?o2alz;9Z;JM#-@BPPk|GVRj_ud*qLz11n*P3gt*^8*}eijvy;j6fY zrhVHDkuyDQI!xmAAy{RLQq`4)OrsPb(-eUi$@H&{tQUsb+AHpcnOT5))N?FOPwS@q zrl*qH=9;eNR`u^pqfXN=hB6z`;8=dW=XC2wQQW$vS>439Ja!d$*&ap%uaXf%Hcn+F zIZT0X#Sd4kZI;Dg?%;s2O4nj|nq;svBQu^ieuU7e{S)n=PwTs8i(+n=%ZQ#N2wkbw zu_YlwkYJ&h!J*kspXA5_wqpzhQ_c#=c`0mfS4GpTocv5(zMh`UF*CJ5=rBRa9P{zx z71QaF-kzEjkEQFY73a5pY7753JVxn_kFy0YY?l5u724%H2rn;BsP9T^=NMNn@j*Od z^QIa&gvVN?!$G=i*lu!BJ7EpVFYS~4`%TljXR>WOv)4=pegiM`0Pg7yo{M~<)E?8} zwl|RTA*pD)960^Q`ceCyf^TmZL}Wyty)zF-oCsF^rjkvLjKJyI*GOqb`uNk+>G*JI zww_uinm?ODaCWd28)VmMn{TZ|P~Iy0q}1v4Uz)!?g8GTAn$snae%05y~ZHb8!!Sbk{nx8Rcn&qns>000AEeFj2;#RFFs1 zcW~6nfF?j6#fqx}1hE~So^)9IwoFsuMDOb;pgpdIM>XQez7YtPiECA76fvy>`-Uyb^K2H- zZBt;*tXr<_)s?(bz&8LO^T-x;2nQqp3fG~{sPdLL_gG3CF1~(KNm$49;XR~i<-CR^ z+NAvvn=BgsMo=uf=5>uvm>KwMey{isge>k%){&DT`S!%04DY*tn%7(Ph+(bd4I=Qz zLNUe()d-185}Kr!C2vk?n%0eWIp6_d0sq}EqNQaoN2SzW9u>o#3IuFND24&UQ_0Sc zdV$S#8%C94&ifWeFwi!8>fNui#;(M*Ke$8Btez2@7KVD6#1a|7%o>_y0S9tT> z1UpyuW6hZBdsFVxsYH9TUC=eo)G47o@8frYpwLhx_MiiJwl`1sl-bJ&JY>v-zCxJ2 ztND<5&jFwCDGmpc?RRc%29$ndbE|4n%Q_Jbi*{Nk_#%%7UIskyOH&cZp0aiDeOD`5 zxixDqa^mTuxM5Y&woR-3HY9cu?;E3{KDSjV*({g_8B=unesuC(T#MSZz0SMAv1G&` zsqxOnBiCcs2GMjD$9D2w`3k(f! z&wcsEIL>UAE?JFb55^5yb6T`>9<&Aa`ERD9vb(aq{YKANrc9dHA>zV34T$Z@DTD%W zH^sYOww2ckS1;#cY3>V)F@6vQN}|57`A`C@uA{*f%$OtK&k%{rl-%y!jxgb|><<`8 zO>>6o6F$Q4As3&!$=Fy^kWkXTu8et?EATw?&oXOJEGk2J?NLUiq9TVkCPyA;dyl@C z3W*-{u^T)|zyH)HXKTd-dsmo$-q_y;icgYgEn~9J-*;iDJfQCLWw$rKrdZh7WjF76 zn;JRQ=S~+K3L?E*oIBCM6>(y(tU-liI*9Z|HuP;cc0PZI4 z=62=E5~w_>^Z{AL@Zl`jG=$-IOjxm&ecb4e)84_<;;}4I5(EF3osspK@Wal~K#*UC zZh6^r;1Ks3G0|$x_BqU2RC;1N@BX@Ph9`y4??0IAJ>A^N0=5dKgN#o)c+czV=?Tb~ z(x+}}V5d_10pzI=?QnO~@E;{kIPQ-&kn-5<^_>BcpK2h??c#@t^E>Lk5>#Ajb@uK_ z0yQHg{zmqUgA!;jR+%q@W=*~kn?CZm9<{W`y zN}DULV()1q0!wdut2(j**DK)_hw3Y~D!nla=DZQJ5ha+5br=o00g$|ic0ue)d)`*) z&>}WxD+2~ZL8Y^x8wskB$7FQFQITB6;@hh33?5JgMoLO)M`h*G^xq!I5vZ68mu{J>H3?sq9t2#%QUTcZk4FMn|)#u=}IV^OTDDOIZzs zRGmb={UC1In42c9^B=I3KQ>PQ?0wU`=fJ)EDJP`zf^w<~n%118;jIWp1m>$v;0LD* zA-MQ%RkRCzAz}rKTn`&2S0uLB)f$|d3=n#9+?>I9F2gJB<0hk;1L#85W}`RuB#P)4 zVbE@zUI|W!Az@aIcqjpIH>N?0!x^iB>d7~?oqrMflIzqhPy-KY%vnKV;#~i`y?cdC%}k-ltC$vS(J21e~|ajN1FTx4Ais z^O?^KtZqq)ERGS9w(%c@zKrL$A10jUYXY281d~`MN=vhbmVGbWs6_|}l1l(xf!l<) zxY-|v=`g-PkvWfE{Orvb=g&1n;!W12i^HHbEAwZq1@$ye?^wuKg-zF`y~W#4H-D6@ zXqUs#RVs6niwom=3)K(VsFn9Fgu5>yXpe7gZN0dejx}a9r<~??h}`n#G^5N^%CI`A z&R&Bk9E$sIKQ0PPy~~ut0OHbv#qt=|MB72LX-oE3wh(6Vji>tk8dSi!plhQf(rdBH zmONYCF7*{+3W(>A7~&a+whw5y=m?&HdOOAWrZ<(0tS4c+v~KHom0&iHuql%4O4O@A zu0auB=$Df}ZZ_KYmZ%F%MHJzYcEZse#_?wa3Fx?13z{aAkU&9Z=4Df)rf*`?r!?oG z=A>52PRd(zw96A`tbLI5JZWNCKth?WN+K-hSbbk+NmyKB9W;8X*r8s-Aq>zrGkJ8EtYiC^|eMRf~R20qO zfPdo$Saq5g*}m)M!}*^Inw(xZ+avxcp);JhQ@Pu#7{H9%ysS^2dGZW}n@8Q*m9vT)uC z6$2eciLd2sL2y{A`TpsY!S@wi4h3C+w@b(A^$lpdn)dO96b%WSyc8EztE{qj(;myc z&z(ZNvT;+nEwCe#77*;(e&eIlOP@Ggq<*;=*qxQ|ruau+Y5U#uz|3BgJaMg~?!>#I zf;*H$G*OWe^LOziEINXc;wEqk#IL3C=i-h)xKSg8f0a^pZi?T*9(TaJn3pG{7ZWbG3Pq!}3(_%1AvI$*`Z6pdSt%UgD{;dsb#+d9R%Pu7@0M zkA;c*Y~30j_c_60;?QkZ1wn9sIkn}>bY3f;arX!&^W+|sX801)9kWoT{N|Os&qSlH}^<-W6QqzlkF3| z7Ip1Y8HuQzX4*u@* z9@zS@Pv+lqaBpSt&rK$b2BE_vyGiFN6 zZu{I|i-e@q8{$oV88}nSiMgZrB78@RB=F-4(RRqW%swJZt~u`jKgaPp5FqmSLlpC> zs=&G`3Nt1Rmu**(F#OD9VppOVWZx@yds7$4GJ}^nePuG=NueD&woVMVG{WTHUQp1- zak(bZE~jxHvSNvIuYEYw>BIjQ+d;G)XLZaDU0XonRxU9F^Pmz4gh}5w0Ut_WS&M&s zML|Y}P!sH6k19TAn1c#YLQ0(5nVQZ%QPJZf0q~AwU%ftgb;r&oJ2l5W?}FTB`humg z?gP@VGRzf}>(siyXX4LqVlSffve4dGn0SG#X8N7q_}yN&x{D^M@63<5t(9Jbx+5Th z)oHmlm}DH_c+CXE{Ibx^xzpU_-QGbi2U+PNJ!6CRsAMgR-#Z+`s0KmGgEjLlNFPT7W|xQ#D+-bmcE!QiL~JguKxC``_V>VkD5O(XlP2{|BFFm z@$ezRNwc9Etwcg*gcNR5t{r>%?j#ee?zOH6+v|+kSrP)iyVgJe`B3r18!Xewr}5V# z7VH=1wFa#FU3a8^yN;IjeNP*(c5te5+wdLwMZbkeVDwt`yn#V6gMp;9Ch=&yTzLpP zT`W>^Yxy|n(C^yO+KtFYd4cZ3V7r$ZwiI_GHFpe}*XK32CmCQvURXdxPl#Z-Hsxah zXb*p(?kCLD+=g1%6PhB}$S^Fro?#0gl?Qh~$0Pn@eXhSY=SNw*HgibdvOzKL56MbX z>us=+wGRI?U*kdBQ_7j@mV1I-Zx3pdUGxn6{Ef8?_G$^fte5}`Q7lE($|blzqRc6) z&}%(Pe~*#Y4bz_Ha-C}7g3d5!5j*M$J`<(Q;j`KGg5~E}dtN1BG|c)#hP_hjwV;@y zg8XHZICmvdTU@g@_~nDneoc}D#MXbB-BsPuu$dU0FG$3lcI zW8>Y`)F4C|TN5T{_N&HxVjDOH<^bhmrSg#)m!1}OY&E-8NWpnF*Gkz$k{fXm7xX6Y zMU_U_cJyD02BLk_t;Qz4arMaTrHLFYLrvv&68~Q(IO7q&BD%RK$-C(^>0(op zs++`d10lD^r6+{3O>qEXWT`HpJqtwfA}t6yx)>Z5HO0IBEtSoa!-+RG{+qyG^vX_>7SHK#dRc2((*mffwUGo31v zEQI`$t;H8TlCsd?Opg1Z<5PNHWj`R2$7MqNMwgz?-rur)N^b$vYI0KPx;8ZCl>)9e z>J489A)s@k~gY}Alb?Qovz z$(@L>4Hglu^#iG;OG&}OI0rjrg{DFLt%f-Exs-IaS*qmhh;@J1LD>Z{h}Z}Z!&ckY z%`N+w6|Vn5as?R0GjnoB&)WENe>5uMrG59PemL9-iUJ3ABu{HUjyKpWpq7+)c#a!A ze4?5#hm=2poQc=YQxNwQn;!D!A)`izyf$9^%|LEk9TW#)pPwPHjK&pO)J=ln3{sG? z?J2a_*0aD(jeEdjOgzcLl5kDse9zXfb_w6$y-67wwh0TC4;w2;*5dP|=C;~f{jot( z_U$g6PWRT+fobyDMw(f00yx9yXz2h2gTiLVezt| zxSd1l(Z>w8It8ef?fqgl0ibO(M1z8GX*4%GxUt zg*SSj!@_IwqC0=xOHOYP#2N__Bqf>5FGlT;pdJTNWhkw=9kCrWL*crpr=DY442ZQ# zJgEfIa1txJzCpFN_`-@vqn<%v0}VwrfrmoI`hxX1cyk2b@a00+7msO^P0uQo&)Ie^ z;gKoMtN28}^T_v%#x#n_ChxoM9?iyF`?*KWlxdJQSo3H?T7z?osM<>oXA70{h9|P^ zhZdVXRca#%gj3(bJ{n6ckxCX1a0E$zAw)yhc-^;Xe1MZ zq_9avoc8KyctPJtHHe?jdMYU&9ldOTLLxGMgW$(sS`l1K1>GrLG5vGKToQiRVD`< zBh3&#DyarK!qSLgUQOF@dG&ZiU)o1yw}3DO-V?;iBWs) zDZs5_nI=MNu+`X2jK%A_trI_Gi;x5lw3z5YQm=I<#8@|}0v{~?b=h(h^o{ll1+t8k zPGcqTUbfT(-X?oGJMA;+DOkHY3xO5G0gB_m5o6)3?GK|U`8Ive`gfu9|GphM#=9<3 z*;m?u?01UpACA|mYoh#(vYPnzmmBoZSYNKJ&vL5aAV{N|QC9aaiwr$zRpJZRrG8XW zsNvP-M4~5Z4i1~+l=!gO*;nJWL>wT0^VfJ=k)=B&+*>MZJhVxBhceA>nMn#_SIL~r zxg9$VGsa5BCFZ9r7pIiHV(L$6N`r`6`7j%5^UrMwsu)D#I5{7>gYCu!wi1jf_s`FV zbblx-->5X|c&(f9Ji~0Hw8P&>OAAqZQxWc_bzhWS(xgSHAod`i9-Mp&h_LBowD%1$ zoQAPClmJvE9uy9aaN1NZew=GSQhpUytCl!a)c)#;%zJ#kJ&so%ZZR=>-~)XqJO^Lx z;mf{Zsw8)eaRZdOQ^efG6cotrSc8Nu9htm6+sUFSe++kUlW+7?w{;Yjmn+&k-5x}8 zE`WPdt{%WMDO#Y?Nf$U9(%eDV<#{&=;-m~@mNvz__+Kx`8N_4 zGMd}=0<*{(O*OTwdWZ}9b*c7L0f@`FtaqQ<4dJ{#*PLPxH+#!Fwd3!#CC-vV5_}%$9U)V6ECG)?%qw}&C?3&2zk99Dx#bXin7CYg-FIyyo(t^Z5DIiQUZ-vhBKwS?Q51c6maHb= zg{WtxODgq(h(>e=l}wHY<;D`!S9|d}FsF@54fa9%O&&}R4Gn4II&%xpe>EUeK8D8u zDx#uN2CWl5DWT~mnJCcD=4W zU)IjA62}WbHSfmNSO_?yU4n+bulv558G3q^BdH(t<g2}4`F$s089a|jQ!N8TF8 zi71MY+#h+*aHR(#%~|T*qV03Jt8$4Vub03JrG!_P&~Q0Ou{WrX zLuLPL3jgfa@mxqFvo$T{<)27SA!DT=Bvp_OEq;I@Gsd^Seql9;oD*7XxZ zmt`2bIaT>!pxmV}DNL~ZE)xqL@-_GdL&_TkzPA2xCIV}&kY)u^%svoPJSbEgT@+nP0Fe_3%!1#dnVKfg4H4?n% zQ<5Q^A^`z(KvOe8)x(A5^Hn6*_B$s_79^O8+S&QvWtLPfk_M&GyK`MBY(dfdg8>!T zpEdqX95x}8YC@z_NHc`~;9D0rpfphshF1WpaT3<#>QGyAN7Ek9D2kbL$EsKG#Hx>@ zA#47DkedAbeb8FI_`DfF@*C$1EjJl-7WGOhBK~2m=~mwFzPwo$L!^DSH^D~hW_oIO zxU;mlZxrGpRPhhr8Vc#BkdaC&WkUQEAy-udq}@ii1&vuwH8%aFCaAoYEkWo$k`=|q zZVQy5_~%7gM?$%Oqqzq#dyufRG%Wj_4cdt+Ho8fv&ik zKfbQ3y?xJoVN$lOjQz;>{h(aZM6$j`RDawE4IYbgF5|==5E|FTj*i+gcM()ceA@ed zV|L8HDYE$E$>E5@8156?kWyz=?)8ob9@#nGzv=ni0CXTKgj+vj21B8Fm7M4iTx)1h z${Jn?U|;#oKmYgvF12MS|MQ#WcmHQ9p!{!?Vo}>Q&?q85**P$sTQp@G3oS7;IoTH% zM_nE6&z0h5BOAvGMvF8&?ZZaX)ZAuF&Q&1Qiv0rcwDM7OfThQLcH>wH*=7Bm{)9!$ zqTT&_g`;7k$wb=7#x%=aF_G~B_X_d(sV6<;XeE>F2}Kj`?1Ofj^QiA|*+g3MAlgyq z(UcrbopXH95N^thd!Zl;-FkpeHcy=7Ak)NgW-5BUKAdw9DZ-I3NE5e4%-T7KE$D`-w00hyCMGGwh(_Ln`cf0RO8%~37q8G*i3X7D*F*WA7JgWPzidaI?O!UI+z<}Lm zZp@f~hwc5j=HNodAGrCRqJ{PWM}qjmr8V3lcRhL+=YH6c=v!48G0(H8e}8U7Z@%h* z9(Af{-XWGi#Lv^#hxirB`;O4dVR%vz9~u&8BPbgsH&rt*UF2$2VE>)gwzy!q0K4u| zM57t)0n<$|R26{GqpHMK)mpcmb#t?60Ri&!N&@WDcBAFIA%HdypRBP>1*H+1?^)rD>~W<*=@^j^xd8;@6sUKZEoKy z?qS&8udE*?yXZaHXid;7h-O4CGwGD=8yAm>THf*NMYAo9WB>j6gkw>nH}_g8mQWEr z^W}jaZf>*|r?Moy}x&0}tF zEP->L3}=Md9)G;pvCeNMZ>okOKl36DKZo@wLJ{uj;^Zdd1OlL83qaF9KxKZqn~FaQ zmo7AU2ui88bB^V0Ah;|r4#yHq7Kg5v&(0Eu!*JQ0?)}*HyU~U6z{u`PO_ogJaz;(W zN7LL!A5o#yh~~Zexn@Y676F;Bzwp=3f$ufZ`H%z%u!AAU9=71tR4-qXR5Xt{SAmfm zpwi$R72g_PJx7+`WczBVad2#MMRpXXin_2`%$3U@D|X~|(-ARQJ2`UHXTfc@T3$pd z0Gpt_u%X#(mYt{^BTqjYiZgqav(U>X{(qjPm3N*#GFM|ZM$dj@<(V0OTEuarFEDx$ zP!JB3P^I4S=!I`^3_PZ90^J$gM@#Cz=uf0gAOuBUre0<-aEF^4iOR#9**+Rh94(;E zBY;>l2uOrBY^E@-~9(9K*#>00ZjhzE^YUuu4axjUEx_inTT0A?Ce@5({s$NHYTYMQYViHUoN;d9hR??OT*hNas73ZQNa0REWp}u1dV;f?H-L z$3)(SKo>Hjfa=6#w&dFMuX6K!(MLtYig-L8ak-|ftn=UhA=8_*R@QNJqlb)?9A!=o z?omA5OE)h~a-R!T z+c!PwG20yt8$W?9((|kwpn-0zoM61AS3__1XNcb7D4_6(Fltix%yW~d#mbC@?wj2J z5E!0G1Y`r7mzY7@6r3YAcAjnx^6VDw){(Tm4n^-|`_$DIHcZqmBTNhn%Kj*SK8@M1;E);B;3g3+so#bTf$3fQ(a0Op)LYEejc zQ18KCRmao7SWK)&+5-gd;8EQf0NGlt1IrRSeUiw3zEJwSU>aRCv+(l^n$yMj{@frm zAkEOD`Yg_xan3mF+u#<<)BFPVE{=;URv^b$ae*rDHmY{6Vv#Do&^Rz%fS;dVt4!RO zR#X&mk1{$xJ1`yWK7U=Y0xeH@igxW{#^=p1;N~L%f>Ppgjs;Rv8uk7G%tfi9-P0zE zx0X50sM{jYc>H2*k%&|jon5$21N%TW%5gr}BMxQPJspE@%ujV2QqprB!40An3V?h0 z2VD2)30AD|_n36|=nd`n7(b{-{WW}pLiHRQr2QqRw*4VLgPRmmif28D43f`@^H~}m zG)vj3;cln-+Qtkg`7JSO(DJRVDIY!cj@l9C)LUCbTX4NC0{z5(1qB5)cWRuX3!@=o zDvJy01q^cX5b@-W{11`6++7;oBR4mci<6_qiMCy})Wd?grMkF1wNdM5_6p`&_bc~W zD!9}ZLN`Z8d2%{J`t@x%3sc?FnGHJUHoH_A*Z`&S7#9!uLw;Z09Zk)8Iods>yp@3z z;ze32LghI#mL*Ti>H}ZVk$nYV)q7Q2TMJ^WYwrEQ%Wxs2W_3bf^YTqjyN)hsg^#8YX`2TEaH6~6-$3;0D@!Hh)-Vd{ zW!4<@O_p?qk{-uFllNXLauA=zQmckZK+B3qxsOw#+1MS57V8G4yEj(c&4M^~)hHP) z&zTpace#X`2%J#YLDRGC#SNDmse$3nnMnd3zh(Ts=A@tcv=dt93S<#f4_n;M`Nezn&O4mFT zLc8*#9vhCbPpILyKPK31p6$2W{Ku>R-Cr$f?soU*H2-U18Rhs@WBrC-aO`KkZW}-& z&}G)eA5$m-5@ZF^=i=%s+()qJLBAd=9d*Sl?H#3LAA}(+3zR=>*9h(#tvI^uMbDf^fb`jA0#Tw42mQhz3m)J6L29JcsWWl#D0<&*`L{hiW#WpNGvWV>Tn27>o`HZi~bjKb?~pI11& z_VPAzxDrJkn{`kr6Mdg}vY+zcQGQ^g5Bn3)TkDHPcXELp;=daD&sL0~|H);7XIH!f z|H&002Uc7t|H&1~+m>tI{3jQn;u+om)&H-ChSz_mIUQZ`XrMY9CnxRl9mft-RYfC_ zPpu?-Ly3JrmeZXHV-ivF9vntGIy%5W|2dkySD>N3mz9+@BO_y*UBf-G*v31`3obW5 zwDyv&R_WOa?*1|}lPdvuv_q0LDl+mc`_3%;x~`da3yfh4)F0#ah=#0Zz__Fi(zzl$ zS}M`GLZ^me54OR7j`WZ}p2v17E%`uQ4x~75I!4S&fi@J@=TNA^;IU#qhSsa)Yg!yNl}b-t1+nRVf&AnAKfcB&oih}! z3n>ZlD|mJF3mXMjrH4BVKW@INyFF43(-ApmTURBCcu*=e*zYaq{ z+SF_S4O*s+8%iE$RckxqOs-XCF4 zXcz*4EsQSauyow;q-oZp+E8o=##W3UTN)K7vC$$8JUhcfnFLiyOI8tw!c=EsofCFi zPI(!!?!Oulf5~F7rNlDz_+YEz(9ghP_f~)XK^PpoiyG!JF~4-=8VOglFj*zwxZY8(FQwoayTdud z@dIZ1^SAE|&(2EeU$wzE4c=d?uhUD*{yBHvo!#=P^?j2IF&yQDvEE_L^otPUu0(Q1 zb@Q;9)UZkSlW!4qTX18-_JdnaYI@8}f=xEr^C}R}?r;5UYr1*tJBu@2Eq+@k1@WWq zRBYw&;tW)|nem`(-njQSWpr15wJp&%|A|;BvlAN$%)30S91w2%By(XYihX|C; z@K~R&J3fD=u*majrBb}M=H})yDB%%(!HVx-MOj~~(cFlMyf0wXG#NYUs{L40!_{I- z@+(epxtZQTRN|wnB2c zBW>-;Q0Fa7x2_Ff7>KEDOD|lY;TOy07sk+@SC3%{YaBWIDyo=xh!xeAMe||{v$!I6 zdCq=Xo>4cxk6O@cAzdFQp|p1RINqlhSE7G4NV0j*SttDLpNUdhzC4}$%Ui=rhK@Y>ei-8>yXs&?RcvGX?Q zxN~ZtIIlX}G3KlH{|2yi|7~pWU>t;CsAfSdk&}>JC`gXFFYA{mH9SUzXubmg%VhRN7C!8KJ3uFb+iuG#|e;&}HGUJs2f- z?-Vp)Vqj0i&P|6=Y|fchdzqFsARtG6q`+Rlai(5fIW%o@7uNjEZUpZ1t7Dni^`Kc+ zonEk;{2k2Z;S0Z4dBowHlReGSgLZR}`lu{G+JjxsK*ph?4AzfW?zpffu1dIhn&mAIP zWMXecuUsAH3p*fUpPM!f0!^O_w?XanzGW~M9`IVQURnWA5Z6Ake@$VS6?6N_vq1I{TEQD$S|!AtsT$^T5T4F5|rylB$z2pFVxL1xtBsbV}&MHM8x& z1X0E}xgWbbI!c6Mp~7c4Ul!5_&Dzy+884*z5GNM1QM8%RMo|k01N#V0pX%#=Ij3 z`$-Y7-lfqRxw?N2_BJ-#jTn%{++y=Ry4Sd~qXWVu^a}xX=nDapII=iFMe-+?0^QE5 zb#0#^zbswzF-7Y`GWcayWK>kp77+_`bC5y=Z$|a-WASn_GJsh6 zg)*g)wp5)y_l|(QzaOIM0CeT|cxB_{#AbvDhp-ak5qm69pp)X@3W2xJj_xqY2cOq% zq|K$pC*=@`2I2wm%v6yv-pIaY$dtOdyK@+gjE*kZ3*eYZ0oD9{@0eHaMeEofoB<@GdF?;n*b=qxAZ*b?OtVMW$i#@ zF$DhVA0jY{v6nipM)lao15&db#}cO80)OA=A5;Hmf!S-6(*RJnNKj^wC6N&r2xV%u#Yqa1!s+>5S=GqEta2=LCBE36bYxoFc3#e`s+=)=?Q&>z!vda4^L zXqeR&Ap`Sry?*`rrE)Oe!iBz*&ILKi`RVO7fR&WjvKqG_hs(3$Xck@-+9@D_x2bT0 z?Q(c6O}58^khnC4&#x#Do^jfGdWvj7E0GBXzX)l(v(w@5c31_*5Ftb@fT|~u19dAG z&#}eF`nps6Ap!uV*wx6P6Xd65Tu%>JJzSa>0cq*r?Krx_xP~z~yw(^VOnKd9jp-o4 zU&LX1?4`#@D!rAoQK{eWscv|5(hs3DAol#8CrVRs74LZClwS!w{Q{3By^73*2o;!P zh$SFA8~vbsca-55we8P`KXPD^Pdd&PG%=mN2Hl&0UX zIB!)k)4xkc!AK}te2$GDFw3)J8-lb$ua-BOZHv#PbLB8E&zXpoeEI1Ie@qGR?9yXK zSp>~8S+7~%y9Y?D9(gX74kM$3LeLHv(4-6@x-n8lMV6Uu_W*7G%~WC=kHCdgU&aZj zczpwGZNCxPa0%n2ZN>Na!_SC&VM>oz(!`6Uwyzv+{kuj24Yp820vV%WNNReF6x_B- z5Wz1}2)OTJZXj%mK0L4gfl%#``h)z4lNFjW7vJ(fcM#@c3iRwqHA17_6Xrl;>Qrr$ z#e}YqEb%{3M+Iq~4L@Oi?VCWy*u=g4MgMr5e%C+L-TaC$I?lHI-i;WGk@96!hx=eb zrlHRg;JUO)?5t`~TRR(??9k`gzeJxMaaB=$g9f9kyA~m!u1h0-Rk1I=3>{J7(-tl6DhUoWavJzz{fq=s#OTKr zS-tuPh};8`sP1oM-zlJ12-KZEJnjCxP0&-}Ek332129A@F zBpp-vb^!9Bh$3+Nr7@@Q2N}696?w!=hJ#L;p(Cx^gD3@f9Pg*>>|I7}P_dx+*3!yu z0ml74nByxsL8!D<=&Rq@z;+gdi5MwRSS|%2xJ;h~P>CvGI-3Dvb*c5v2|7PCsSR^N)MA3tuWfgitdIpRK{9qYQGmKl#M~t{$nT(@fFu+B=wzOF;@{)bu%BK$dL`{i#E1x{N}Rrx!ve8_mb`dvt(Yp1BSYhBA!m zY|!Uo0anJE=913OLk=G73)wG~#L_2EF(08;26N2{*A zdL5fa5gv%0{wJvZ5I5C%KnP({QG8nT*&A1nMnjROXPH2Ymv8(htt1~3NRpG23oy#9 zfje6Sv0fb|lgl&db2Ntzc^q=CyQ}wX@j)=h-pXogHJt*tL7>aqE{h6b`gI+dczH=W zzS5uY=fSfwV>?-|pj^m5JT!C+V8L1K`##ve^xg2V8U5hz6@Iud(;wXd=%i2v?Tmw? zu(n7Lp>nmBOIy}qr}!_q(C8qi?U;QhkpJZcwAS*#e3k|#y^6uvCKRxA?t6E)?miBV z@?#;egFh;4Fm526LgS%pB?w}OSL;wSFO{Fz2eDj;BuzG|w`Jb&2<{T!m;3VSNKRT> z8hk?^e=Z?iJ)!AoY;5eE6MHiLUJJne^$3izmEKNlK3cDJ(^z2(k3$MyAUieFAmo(p zHTqsR32(J^bOgwuE0F!Ldemvx-&w$G0u)jIMqN8Ir<~u_-Ao&nSCMY9N*Y6s#tJ$a z=$^ZEZ4hK7fY}q+*9$x28}EF1fBShOQaWZU*vy1NF;KhS@O4cGY&RdZ3o%$-6+})G zy01{WpQM_wBiYK%4xEL>%;Pj3d5C3v9WhwPPNg3i`YJu`4z7qK#jo;n3_UzN`qoT5 zWCS{%n3W&l3_)Pq4eyDCXP6>qXo%mTgUz63f6X!eME9n$va@iLwB9<}k01J(RNZAz z0K5ax=q4xkIW z;~Pl!JP3M#p(p~3ZrHA?k$XygO0X>?`?J7ATd&l0Bo{h{VzD_naEcSN{DeSCJ(tjw)NA{S>C1Njxw?s@ z3CJlRWdcpOMxt+9Oe#DU!fit_$=|K@^z_sq%7Q-r6aHJ+z)=?0Qjk=Q1K~g2cZ({Z&Bmna)cCH%u5vz!P=%DlpA`jiz z+1bm;WQ$)GlGU40>4olKciA|4jhsV~SvF!8=goqWyfl9Bfu6h-GLk)?^`?+Afhr}c z)^#9?k2)VRvBUuSGwEEN{);~~k1MHQ^+BenAbN%_uSl9jM@Az2Es{z28SI9FovNA| zY*ga8D83Akx|YVDYS`;4B^gy!RY0`FbLSd#B7hLIfciBC!4Yur`#@Uk2%yQ|vyAjA*BiR9qmE59e<9~ig;;X;X;=Nf9e5Uv}MZHuTU?+sQ} z`c-o_>h)HLZuT_Q(bKDdw4UEn0^JWL(vq8#69U4%f@qYC(@zAFR}R(NEeU%Abn`uN4Z^&CKouLGZ$1Y3o-0sQFL4Edjnb)22JJ3*UArkaKuE`w!{K$Kw2E$9t21_rCP z5t!#LNwA_LkbySz0ATvCJG@GIzKwqi07#z%K`ZgRuBw~8JvT%wqjr4-^RIz0a^02c zLWDz#fQ<$mcXjuHTb#9Nbsz6VY}`vjU1=57Uj+sCMMg)f@h8DvT@w(ft`6;34|Fr# zIhU{i)m916UY2{DhdTW`a;Fzu5J{EbNTsm4sL5_`=fK>1Ly zf*@F3IVezG?IaytqOT7q=D7=49_WRE>=@r9;AW(+63QFcC_v6NKXkfzK+MV5+>C9% zGHm18RiB6as!Ci;9Y2-NRD63Yx4@TbTz=x|od_&qLomKu4Hs&G}JT35`XB^jNc~!y-U0UcF%DHBo+ky*nb81UT}etI|p4 zk>J_6PPu(Bv*ahy{mHuzA~+r;TZw4@kQ0Rn8%rYu44uFKO!Uw9qn|BZh>oxaL`i&{ z4$j42sYbuxet>v^2@Em5pMmj>+qBlT<%MKZ>;!i!cZ(s+aonQanl$6%iS^ zD#|V2PJQpc?o$3PFvzZPy^(?8v{%QtsC z9st&OV&#!oD0QSD$z~)YA;Az5*FpLl#0TD+S7oj9rTeBhyItvF5hnH z^~dIao$)`f_`iD0K-WZ}ELV*;|GfLkyZ<9W0{?ff1)1&tP|U=tRCXK;{=$gIqy;Qu zNXjYjCUt*MNh<$kZxy~E(%OGDemzzL9`?`C{SZ<#5sl9@0)!XP7h?8!OqX)5TsJg8 z_~#G)<(=cBRYT#wd`bV{e+X);2Ah9B4b=Zeq{;t_km&y*;HX+Ln-oiDgBcLCRj^>6 z2(PUSln+XqAJF0i& zp-_RY<+vCM<(eq$+HHa1p;*Wn9q_BJa*_vskE*Unvm+Q$KokwhH$DUssN5AvLsrTZ z_PcfeXt0QbkVC?OCy0L+G;K>vSVWv{s_M**Iom>F&HQ*@b>*+yi;eZWTKD zI{Dpp6_FIXjN!V96%XDI(kd-aRM?;;CN9py^~GV^E$w$t%E57_!aonIn*lMH#xGC= z$@BrhQ60A5(mu2(bM}aE-(eRBBM`RqeGB<}Twe?9>pDh<1El4dzw$VAvIRluh-XmB z=u2Op5!}Itmx?b74TvbCj9dt1pB}cIdmAd}@V%_FA%|&=iVo@CbAY=&qCm;}(-}q2(CGX?%N8X=!O?AjSS%y)lL}~I|=l;c-nckW%=pRn`k&tWA9suCm>hBg4HxJpZ@3s zhOC5$54O==B>VMnLZY<#wV zt&^&>L#LDbA-!eJbVP0CB*wh6dsf={F6Y-l0h)xg^bTK8&pzBtK+T4S)e@->%Ka)B z5H$JpffgWrP#7^aLZ}0xCwDVS#v1P?o(?bZJ#V;>>nm~ybXNOzSd4>Qvn}_G(y*CaDv5XaNVX0U?=#|L^M!`A0btSyb_MpD%k&;Bx~WE} zd2Xi+-MWtYX!+=MNc&^cTzuVb zKU(zX;h9btBOQ{v<2udz^ScJ`|2%E7V!lb=w~BhwUwx%PwBrueZC25}_EJsvJr8tG zRoFFdGvv2}Xyjz4F$blvT9FtMx;}U9LyKuyPfyQ{b-PE8QD@N{vNkV7VIH+o>l-XD zuiI1m9pJz?%%b3-vF%Px)}e8Jd4+ zbYS3)%?sPeK99vxA7;YOp9hwVVb=}|B-G7YboIGQ%P)R)!s3v&vne;oPPX`hRDj~H zjF3*Vege{I_bTGk7{Lx(m7m!PNx7I07I?*@PnNZKE0@&`VBE>|Id3DIUa$u%EF?@j zF_;xLpjdePZ9bf_9~);Jz5(PQP=+7EEA)@r`3iQQTZr7p$%)YTber6cE<7H5BirmW zMzYJmmX~-jlG=54tL$RO?{`2|PS-pAvHN1Hke3910{6*sDcx@Cy&%;G7KP{kFr>Sl zI?Kw-%X?oEnPLU`1Tq$W`U?C6eV}m|X(P*MbT_6*gV~;p|xx)^SsNs?EXWES5=RX z|NQ$w$jtby5OwqtW)M?A^;hW2Oh|QEH*Z&_ zwY|S{`AoViubjyro1M5iAuMFyzJ2R3fviKh#e2YAT(w(SH^js}zhG|e4D=m_52l~8 zQ%Q(FPW@bzKxNy_=VQi_dh*T_ceXzRYn1S~YbC3mE7we1_az8rxpV}?x=AI^7-TpA8KvU_Wm*57x}8`y19NI09+;(z<&gB%GV-*x*9 zH~#cn`}VRY(~bMn?1SI8r=9Pn$@jnPkD|<=SQS-M4|Si`9&>P~I=Ji6Pl6|AwQgmO z5^s{6Em1iP#9^j$NQ~6{lfLFl;x|IXB@Ey&O&A&&oNUnFfRx18u+7e}COENx%6f9l z8CJw}%+y@AV}8*5FVypcGaSLHRjURXq0Cks9a@qIE2?>v6)>4Wk=sh6Oc zuacHLhph97#%Ku3kAqINSd(Q88-Q+IzaNZpfEJ2XYjgH|5q_l!Obz95{B%PS|5`&Y z@Ry`(czsUXk$n6#A79!A)DQP6U593WChmu~SXfx#UL%K3H5l?gEIFe_s|gh`?txTt z4apsEDr9p)L)oax%eq1&Re$-<7|k%&>LXh^AigBeiDC?m=+8s&N?97gQ9dJ`p98|+ z%En(z+wBWaf6@z%kh24IP%LjP$}{nj;n>aQRG$winCf=vAyStq`RUWAKQ&w1jG&z# zZrkJ(lq`9MgU2)y0@O=NKV?<0FBatHI^p}fy9kBYqAo8E2iMI|&i5k1lYp2X`|DO% z!$FR#pfZ(-mO>pLWIc!06@&>h%|qd_vrrNb<*?(Su1=>&ZhHE2e!aqoWr*dSUr}rt zoVIPT-K|79fa3nF4@r%_hF-hLEX5Lum6esZj$Yu!J<+Qm#IRadIQir3p!e!J$g5^= z{g$seTOe?cx>i#eG-aMSj zw)-Exl%%^-RECVDfifn^EGk7shLSP5MHHFmp;0nb#t6G4A|&&ygfbI$8OyYddxy=& zwzqd(ls)J{Yx?Y4eQ9Um?!dJ>>aG=@@q@&S z{QVa1Zs|szrxQKS$q{7S@|3aRO~#9$V@*!j_O;`Yy@Y$azpARLu7fgisX^%O{ModK zlY-B`S?`ozP-!3(C|3iEAP9{M&j+tEdA28L?%>?F@sY%jPRZ4A)|^2z`MjmFo}3O2 zTi$iVwFY8EYre1{$nnbbOO@LhZyL6KIh%YudU09vMke>}*>nF=bI16-NH+_MeVo}d zx@ThLqJ{KxA0PgQw&|yl}&d#p<&9Jc_CL}a8 zl-OpOwo&k&jr=$%?8XCV2kcB?Y&u+X$IZsGm#~!?nnIm!^x~X;HauvfT;%cD0Y`D| zIBT~_dMgE*F~2iG5Gf(Wf=#?UdoG`PMklGa;d4FgtO5oSclgs5Mi@p0Z(^ zaB(M@yRy=@@{D)6($tm<{|A|tH@||}Mrqo@4VU~8;tP1^afkA_4StV7z?5zF^1Lk% zZv=~_)ZCW-^;*QUn1Idibsk_3+Q@DW(#e(pJFs~qh~CflVnx|sH`XFwuWyj1m6`i+ z)>^r@$XEQec~6#Y zF!evy3@ppLW!^w&vH(Ey8s3>eU6o|>of}Yts3uU$eokHGHs^7Q)T_-AQpYyx(;YmX zBI~a}oSE!#uapGikNxhK-awv+L_y`NS2dSrWm@YR;rBB6-Wsp*tjfHXABDpk$o8dE$rC~0MFza;&LKBjrExRqFb+ay9sTjX0O&9TDFzalPDVXc{0$uA~CIO zety0o^@!r_T5d-Lu<6zcE0)H!a)i++AFue7KKZ>3Ase6Iaop6>}UJWv=xmnf|E?>TE zp(nyRMV6=b%15sEVT8XvTD#HVe({-kccg7^YJys{ROs+lS+s3md%cAB5$v!TjC_i7f=AnN;K#QF9mL9vZnaNu3cZ2t-NA$y$4DL>etu+tK%gZD7 z1*^R}-?=OIbdSR^d8yR1!#XC=)@S*C%(2GhS5J0gTNvr>hMM9ZuJifw za)Y1C*BfgsVpFy(J$UhUf?;24>*`+QAHz5k3zT;t9tj1j8zGtClhP*Hkl|esjK`yR z?1!P{T^j*%sI!dDSDqrFNVCtOyr7-8vp29!S`L>CK%=<7MAZ)5$F0e&J;l02i_)$t z9-`B(AB6E{@I6-JAap6P9~BuX2JbnlWleTmG*CSZXnipVVh$z4d0OF#O3-;AjhW`| zwXIl2ur>rz_YZHbJ_#Z)46aGjc_Je(A3MyHWlr>NZ|fCO>KYlfQ3i8Ep`ctbFn_hl z&gZp-{si+(kAU&;(kK-s{*66<+G*zjpZt5jOMUDALIG~lRUfjYgex}GjH8O zBPu2)2722uNi0NkxL#`Y`Y3eTx)(W7WSz`#F4EWG!fZfn=%kSaxu_cLf!}^dzUn6$b0Y}@RcLY%s3~h)<`PzK)lkzB6zS{xz+&J;eYWs zG>}d%sN{TB*_r(qZyWYJb$0AJaj>!Hr;u*A{L4bAyLXJsK`?kwMd1{Gm>b&FRHwlp z6orDDPO>z%4OeVXwedKseDM^mnC`Q+NW&Qip=3PZho_2y&czmqa3J27Uy8B5ex)?B zVZC)Eed$XvzKT=pfZQ#dRk?58JVJrATI<4_W#zX_?t(oBWSEYOirPPnmwNAitQ};L z^cD|%e)BBbPh(wS%dcaBR9vd*M}zhE_<@qo!wp^ewYRAfccSOJ>6g6T1^SWL_{~HO zfhNexQq?YJQbLe6GgnusmL1GmPU)4LSXO95tD;^0?z>c~2e3zsloDHY>AQ$5^Mc&s z#Qhs&0pJ;sMW-VlKR)T;yPXK+J_Q!@%i3WEr8Kd7=)%8D?%I#b8;P*A%8!W4bDy$GkhMexB8pn2q9ZWn#fn z)8I4-%Tq1FOQnfE>pBF$dkts6V9Kho4c{?n!)<#0y=<^UP4MmfRJWg^J@PkmdL7U| zl*Q%!M6Mr;@QYNx@*CPvqX7^h=CM*1g*rP~f`t5ZrQbCq-@DVIsi}!B)jjHTW1O<4 zc6y?#o13)51=_x&Jg(c&7F1*Q0Hden6CpUHLGQ{xaB_HXJQu&c?g6VJDW8Ss4LBkd)PEQoCKVX;6-I+qm%Bt>IIRrlXgflwrVP0 z1ifD?Dk*tEH2~Y<9ih?Orky6;Gf9W0tdD6{aws)NqNfxCZS2+Bjt>1*2*%)9i9sbL z_^fp+Bb{8*vq}$WYNd6Im@_hC_>+tWf3-2dY)KNT_bazCFziX!ynW!@y377|xESv; zAPUkjZ{Cn`L&&o@qpbrNC=Ei|BL#_BX@*6Sxq>(Olc(oL=o6qTqT~P$r>PAUidS4* zBptp?PdoNg9TR&YA50;mnenz&_O5M3W4Y6bCZN2)C>C<45jmRDImP9a3&rwnZEY&r z5z86lZ7RP^u62wxI>!X+dJVPfPqq-iDO7t_nXeZN?=9BJu4kTgaj~;C;?Ok?jm>1FCd-+C2jjxwd-ZSjnhjuG#;L-*e z9P-c*fRbL`_u=+D41GlAc0PEnGMLRpq8t-(4~=YZvH;FY)V1>&v{N3dy#`HhLvOOR zz)62hLp(&(LN#Nzbuw>6BL@_{7HEyVtkC!6BDFv3O0|{iAv4B;2wfVP0l$RuRMOpr z3r}(i_Teg)?yh0C-n0W6Z0U}Fh=(K`b04WyY3P`Sm>f|!RcopU=Z zQ2fOT`g>==qmZfG(l7>v{Kxou8NeD>a2Hx+EI1^i0{Pw5k3~t-xk@cA;7VE?-g|y^ zY0ESWJ$6lzjpPUnHoDst#q-HjhOe}5t}Rdy;RPq&Y7=O9!9-%g?a8SWL@i0#q}C4D z4Nv(xk}X7fi9~zgSI(s7j0?n(q1KR1GGvJ@ zJ^s*dkMDZ_{%F;1p3BzhN39`V-_Hcu)gp6At@dHsgvBS7q$@HQ2NS8ceaZIQ2|Ww4tzeti!IWEI47plC3T5fKq5 z@`$-4Cm>Wvauo>@mb-@2GItIo#d%sF(s4fTp&9{lyM1rXbzd z+A;irdMsAj2f*S=2a`2Et-Cj*W+k!+62iG>&mIjSaS}DjomqW1p!#`VPHa*RkhCJH z;e~T5IhRpsyt98bZphRPU2{#*WG;O10XZ-8$eQt8sZ3g%5CyTm>Ab0HWLz6~Zwr!$ z>b$$|QnX*@pfI`q#rrZkZZ?ouV%PNDJs$}YkJUrlHBqsFtl2Mv;<+u3-=Fh({SHw| z#?0ca1R6B$NC_%M2BuFT|0?cuuG4~k6dZ`ohi+zi>Q6aq4d*O9t6ENn=elp?^J3Sc z5_Ry9ES`E5ObvZN+E^NxYrj}`)Wp(qegLcp&g>N)8ETB?S*6g$?j~g)YI@GSp{%eJ7f3ZURI$!j1~vR_tymBi!TCiEe}}= zu*0^Lq#cKO=vi@oId{KpULQ?XvE!w^GIEtEVl3P((sTl|jZ}y-v$;u!crt)xc9>78t z_-`oI*uTG2QAd0WY%X3NIt0_6Bw!AFzc*(fl}Q7(mt9}01DJBY6ewgc6(j|E&viTi z!!uGT$ZZCs9f~&N_+c#WI3bLB>eQ*{94Nv}V-704V3P*7_a0h4;$;420ZrMih8;?* zL@QHlEekrl38|=Rgk>=Ryc*a|Yx+E=Ap<&t-j}VoV{#ESp24-FOhQgB=Ev)TN_C7y znYm+9tAFw*Gkd@IU0XrnSQX<_e}LN}fV-N%a&FEQ)qbEMH3BuFlX>Ln9C@6x3SlYjtw%AWK8+r|Kbl z7@W5zuUc8X;Kl?#5LdK z9F~`iU31h+nneDoC>#F%wU5qxFlY@cc=c>QfPOx-eFbU`Ht22%XT74Lg2NJ$G@=sL zU70eQ5R9p8r8u3Q;~#;F%@XSsxFNV@T2_r*sv~LW~bBBN=-G{(7{mls@$R?L9ih(cWf;S z6ZZ?I7MFlWBBX3W2G%DJ;w_HRW>=;tz=i0xP^oT%Zn72$TLR}rP zI-j9FFVwcLHS8L*Uy@z;dg$H`^$rbkdEcN%Fe%)Plc{^{B5qhPy9$$s{#$<%iwFLR zIOaDbZ00AX2n6DZO8NSt`EjO`ncFr>a1HYL!bO zlRvsg%U2cYJP+r!IAtu_Qg;gl<(p1!x)dN~2kC|mohYAm_GY7)Ii0p6Mf+~>UEAj1 z6gX(N9tu{?6A~w(7m@}8EMw9EJrk%S#1z@yYKUuX?JdT_Id=N$Nj&L?O@v(BDiiS) zasQryyWe|Bt&8ibYoAe3&f#L}dF=gOrxN?(!^N`{?may9pWi?1nYp3n@$4|Q$w%J4 ze!;M~JFMh;c~5SU%YGV{z(R6k&rEB5j!`jXGR579GNaecAJ|_n!PCu4C^3-0yO$Kr zdRb!wy2({==OBC`!P}jXrz!9GH8^MFqjIb}+<2zl%aEgl)Zb3X!5S5Zd0R}61kd+J zk7Vn~yYSMuRC#{zt@lbj97YiHU`mXYTZhE$Q>3}{WdhDq+qDFy4tvbm=R-HS;Ut}& zhXtRH^6{3wZA(+r@T_y3s&j~vTc;jwiA{}%_Np&9&RSYsy=tFtB>8)Fs4ao{8px&Lx* zTvuJ5^_x+fN)9J%`KdGbt0!whXPzM5W!aO(-V?h_|fWGY*I4AzjuFtV`rR){)pBSetd7p7s2+_r}}@{`iw! zV74p`sa;`%qPEF z4^8YzMBTW0Hvp7uJ~rOQOD}i+i4zKI*(SJ(#!iWbH^Ro)c!z^Q%a=#{#9(6|G@{)< za9^Pkf&t($niN?i5Y&K~=j2+yQK{{0ad{LS1Hp@sYD(~rc`3BZekXoXTd#4bEIN=36fu)k*F>ASw zwT3J={jkS*f>hFgom{|HE@;}gQ%qIwTvWd`eUe&PQe1>wbDKQ3if(dzfil%I^PxAx zO-?PAc4A}O-r276hJ1n5Ml&Wi&u;m4NkO`NV#p3x7muE&EL~jkG$)zU6Q#nO--{{> z)Jl6%-B%sQR#n|!6T$nUTka#faES(ou*baoi(3a}Wv__}X6gtSq%zqv$=gBTM0zS? z@7!hp);^I3AuIuU&f_}sZY<&?;LsLBHJS0F@l=+EtBt*NW7xa&Ecy_-F<^{c510Vi zfbwUF_-gt?A`mRrsmYDw!LyCNldm(4vyhvg}fVzRroFu^KUe6*`a4Z z%}En=J?PMk3Ie>QJ%qVJ`aT?FV#Z*%_h+NK_ z$}>KQ8Xusf?H_QtP3YLn2c#|J6vr%>EzC;z^#&DBMIW6!ed^TTr0d?7liPanZ89Iv zXb+Z&Twkyn8!M6 z&jp`#tfxvQcjp)`T(J=#<+ep_pzJ5*QY#h;qlH_VJro+~V7*eQ;S0JPCFI?1OvdVj z+3!C0d(##gxu>VxJ(xc1O$gcJ3oOnd}WXoovj)ovDsByg{p)+!z{LEZC7;kzG)I3(fvW!2zc^yn*t9xCJ zq10&rILuJuR5sARq_wVrNd$E3M(yF^K~jvINB&{Q(}e>O@gFxH3Cd&g0Ie_$6bW>B zzire-WSWNO#z5|aCjQhEBQ*Jy&f;Vjvcm_pcvkq6>d#op>ig(q^F z5i*~|#6rW=!u3ZFjJ^-oTn#CX}NTzU2@dKGwojk7k( zsrO8{q$A%>Z(FYxuNfWF~D~CdY&aE`UjCT!>H9uo~VYs^^{iBJoVasOwZzAxk zENC{G$BfnipvbVMLDFGb$_vFR^sZ03Q|}C+g?b{FQsErE^Z9EPK`E7;v}kez|M})l z0IC6}fW2}{u_ZB^g_&97{Cmq3TMrZl3ZwU|!~+a3tz*rYvrbM&B#j>ZCZEGmj^uJ# zaMKL*dOSI5Ihgk++vG5{Kz(hkh7?qDQrZ_G|1oGJoOJNyTltN?Yy`Y z!}|@rWT*d@E|a+!VM~70Parqu6bE}#3vpPv5Zl3Eq~U1#-#tC$e&x^Zqr!9lsHL#plxb0^@ag>FDY< zq3)AJ`pfS4Jlq@0D#$p3u#SLh-XgCaCCH~*BZ=qn9@p@^YZw%X25}49CwcOAhS*qG zBt@*Tw&a#b-EUI7>vD@-d7h}=sy=7p$Gvo}R18@aVNFe>F}r+uUv2PFR@dg|+Da`^ z0v2OtF)lBf+)qB?&e)9{d|UD=*Q?qizCj-4wOd`4d%RZDg-h=>DW;_}i&@WbrbQp| zU>fY^4XS6Dn)QC75jn)U>_;gBH^Ke>7VUD42Rfr1>*bOJ=q0bF7xqHRSttmzC+AgV z5aeEY3&fYyw98kG(3|b^yMGF}o4c8iP1w04`8fzWXgNzun zN(ZIeI4uxWA{B?$o`cM@oW!i;y5NwfcN$r&;+zdYRPyq?2&{*LG^^~{^{Jo*_Ep7v z=Obk~ZAs(5oS%Ff;{)MjIJ=Pmh6!t1|96d1W>%#K0Il`7KC5y+ssAZck`Dqji{oO# z+ASO2Ms+V3SB)Z zzPzb9HNRfqvrzAEABw0=dvCn$pWZ!n^5XTh688e(SuhedOtcqvBj%a(2Yh#1qvwqN ziy=UTJ9g=%CLv)u@81;aoH_tU_2JEa8no{bj=+wQG>NI*H+I~g6JZ`Mf0w%t(tmm@ zgIIQag;3AFouw3j{>&2S4#ZO4try;k+ltvYsf)x350|BRP93^_aF_FJXwPSY$Mb$F z6B5a*m>;*t-;FLAg2FYA-c!P_B?@1v*j0D3@Ws9!{bt5Pn-B;7Md|jzBVOe2qFnR1 zN|2dQH>xK|N^UqqrQ_fr>wNnP8(BG6Zrz6VsI=m99NaGM*=e|u;L@*sAx%e@4|GV= zSUH!&n(~mRJ&d_bs#C34EPFv8ZTsbmDR`ug8^)PwX!rU(^=2_($Fu&1D&} z4bGN+_52=S&0N_VJ8N%LztDBQu=}EVj_1U2oB8pag~G#l(&TBdJ43P8D4=6;q4Jw< zP>Wn~9+QiQF+6rJzZXN8JPQrxW7V3i>7RL587AFd!wo5x`)+EYsgSQD_Rt;-GxlfP zpm`o~eOZy(8T^t^KMl>6i^BtRQb zh2L*DS#$M$ESue~xSg&Y<8vbsVvW6Yn3q)}Zz|=%#Lw-P!yJ&u)NDcK60BFP#ie*S zk^4GBfXLVLCU;NgzWZuRq6y8@Qdp?U|L(!}ou?1-?LD%F^?0o;q@Fc* zFjnZ8&A9NTX+kem)GcrfV?w+-LLb0CHVSElq$5+eTW)9?uUKPkvAWK0j}?KTx$~wmoNoOqM^T5R>l9Tb>SX079Hv_vFID;$eSb zV!e?-Aj<8lCYCHLFFPD%8U*;(%GmC?3pVp$F=%px6AHVtC9J`?aFd&V3g-mWT>+Yr zxl%7}A@eZxD|@#%2z>APW-KYz1)ZP&Fa>$QT+j+V@JszDW=;s&|*>w(3D){b4wcl|3 zxX#_zYrP3$*X#E;aR(0X@#Iv^NWLvzVd*atakzpv%pM%$&0O!DbLd-qA4D8_eVf*i z#m~1;`FQn1CwX0t>XGbdk4H327M81?BmVsz?c%sbh#EN&NVNH!E48F_;b zyd*GQy-`BI=+XrNYN8PVpL&DX!%4%Zz}Fx9Ejl^!0Qz>a@NA@_)bk=@$Xe<@-iM%;s+#M9jIx?n~i zxytPNYt4E0``0gG+@4*Av$>h&*m~uSs6E(&)7~N%9Khu~&knBJ^Ok~KX`;i{;`=kz z3e$RJ&xnn6C~J6fH4w>dJ-7xDd7m|r``>LHDF&C-82vHpcZGJuQd9tY;f9~Y`cc2f ztA^iyY>)BiDFT?p7=|*>$z9##2`panz;+X=O$14WrdIqFm(%>K`<<<)2W`S*d&>(? zMa$xy1Fk(DqH~2qV^j#)`waEl7)7t23yZoy$SOD7Q74I2^PvSD95<(hNUfiEu{T6C~?G?rPgoVn?QAXQ*iyrhSP;T8ao~@W)*izbLM+D%!oM z>uBrc7u;>!;Rv@h?2mS;N)4;u%^lT$#9ciI1$Etj3HtU@-jDd+;Yi;~cx~7Yhd)1M zb~xMUBJ{`u{+oZr4Ol%okU8(^7gz0F@mjCe!zm?t3(?SLrl3AXHmZ%sVhj!Be8_-p zXUV6+IX7hE)5&Zg@wAfjsnodnEhx=M7k`RJkki|;#-J;S-}VXLy)lXL)*M;aCEXZ3 zn(56tK7T&^diG9YWwoJe=QrGr)b)n0L-9ME;eb1hLJ~9wmdquW=+fTB?r{A)ey{D| z@Yn(M84RSo260U}x^EmG$+yHi%R>5z)zLx7tE)XXoeLFbpTM!GUH$6#Jli;~JZ=;k zmXnU4c13!)gIDL|UK>tEnvZ~s0GT06!+=x@&5tSd<9@7(Kfd~U&1Q>ygAjT^(cdgp zyOW;Nk%dMj>~D<(h?7*@$)Ra^@0Ne%$lyUg9{bM*&25URFZB$JuPWFbEgI8}ETUKR z=?uN`Mm?Nf(2r*g#(*xG|1m`PL7Q!F6HvLG{JuWT`RGET%z&pAyRk^){(D|!J(fac ztUi*e8Q`-+bI5u=)5(eXzz)gf;TwkaGV9u+`{I@IT1IRanqgn2(fS4a*|`Z_bty)@ z-6;odiw=)Xh}P7P?mSp5C{5eDJy8(Lpq>*^pbIg5_3%Nt^&KHRYP69Og4e|@`kjw{ zo$Gv79Ed4(N4IFLnQK-Lvi^J z{`}zhf4JqJR}laAUjf_c=kSjH74QD}-M{=V`+wsKE{Ibs4bF6EDRohJF`(})12xn` z^Rtt1Y-9~Y6ZznbOoUG=f92Dx0Z7h{=;P$pOq_qC(Q~yey0YU&R6*34}z@F;}3GI@W-Fz zSfR)NGJ1Sn$Z%Tw)WKkgvsBGLz36-Jq3t)~PdxDE;6Gvh^zglXch=oFxfdg{#%F_Q z&AXT@Z%(zb3GQ{sejI2vbfx0LRt^ge^YMmE20<>?WLD)1thX;IfA#)*Kc?~cxkFWZ zQUdMUbh$$b9*u4f$GWX2KejebeD8F^yP!Cc%Y)}14MW;rUdZLY`s0H)VEz5^#lQWq z`B%Z|zx{6Mi&g(_*!+2=!mkSPKYy|O6a6n^YWcfAKiv2yYh;BOzY%PO7%Rm1jRL=Y zT;YrrV*Eyd-#)Hz#(yEkZw`tdS9zzb=&WA^TT$S@Xo0K{V}%&MQQ)_aE1a=HjNd5m z+s75oSRux56!`7q3TOOZCdTI|%$*pIkpO*VHI8Zrquz_DMrod*BUxf*c15nk_NyW@ zO5z@l*jX>^#J-ISCe3?J)rIF7%FSA(<_H<8DA>7H#(tc-V^k7s8Aoc6Eio#bxRT0E z8BNj0;#2ovB;(TpxV*+<7KG~`dUcFiO4J#hP_WC7@!W?E_jKBK-eaR~0x|39dBZw~ zk=YZHEd01#3Lf!kl-U8#5jO4u94S666!)QjL}E(=2aXD_UhwJb!0aVSZvI&8eM^GZ z1Is*lsOMS$DBbxcH+IsA>|*Qvq=)izLlv@21SsQ&agj7ZISXYt-^g+B)22gqRZl%) z+{b72t3BK2-H)Ne+ojy=393e9 zQpv1oYH|(>Oons{Gu3DPI%*8+zy9mclQ|?QIa@oo3Ggh|IaXy@7d;$#C}EVANu7`` zshVngoUtwucc;W{FG}9f)`iJr`Rb{<(8sh|pPUQ#V!0RE^YlsL3|0l6<5dZH)KAIs zrxj%Dizt5j%0VUPu24#-Ek-!kk)C*3EzO)XL9c0-R2;RZg|=Cf%3RxUw6Gn;N8XK}X39V2aM*}l$-GPaRP6jfPM)ExgVftwRwgDpX^_{(&Tz9H#esNs7?@A?l&8oGQ$(zB}b7?lp`d0Td738h)RpRfMX*V!GO7xB5PLPpV_;A{UAS zYq=-{bSbr`3Qu~gP%GLxTWC`bK(5tD%s<5o%e!a=MP{n1Pp6bP4qRQ8^rgG;gTwd1 zzoX^Ju@pyK)u~lUY;7G9f-Ul%j(}3D#iR^h*M|T@xS+-|8rM-HnG0RY)<4#$||-T zyuNC9$aU^zvwvWv8&d-pS$g|Ab3yWk;S6hx$Fw-Z9!yrGERGMiZ&AXCJJeG~QX!)f zBUwj*uFFEs3~PSe+xc(=6wM;vFIqY>U`dfrtQmsdT!xITf|ho_b>w7{?Dzu@v5H*%(CQvHG!9(cRr^&fwnLixKBdst_PK((h_ zUNwB7pdd2RhBR#gAB!eUQ-PsETiealjWWnDzIf6Hy~)S%PO~-Vbo5D9ruc<3sjEp= zdiAXBPRlGN9)b7JO@2ZqJx^B|sg{K$)xnO+GLf1ZPm6knCyIyS~u&&df6@JvH7#?10hNtwVwDl@bIg^UYDDygQHLKAa7EcG;c85g`u{Yt& zUQ7%-PPk7Bb^=PME=NihN&bVw6iSS|{4WVf(^<)L+ndkLwGH}S%Iiu?>zw&cf~usF z$KI}0xHN7mB`wDwA??Vdl|f<1NKdGm-548pyJHdT)%~~`wfV2)(AIDgx*0RiNuGC~ zD970W>2t^CZ0q66)=;;=e%ZFKjUTUJg}tU{E9Pp+dlxBJia$#t#|`X+uDz)p%7-LC za(sPzPA-kpWpe5oHZ;$vm<5??iPofkwDyb|LHB*Lri53S%=tuuU#&}OHU61t-CR|c3O?LNB>PoWY*_rMc+vDNk@v@`@ z`Vv2w$e5lJBRO699va~GK`Dy#&~Jj{Ld^bZyFCTn|5e17dd0G*rNlpe-086Q+u7;Y z?q2qQ_3Z$SnGr+-?Z69t5nqPZ1h{9~+uJ{X`0ybuQc5>D>km_eo|icbk>1Qi89KH($FBVKYtmO zV38&M{hzDjp8=6cVnqIwn%WZEAP5x;Z6H=57t2ORuj)b_^lQz3QFT_IPeLn*q0$?x zp^7*JD&3l(mDbJZrq0f2IyySwGk&zJ4XQh7rLX+kd#-a-QC3#o&c+4|b5$hvNAT*R z8ZQZ)4Vj3rQpi=2H440w~-AAm{%!yf>qWga2*p zScWD34_7Jv6H3_amL(!*ZgCd%s(ghVLIMezyK&&}4-if3U~@H{^KRo9r^H}45S9QPCr&4<)$5(f?wd%>uHm4#m9x)(DaJ`Hrhuw zCMsNV=F?N7d=0%S`@JSzk4u6y=~df`T^QB-(xzL)tRSfr#POk3Q@$g=i%V+avtW?) zLL9f*2)nSCgm<({PqI-}YjC&QY}kU=Sd&IJmB{WDnZjK-X@&InIY3((%BGAK$o~lL z#(EOky<$>G46@&S7ZwKFbhD|w3-iab^V5#Fj7=-QSojv3Ax;QF`m@}Os$;Q2(XuQG zlCvITeMatk^|&eT2;CF8es*%U6{&cv+iDaW={c3PL=4wpl$Ppu^U%()w{|QTD99za4}PWU zagnM^=98~_OokHHz7|uXjB{Te@wN7t3|p{1BiSJfbav~rR#>937gLg1>^l2;T#VZx zy-9s!(tB{X8)P>8w{bD59)Qy3oQ5_92M5zc+xDOoLFb8OSQ`6mpoNDeGZnw)LZsBz?>oKyX1WV=OH`>a;rnZw0&V{SeB6I3~4u|W!CIZ{VPd|*AnGDt2#ewArxQP)KNWI%Wc^qxSp z_Ieq$roJKcLT8Apvz`y;WpxVzKQ91!0}NTSgpv$V7Jy^xstJH}l0oZP!YbIOI*-#R z*vV+p>W1RID${2tB>QI_mc$bmDlH}?Fm9*_`Pl7^=Pxk&BDaN4xW3WciA&v6-_s)o zK>Ri)w#6(y;XnWL`Leab8a$^frg=Qz+f$_K%aB8_D;l$gn!guHF#oN=93iyW`;>%k+eN8B>x7mgwQuZl}_hG&9OQ%o&AqgRdXcQY#J zk-F71flEtRAT8{h%xWSj@tBN*ymS5f_|i2_g4C^v%U;g$q9O76h72&9KL$O7L&CZ+-@q|Sd!+HgUR)hE?|0Th5XJVdlgH>GCY~0jfL#D{+MW%U;udi(V8&}a@)*};c{` zbi@H!#ZiL4nPsvym6f5a5MBy3j0on;K@a=^ zEcfmHb|6QT)a0nXUL{0sTy|>) zZdI=I9B+dx7!#j$<>kkHy_~>qurZ1Y1awDe9C{U?n(jl^6Nc1RZx+-wNG4U2NB>1e zciI<_t}U)hUDxNFV+)6&dGly{jxh8@jd=Xnx4*yN`wSZ=XF!?HCeQ$kDd+arMNdWr z7}^!&(Yut$6BbBG|1OMomvEM2$&zJTFsXnLVNQV z(ku-pm4mnfAVCY%kO^ex0&w^$DbwFo%a5FsjxUeJ%-%St{TFUPe|7a2M%I70a>cy= zgD@-PSRuzB6!_zZ74BFe#~&2<j&EMZ24kCKD z-#1(VgfJSUlsSu0`??_w(y?pT-81AdYcFsMTuo27z3*@u6ckCJxTaCf$A8>uY#Czv zI(u1J86YIT2fRIngmql2klc@mY!U_l={`Riz9uda{i2Hw$vs(gdfSup2qH10+;jzy z5>Tr?5rC-|N82(w@z+CKo-?TX#spw>FrRL3g82{u8IsOE5(Q<~Yd)rRkn7Uiwms2Xy1`qQHOOnzrTf1}kdEgAFaARP_*NS1 zXV(C=yaj(^5PDB9x6jNC0IUzdPITsKv9Y2-uC^SdTd%nW$BdMQ!THF0A1feDzb-HE zO2WX~ety(Nb|a~6lc|O)zkL4uBO4vMxc|&f{(5M8!12W;Q0VlS`Q}5Ttz%%Y5%JC! zlL^|#rn?crY&z)xh>E~4H(IomU@b2C<=;zk4qJ_UHZek-1TYlH%K?gxx8KTqRwJ0Y zrtdOu{k2TN#|Ewr{BTP$jW&t_>5!9`$G_`t)!`hJ?tY1it}z*A(B!=6v;(c@DO z&Em*ZTV@f?#iNfx^t#$Ol`H01b{mjHX{9?aA5E@D5(|>gr_pI01Ng*?M(bK^J!z0c z-^4(>^^1Nt(~_UKeHhv0012LF%~Of(0pQ)(ze7|j<;zt%z^;kE@eXA^?UT3&ng9eI zHI7jk85sfkW2T!sw5~WffWLb9%;}@r8eY;<(8qcB)KBnS49D`;aeAQGpvfwQC~#)! z3YN(vb^y2>AX27=g@>U$O0hO(R6Tf6CyVL-L(d;#5bn@n zzU@+;sECMrfamJn$pZ-CiK}nj`f|EX9v&Ibbt&V^hQUK1z!akCP5+h{gze$*P?1eD zjXnP)@N;6YmNl8OYw6~tb<2S`K4W)xnTm=EnD^!YfB(Oa48D$nIx6^4J^lg1t;x@) zub59kQ5E#?eFmg{>pZ#-$Bn43Rb=}jc#{oC69MG-yb+w^0Cqd50U*UoQ6A*A-7+hx zNI48^EwUo(!O9@+`gaY+5-DkE>o3zHbdTP;5bxV_0d^l$nqf}vXddQp48C|A!7vEv z=Wc$jM+@t4OK42+f}tV9=Z!=289wX?tNpa?Pwxij@($e{f4u=Zdc$papu_k*XpQV` zC?YK#wdUDsWCMy|?rr!wFOQ8y*l+`YI6t~+4B%|59rgaYd=6 zOQc6w|DFze-L6rD?i2{DB!0Pi8Gyc)hU1x^oLb889MeDqh+DbBC7>mGUu6&&=EkVhDa?u2FQ&dR>9w zSZ6U{YXPAnwQ%!u*b?a0y*TiJKb5^VrxYMu`UeJrT$5MbF(_O-W1^6DQmHv1Ap!P7 zcf3L%VD)$#ib1$xacj+8*sdijF20-NZp@v2qU^u|GLpsqil$btL*{L2{8#7ILx#2f2?SFQl*Gfm}kJvMBaFv$eW_IxvO zaZL`$%UjE@LqtSi^O}H2`|;AbZp+WbEB>|7W4xRNeXPfjpV7PtN&(`V^73P4$u!=@ zA$MsK)Q=CELgRKDAU`VPjUuC>{4Qs(HTY?yM89kJ^cT`luTdMl>N^JeoU)4_O1FSm zhB;2t#O>sh@Wv0P?dt35er~)Ij#S@sv3c@b~VP*)$6+Xl7yOU)OR<(&4ggG5xV zw_!-kI#{wXDA}JoXITH^l^cEuZz%;(H4Va`zt83P2L-JqZZRH26iQCqU{`Dyl6coZ z>A^RnoB6j^nCiWO}P56jmjDF?pVom zR$4g}cOnS!(N<4tkDqYG;Gj7$^$k%#8W8gb94|?L3KEr;W`~u#SVJUcJ^-PBd%mLu zS05XMnnRu;iUCb_fG?h=YTUHSWN?WrJ87dmGYbo!v~;|jhMSlw{w&T*+Wm7;F{=PJ zz+W@r%(1vedc?c)kf`XxJD4D_RX5yp=R3VUX)W@)eWLAe8ynG!>d&8!@Ne$qIe}nU z1Ox@k+pSAd4m~Br8D^*xT7&M2s@p- zWUeL6#MZXS+Y!O+2X2Hq<-fUiY~PR%9`{#u%ysmLy=Ia;=zJoX)tP$}_vxpiF`_iy z|4)0@9+h;q{V^wY%(6*mDy@8_3FMXp!;{0? zNs+K%u6<_Hjbme%$d39J(i?oTQdHcSEjtgE*XQ|R1jKD`P`gTd>k6&G-}cSO=$m7k z!1CgyOFn}6eGPNQrJS_QbMKhbsu%wiqsF4$6zrj(hda-in%q)(-7N-|v$bGovwoqY zgTowyUB>s2z`Q`oi$f9M0zTZo|5pay8CX}d?A(i)tF7_+%mT!o+?u$lBc=#~9|)tJ z{|pP-C_LN~0+MFv(ef_&tQiIyitYN0U_80j$?3~~EQ1Yys5e~t?Pty~pOhz!dv@=B z@b%st&IceGeZP6{nu2?q?W>+XdE(_Pe{3tLPc%#dfiCa@dr#HL6!$cmBiL(Y0BpQ< z8-&lFJ}EaJWxRZ^qqs4W{^s|^ zE%BPYZ+^N2FJ|<&O*+Mn?{^qpk#x!Am_Dr68>BvsX2ew{^H)rpA_I;0XB*n(janwz zl4a%ermtI*?B>9#egiwuNQZu;ZzQ(i&ZAexS`iAX9vi}fYwdn?W}1Wk?ZPmy4+a{vd)p8L+{PgKwHXa6Z1}60y zOE<}yyKgusIfhqq&A>T+16yLjyFC+K!Jb^@?(0YRZ5bzygJfZ4l6`*nXC+3i(?2-C zRwdnM>S%Z=5 zBX!hzvlqRM8(V!zfS;fg*V>pJ>rc=WR!gb{W*}MlWCJe=>cvkCm$OP*dBH^ZM- zfX%{Tx4f?yZ)oow!3RTIP0c*&25j?DbakMj0c_v-rtfW2ceyomzx!Gv3S455{Bn%2VeS=uSVWp|)DHu~-K(Z(@`ZN38xh$z;4PK0NJ@}} z)ZT2Y0~?Jd(ck*U$+NZ?&i*3w`ZuZF>>1sthbngi=0z6iyo=QkODTS+&N|-e`|Gyd zNmul51wU-~moIEUI?4@+j9iUW3|r)``5~>%J{h^Cu)sGvTPX6w3+&|xd9Ow3r!a$n z!h*+^V4h9pO{(@I3e1BslOU%LfG*5kF+A(0Cl_zHIeYpyZ4PR%-g73Wisx)i@H98t z1ST4tJ!gL@f`2buMQ=>{Pv3m|GCuNc+h86-^qRujOU}zzrCF(3g2Zj~`TB6P}bYN&9pe6pFv5fkS@R-oX zV>az4DKAxijd)ZZVdCDALM}gULwk>-A-r^L-BSf&|Bb$J#h&UTtJ_g0M&X?|3eppc zq&?n`&o!&?YP0I~0nxYajfA(<8D>+5!%JgV$Ru9&bn5=8 zfaUZHu*+Dnk#rENAvH{#+lEeTN^AZci+)I2CEYvOM+iF!>;h5IxQTgIW4^E0odp1wh4qsU{S`T{Z#>3G62r)GPErRd@ADG zkT-w!YiaZk;w|GIg-XpMQXVxoJC^Pu)LQx?6mo2l22mN#6YRJ~&Ju>ZI-L`WG7}A~ zY|ALog|T&!5>bG>`gxL(FG>39l);jw+%)tyoWfr-k6s%y9#eQ)VB_GHex1}L-`u&L z1g`-F3lV&Rnp=RB^_HVwWw8t{_pI&nlosqoXLeR$cme}YdY-eqVW2=5?h~aLu_!Pj zj67P5l-EntzY3KY3Lkc$TUNOtaZdruxtCcIP~9=pad#dwpD>c-3- z;RQd32x=$-Y!M!U9m?*(M+rDhAUg33ma<3~+k(jK<&Mi6QGIc>dJk@3^rbgkKjuB= z=9mGDiBlxm7^R6J#9QQU7^3bSU5l9`y$BekUQMRkq%W(>3;HCz-+@EfV&?EBkn@G$ z{h9BAo3x}a0xxgxumcksLjS+~srC-|(tFp*Y>K?kd}3k(9+BF>%<_^xwWNnir=KNp zed{J~7Z_MgoYvetcPRSM&LRPyG{*3ARSckwbFxSBsaFWY4BHYK+xA9^a3y~_NI%(0 z&8+sbl!?Nm(}ry2#zF~QMbfXZwa2b$&mRg3lg5d|DQeDBzi7Uxo!bVEM-FkJQl3Yc zrc+i<`A-NW>hKF4iK17w1=gzrqT-LTq|;1a(u-=$9)zpE!md3| zY?NG1NhEBQM|1fE%XUXvEK9Gu>~+t|ovz+E@ySM$oJY1A6}~W1+0D3+?;GesDbwqY zo!se)l8*UW?m7G%7g2o18FPub z9ja$o`2MkDWZ#l-k{)+yaBS$Dd7Nvzr;VMPCj@PGX7~5wWkcsIcXE3?DAS%m87$&& z!&9QV>;i9KOt`S8M?ha$G^%wXHd{~RMXHV94YE?%6k&Kv(8$#05xIANIri>$S3%qd z-75p5IjY=%5%TJXqmj=aR2qcl$7t-GusM1U1}E;su{w8%PjWubt!tAXU|?6cRkUKa zC>=wYF`69;^88XtVg`VnF_C-=Gg@51Oz$~4=h(Kq@G98*pDPd}F*x~%rD#x4qFg45Y1wB84}LOyD%U>j>~0LG1YnMF0h&;RJ| z;DxW{gV8U)U(@k!Z2YBEJm#an>;`1;z|stc?*VsoaM5KlGyxY}bQn2DI*1$SHLt;X zfC(GetF1p+gMWHnY7QumG4&p-H%}1n!uU{eU{IFYcLBdZc3e;+=rxaL+NwUO!DPE5 zWZm?jsf^wM!BsmAlF2hQEjvFM9_#Bm67f*PESe5{@vGRnz48q0MTz@czVES#(SG zv}$ehggozPrJ@(7w6{uCb~m7qt-`4|&1(KheX~a(B3V+*<0jKw!=*Qsm)AH$zLLQ{ z-LJO#1%#{NzN>PNY~@BLw#i346RG;D)PT1X$GJ1TJb7id?S08Y5SxfoS?mgCthwRy zf1PAcJeHY$!Sn>2{eY3IEo$D}oVx9TZ8tS1j!fZSbknH*7$0kmbJ9fH0Sy!O&1rS(Ceda(XA* zvAsdCgS#jC5Vxjzb=@;E85P#DL|I%c+* zEnuZKs|>+F3l4!oU1akYB7X1y5TUFhsD~wMrK>Z9wGFWga(WXL-N7S|{jg|kv%!PP zI#+O_d(|M`K`t_k2qhk^;N?sRgu2#JKG_5EimQ!TV6kD=M^8dHAJkY<^k>3c_y$nd zyafMUlVl(sa;L;0%UqgaQ~Gdt+|q*>jPJ7lm3ppiYciegrSMr!n{kjs>kQ~HiD6a| z*R_xBZ5nreI6D7r<^b*NvVpj|rvK=O zZzsLq+58_R-Chq`c<8{;fuV(fmZ-A~9T-{&Xo)(@(1D?afR^~*vy5As&RSyUS4=yj ze#1rX4U8inM;KcjcdxzFzp=0D(Z@f2uyDs>ujRk}vf#^a7pzGB+}t{GbEeUED>iQa z^i-(-uMTroFTM2bFGrv4TDkY*aD4rh&)`RYKK$HMA8v70vh*YR9ddIxTXO75vj;b&g_ zf_qiHu`Ew26PQ%(Q(b5GiJN3|e44wZ+5V56du~#qi^BrFf^IAs6|LmXDYa7=S74M6 zZSFX;nLPEOYjiIfdr3TFY3KDXIx6}=jnh-Mm=GLtj%OOnH3|_od`?3tXQLl;gGbt` z0ciszQ)Xc){^W_~&mT|i?`AS2=iErArIi(&g9@c%4(qOBcQ?~Xep1m^)7?&goWbX| zIcFNP&ElUQ&=@oUYK*Lt?LGl!aS@qDkEG>K)uyLd1`lmYiYm~aa#&lNrE$=*LNgc8*1SJPGIWYbZ{eB{S&=}m^Rdx}TVNdK4BlZf#u_WA|k5>5>4 zf$0>l+JEtA_(D3_Y?N!shr6Zcp35#%kN1wurDK6ssw67>zGcYXz?aqc=cVndQ{3y( zvjUWmZr@5KNz*O~sf(LD273_WF#n#SNImsCN?JL1+QK6w8`8l#6#dxB4v`Il-H#n1 z$*R@Wu0DYTy6Ty^O=FEeS~<>THWHK%Dq*Y6V7G(@wPWw!0ptmww4oH5XE)oOR7*8K zjpK$htzTsk9emIuh|HpB+E2Lg>ASFGNJoIARFOZa9>%zCSOWH*eGaHKLn(dc(%KJw z`1OzT_wgzy&BvS5A0JnMcVrh`Pow~L62zYXL^Ba!CN%+q8sd9phSNfSXY7g=7qF2l z4b$8t;u?4yUG*wUe%OcpTAMpAfin)5u%vviB64M6*o@REEuip z3W62Jj#1M>M4v=notz#_@+=D?4TxZVw$r`2{_;BfZ-XIE%PDHEKM|j&@6&XSpM4q^ zJnEZ5!z1TBcQEfX8MS3k<$w?M+OmJf4c#k&`2odcIF?3w7JIO!BkwofPJ`Co_3XSh zHz$K6VJ{qg`80|lz(kQ3k5;2iKXS9rK9g9+S}&u2VLops-{sTM!Ku@FSMw^E(me7{ z(sKH;PuviI=R#|Pcu`guje!U-&xT3@`KP2Q8kOWcHHpcOwdnvO99*Axpm85h+Fg`F zV?*XVLU|Pndyw2`-pLLsu35o{tNLF+;kU+_2dIO<2}%las} zXagXlwVwFTbV~cMB_3!o*(GS&(jeUNGtUg?nmQ3x!J{Uj|*`)jZpZ z+~FyyiJ`tvp15WUeUz6z-_2fs5B)0wODy+3mc=0LEXbu~Y%ou+KD-jvzFvNEh@tlT zO4{&R32hVS+EmL*C$XDzYT9tV3grCXyV&l;t$aFYoB;O>kTLC;V$&^vVzG}Qf83_3 znsky!1Rusps8M}v$JbDM@rYc2q|;v^nrZX`j=RDdGF^~LpV|`Id?86$J4tg9%@aw2 z4+HuMB8=x}&t`V<-Ej<(=zfOepZ$jqaz5w=l$DH~c+NN0uw)a{uI-a?JpSnM#$7z8 z3zJR~ZfJw>-;E3K-kRoxdd4l8iqAVZEhjDs5`+h%dOybXW{T$KEbB0VtvsjxKvWIc zMFyt^W)~vk#9Y0+a{~KvJzxSiWlm89>eBBRs%5a^Z&*fOD}^+h-OLsjr6X*|I=*3U zERNen8eRA^qq?NN^zvXxrJ%0=uphn-;ydxUeH9dx)n2+`Q#l9be;;xY?JDH3+6p2x zW(e>Wd!LuFasXAW#Ra$knyXxnPqa8Nae+nodZO4CoB(nR;3Nn@0qn3$#)_NyiS2I| zj{n_F2h%L)*aZVO<&UqE9crC$!vX(X=1tXn*-?LPSqjqe@K6NoLGP*;;kJ(K%p%9A zxjB!Z0Y2Ycyn{e~=1YQa<*|;e?%*|2?}88zRWBg=y54cNCnI17+T_0jkBSye%>#VX zCvp|*sOHX|GV2bmST4TjEOk{FCm#Wrc?wR2nRhJhF72NvVD5_cu@ys6>?PhxUO2;W$}mF2!Ol_xQqcKs@4i?9^f}Jla(lE0h6Fv4LtJmHpyo@v+3I;18ov( z{nL=2-ohsCl&YZN4Y*Tw%qaBD6)2)gA`RKpV| zp45Q)1M^CIX~?l1#XV?(+CzgJ8xfR|N#i`&lk%e^%Z|i+)LrNGzYUyY^Ci49QX>`4 zAy&N)ChUp}jxJXAQ7j=BPMszZrcq@%E`n8QMbC} zXIkEMVX-xkGUZ*RF~CU!78{7Ls-AW(|19p%Gy&X-8fYd@eNbcYkST;B>m#YX;Jgoi zpWwW>>sA|-2LaMoEVx)=ccx4dJ19{YucA-Zei)|atG0)7P`q+*zbdoifK5+#S{({y zN-iY$4#1CZF#{aLSje4#jp~8cD)NO{_bJk54+0dbR!A!YRYV4O+v#*2HM4puuy_Q& z@Fq1;0i{rn(n}YdIeYeA&3m?OhCM1T>=7Z4y`n<3!@PC@Pdp0cP2OkFMsBukgi$Do zg`%FFg|rfAXC_RR-i)LEC4;vWm{X(j@Jg$@uZ zE`rq8$bjzx4?YBSlg^|B$Hdrmyx*mK&x?}RH&9-eO#VC11NHZhIe^M0qwjUe)Hfw1 z`fWr!cQ{y(a_iPS_r|aMHHr^i3X;-M2NwO^Sw8z^wz$aOd?J3$`!@EzJzJFIZ;k-_ z(BfU8zrXy9?jmMk(IKHjVitkfR%a7BBxVtqZFM%GLt++z*;Z#0IwWQhm~C}7p+jO8 zf!S7P6FMYj5twatHlage7J=DTXA?RkW)YZe|KHfePelA|h4k5b|5JM_{2(jnJ=2K& a0=)}i%e!6cH~K=~-tw*YH`l%nKK?)MVz__+ literal 0 HcmV?d00001 diff --git a/microsite/blog/assets/21-06-24/jobs-to-be-done.png b/microsite/blog/assets/21-06-24/jobs-to-be-done.png new file mode 100644 index 0000000000000000000000000000000000000000..da86befcc22bd9e8f89e8771f91c5aeda9751579 GIT binary patch literal 549039 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

    >O(zNuOb}aavrJ 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�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Νx2w5MM;uH0Hj&T^e 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%0D;)hN*h{u0|q=3cza5#E9?;);c`DqP^%yoP? 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

    >Wf0UB9`c_K{)fk+p*is9|F?tmtG^X8BR?OF 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 NZpllfOZ@Td{{WyMQ~>}0 literal 0 HcmV?d00001 diff --git a/microsite/blog/assets/21-06-24/search-results.png b/microsite/blog/assets/21-06-24/search-results.png new file mode 100644 index 0000000000000000000000000000000000000000..041f86488c7bad05d0e5d583b0ac12078c702666 GIT binary patch literal 118514 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℞2`F<4`4sS*En>3 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

    CX>MP6kEh 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 E4^54V>Hq)$ literal 0 HcmV?d00001 From 05623c9fc7c53d91ffef196e2e086b3e521f4af9 Mon Sep 17 00:00:00 2001 From: Jeff Feng Date: Wed, 23 Jun 2021 20:14:15 -0400 Subject: [PATCH 27/89] Update vocab.txt Adding "reusability" and "siloed" to the dictionary Signed-off-by: Jeff Feng --- .github/styles/vocab.txt | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index f91be8cda7..105059b403 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -202,6 +202,8 @@ repo Repo repos rerender +Reusability +reusability rollbar Rollbar Rollup @@ -218,6 +220,7 @@ seb semlas semver Serverless +siloed Sinon Snyk sourcemaps From b2f86ddf7d42bafe4e7a98d4d36c482bda44bb37 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 24 Jun 2021 04:09:38 +0000 Subject: [PATCH 28/89] chore(deps-dev): bump @storybook/addon-actions from 6.1.17 to 6.3.0 Bumps [@storybook/addon-actions](https://github.com/storybookjs/storybook/tree/HEAD/addons/actions) from 6.1.17 to 6.3.0. - [Release notes](https://github.com/storybookjs/storybook/releases) - [Changelog](https://github.com/storybookjs/storybook/blob/next/CHANGELOG.md) - [Commits](https://github.com/storybookjs/storybook/commits/v6.3.0/addons/actions) --- updated-dependencies: - dependency-name: "@storybook/addon-actions" dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- yarn.lock | 373 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 196 insertions(+), 177 deletions(-) diff --git a/yarn.lock b/yarn.lock index 804e1abd1e..b03dc999cc 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4277,27 +4277,27 @@ integrity sha512-VYOdo8P7lIScAkl02nB9KpUAuOYMManryBIBuKJkAw5D3aVtLobfmdIKvdV6MqEmGMEQPbn7w/UpnjJYhUH+IA== "@storybook/addon-actions@^6.1.11": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.1.17.tgz#9d32336284738cefa69b99acafa4b132d5533600" - integrity sha512-4hyAvmjnI4C1ZQ7/t21jKKXE0jO1zAk310BkYin0NJf77Qi0tUE1DNOwirJY/xzRih36wWi1V79c/ZOJNsLv9Q== + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/addon-actions/-/addon-actions-6.3.0.tgz#e5a24c69d70da9aa98560f19d10c06a50495ca2e" + integrity sha512-7Ls1OIAdtAa4a27/bTuAlejQee4j7bFBkRzAeaHzcaZT1VVXoF6yBfMGuEGJI8brQ+KuSaIhIU2b0Iuzq47dDQ== dependencies: - "@storybook/addons" "6.1.17" - "@storybook/api" "6.1.17" - "@storybook/client-api" "6.1.17" - "@storybook/components" "6.1.17" - "@storybook/core-events" "6.1.17" - "@storybook/theming" "6.1.17" - core-js "^3.0.1" - fast-deep-equal "^3.1.1" - global "^4.3.2" - lodash "^4.17.15" - polished "^3.4.4" + "@storybook/addons" "6.3.0" + "@storybook/api" "6.3.0" + "@storybook/client-api" "6.3.0" + "@storybook/components" "6.3.0" + "@storybook/core-events" "6.3.0" + "@storybook/theming" "6.3.0" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + polished "^4.0.5" prop-types "^15.7.2" - react-inspector "^5.0.1" + react-inspector "^5.1.0" regenerator-runtime "^0.13.7" ts-dedent "^2.0.0" util-deprecate "^1.0.2" - uuid "^8.0.0" + uuid-browser "^3.1.0" "@storybook/addon-links@^6.1.11": version "6.1.11" @@ -4367,22 +4367,7 @@ global "^4.3.2" regenerator-runtime "^0.13.7" -"@storybook/addons@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.1.17.tgz#ab0666446acb9fc19c94d7204dc9aafdefb6c7c2" - integrity sha512-3upDPJPzUkls2V3Fozzg+JOcv138bF90pbdRe9YSNu37QvRIL+iQODY7oFygMl+kqjG2F1FGw5EvxAV1mnlwCw== - dependencies: - "@storybook/api" "6.1.17" - "@storybook/channels" "6.1.17" - "@storybook/client-logger" "6.1.17" - "@storybook/core-events" "6.1.17" - "@storybook/router" "6.1.17" - "@storybook/theming" "6.1.17" - core-js "^3.0.1" - global "^4.3.2" - regenerator-runtime "^0.13.7" - -"@storybook/addons@6.2.9", "@storybook/addons@^6.1.11": +"@storybook/addons@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.2.9.tgz#b7ba2b9f0e15b852c7d6b57d04fb0a493c57477c" integrity sha512-GnmEKbJwiN1jncN9NSA8CuR1i2XAlasPcl/Zn0jkfV9WitQeczVcJCPw86SGH84AD+tTBCyF2i9UC0KaOV1YBQ== @@ -4397,6 +4382,21 @@ global "^4.4.0" regenerator-runtime "^0.13.7" +"@storybook/addons@6.3.0", "@storybook/addons@^6.1.11": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/addons/-/addons-6.3.0.tgz#a86849f46a654d2d78b91fad0088264a32d4e58e" + integrity sha512-/dcq20HtdSw5+cG8znR59Y/uv2zCR2VjRK3N52IkGWk162b/UbSjjL0PhNnnQFGpH9Fruft6mqvjTAKT41kmJw== + dependencies: + "@storybook/api" "6.3.0" + "@storybook/channels" "6.3.0" + "@storybook/client-logger" "6.3.0" + "@storybook/core-events" "6.3.0" + "@storybook/router" "6.3.0" + "@storybook/theming" "6.3.0" + core-js "^3.8.2" + global "^4.4.0" + regenerator-runtime "^0.13.7" + "@storybook/api@6.1.11": version "6.1.11" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.1.11.tgz#1e0b798203df823ac21184386258cf8b5f17f440" @@ -4447,31 +4447,6 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/api@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/api/-/api-6.1.17.tgz#50393ce9b718063b67680212df895eceacc0c11d" - integrity sha512-sthcfuk2EQ3F5R620PBqpI4Pno3g7KQm6YPZA0DXB+LD/z61xH9ToE1gTLF4nzlE6HwghwkXOZyRwDowRdG+7A== - dependencies: - "@reach/router" "^1.3.3" - "@storybook/channels" "6.1.17" - "@storybook/client-logger" "6.1.17" - "@storybook/core-events" "6.1.17" - "@storybook/csf" "0.0.1" - "@storybook/router" "6.1.17" - "@storybook/semver" "^7.3.2" - "@storybook/theming" "6.1.17" - "@types/reach__router" "^1.3.7" - core-js "^3.0.1" - fast-deep-equal "^3.1.1" - global "^4.3.2" - lodash "^4.17.15" - memoizerific "^1.11.3" - regenerator-runtime "^0.13.7" - store2 "^2.7.1" - telejson "^5.0.2" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - "@storybook/api@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/api/-/api-6.2.9.tgz#a9b46569192ad5d8da6435c9d63dc4b0c8463b51" @@ -4498,6 +4473,32 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/api@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/api/-/api-6.3.0.tgz#5ecb646e7c3c4c7c494bb15f4c94554f7f4ee09e" + integrity sha512-swPMcQadLDRTnMjL9dwY6K1zXHn3KcAa3euvSHd1R4OKXTSBBj1zHvIaOrq6yHz7RIYOACmZlEV3CUru9FlvEA== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/channels" "6.3.0" + "@storybook/client-logger" "6.3.0" + "@storybook/core-events" "6.3.0" + "@storybook/csf" "0.0.1" + "@storybook/router" "6.3.0" + "@storybook/semver" "^7.3.2" + "@storybook/theming" "6.3.0" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + regenerator-runtime "^0.13.7" + store2 "^2.12.0" + telejson "^5.3.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/channel-postmessage@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.1.15.tgz#80ea2346d18496f9710dd7f87fd2a9eca46ef36f" @@ -4511,18 +4512,18 @@ qs "^6.6.0" telejson "^5.0.2" -"@storybook/channel-postmessage@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.1.17.tgz#309ce67c94637ec13319d4ce360a8f3742ddbaf4" - integrity sha512-2nVqxq4oZdSITqhFOnkh1rmDMjCwHuobnK5Fp3l7ftCkbmiZHMheKK9Tz4Rb803dhXvcGYs0zRS8NjKyxlOLsA== +"@storybook/channel-postmessage@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/channel-postmessage/-/channel-postmessage-6.3.0.tgz#96e7aea034ec1c4f397323ab7923eaa80d017324" + integrity sha512-q7FeNWIIrvZxycIMBscqahFLygxAa2L4eJ9oxZFF9zJpSV80bxDalMou3Uo7RvDJFrAeHCanF1Y7bnEDMus4yg== dependencies: - "@storybook/channels" "6.1.17" - "@storybook/client-logger" "6.1.17" - "@storybook/core-events" "6.1.17" - core-js "^3.0.1" - global "^4.3.2" - qs "^6.6.0" - telejson "^5.0.2" + "@storybook/channels" "6.3.0" + "@storybook/client-logger" "6.3.0" + "@storybook/core-events" "6.3.0" + core-js "^3.8.2" + global "^4.4.0" + qs "^6.10.0" + telejson "^5.3.2" "@storybook/channels@6.1.11": version "6.1.11" @@ -4542,15 +4543,6 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/channels@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.1.17.tgz#2cc89a6b9727d19c24b15fa3cb15569b469db864" - integrity sha512-MUdj0eKr/AbxevHTSXX7AsgxAz6e5O4ZxoYX5G8ggoqSXrWzws6zRFmUmmTdjpIvVmP2M1Kh4SYFAKcS/AGw9w== - dependencies: - core-js "^3.0.1" - ts-dedent "^2.0.0" - util-deprecate "^1.0.2" - "@storybook/channels@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.2.9.tgz#a9fd7f25102cbec15fb56f76abf891b7b214e9de" @@ -4560,6 +4552,15 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/channels@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/channels/-/channels-6.3.0.tgz#f378c6ee03e0c72a2ee9263c8dfdfa4a7a1bcf51" + integrity sha512-E+SCQLSIlCaOGKEkZ8rFKNyH24/N4IA6h+EDF+9mhw3yT4iv7NCoswCgqX7JhyhSjWkM01onhuMVUVKVvi7CSw== + dependencies: + core-js "^3.8.2" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/client-api@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.1.15.tgz#8f8ead111459b94621571bdb2276f8a0aace17b1" @@ -4584,27 +4585,27 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" -"@storybook/client-api@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.1.17.tgz#3ced22f08a47af70ccf8929111bc44b79e9e8ec0" - integrity sha512-Loz/wdh0axgq0PS19tx0tGEFEkFWlYc6YauJGHjygYa1xX7mJ54hDoaTolySCXN1HtfZn08D847yjGSN2oIqVg== +"@storybook/client-api@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/client-api/-/client-api-6.3.0.tgz#a285c4b64ec318f360ade31d0c87c22e6e1db2a6" + integrity sha512-5HLtYPBOHif9AdzwLCrVbMQdOJ2dne9zv7oTo6Yl0wvLhbr6V/VypoXE0CgFF3hAI2iUquG5z00KlrE8UErC5Q== dependencies: - "@storybook/addons" "6.1.17" - "@storybook/channel-postmessage" "6.1.17" - "@storybook/channels" "6.1.17" - "@storybook/client-logger" "6.1.17" - "@storybook/core-events" "6.1.17" + "@storybook/addons" "6.3.0" + "@storybook/channel-postmessage" "6.3.0" + "@storybook/channels" "6.3.0" + "@storybook/client-logger" "6.3.0" + "@storybook/core-events" "6.3.0" "@storybook/csf" "0.0.1" - "@types/qs" "^6.9.0" - "@types/webpack-env" "^1.15.3" - core-js "^3.0.1" - global "^4.3.2" - lodash "^4.17.15" + "@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.6.0" + qs "^6.10.0" regenerator-runtime "^0.13.7" stable "^0.1.8" - store2 "^2.7.1" + store2 "^2.12.0" ts-dedent "^2.0.0" util-deprecate "^1.0.2" @@ -4624,14 +4625,6 @@ core-js "^3.0.1" global "^4.3.2" -"@storybook/client-logger@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.1.17.tgz#0d89aaf824457f19bf9aa585bbcada57595e7d01" - integrity sha512-oqExrxhmws0ihB47sjdynZHd3OpUP4KWkx4udG+74lYIvBH+EZmQ9xF+UofeY3j5p1I9k8ugEcVKy0sqh1yR3w== - dependencies: - core-js "^3.0.1" - global "^4.3.2" - "@storybook/client-logger@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.2.9.tgz#77c1ea39684ad2a2cf6836051b381fc5b354e132" @@ -4640,6 +4633,14 @@ core-js "^3.8.2" global "^4.4.0" +"@storybook/client-logger@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/client-logger/-/client-logger-6.3.0.tgz#3188f84dd10353d225efadee9f24928395d38aab" + integrity sha512-x/y820f/2Jm6RW5TxRv7IlbF6zWpTkHoajfwYuTpK/OXvK5gx6dwXGdgjNoaAGofGRz5SVjDjTDPOcd5X5AUPw== + dependencies: + core-js "^3.8.2" + global "^4.4.0" + "@storybook/components@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/components/-/components-6.1.15.tgz#b4a2af23ee6b9cba4c255191eae3d3463e29bfb7" @@ -4666,32 +4667,6 @@ react-textarea-autosize "^8.1.1" ts-dedent "^2.0.0" -"@storybook/components@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/components/-/components-6.1.17.tgz#f92d36e370ec6039d8c7cee9ef13dda866eed3da" - integrity sha512-rIEll0UTxEKmG4IsSS5K+6DjRLVtX8J+9cg79GSAC7N1ZHUR1UQmjjJaehJa5q/NQ5H8C39acxpT4Py/BcsL2g== - dependencies: - "@popperjs/core" "^2.5.4" - "@storybook/client-logger" "6.1.17" - "@storybook/csf" "0.0.1" - "@storybook/theming" "6.1.17" - "@types/overlayscrollbars" "^1.9.0" - "@types/react-color" "^3.0.1" - "@types/react-syntax-highlighter" "11.0.4" - core-js "^3.0.1" - fast-deep-equal "^3.1.1" - global "^4.3.2" - lodash "^4.17.15" - markdown-to-jsx "^6.11.4" - memoizerific "^1.11.3" - overlayscrollbars "^1.10.2" - polished "^3.4.4" - react-color "^2.17.0" - react-popper-tooltip "^3.1.1" - react-syntax-highlighter "^13.5.0" - react-textarea-autosize "^8.1.1" - ts-dedent "^2.0.0" - "@storybook/components@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/components/-/components-6.2.9.tgz#7189f9715b05720fe083ae8ad014849f14e98e73" @@ -4722,6 +4697,36 @@ ts-dedent "^2.0.0" util-deprecate "^1.0.2" +"@storybook/components@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/components/-/components-6.3.0.tgz#5ad372abd60ee0cb02516f960f514659e3fbf865" + integrity sha512-TDcazQAtNgE1E33jKKABx51XpvWyXMcJZFWA0d5wu8XrElrL1PuZqz7dPePoWKGMfTaPYWP6rRyDg4Svv36j+A== + dependencies: + "@popperjs/core" "^2.6.0" + "@storybook/client-logger" "6.3.0" + "@storybook/csf" "0.0.1" + "@storybook/theming" "6.3.0" + "@types/color-convert" "^2.0.0" + "@types/overlayscrollbars" "^1.12.0" + "@types/react-syntax-highlighter" "11.0.5" + color-convert "^2.0.1" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + markdown-to-jsx "^7.1.3" + memoizerific "^1.11.3" + overlayscrollbars "^1.13.1" + polished "^4.0.5" + prop-types "^15.7.2" + react-colorful "^5.1.2" + react-popper-tooltip "^3.1.1" + react-syntax-highlighter "^13.5.3" + react-textarea-autosize "^8.3.0" + regenerator-runtime "^0.13.7" + ts-dedent "^2.0.0" + util-deprecate "^1.0.2" + "@storybook/core-events@6.1.11": version "6.1.11" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.1.11.tgz#d50e8ec90490f9a7180a8c8a83afb6dcfe47ed66" @@ -4736,13 +4741,6 @@ dependencies: core-js "^3.0.1" -"@storybook/core-events@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.1.17.tgz#697ed916fcb2a411bc9f8bdbfacd0eb9d394eb58" - integrity sha512-xBI7kmyROcqhYNmFv4QBjD77CzV+k/0F051YFS5WicEI4qDWPPvzaShhm96ZrGobUX3+di4pC11gqdsrFeNCEg== - dependencies: - core-js "^3.0.1" - "@storybook/core-events@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.2.9.tgz#4f12947cd15d1eb3c4109923657c012feef521cd" @@ -4750,6 +4748,13 @@ dependencies: core-js "^3.8.2" +"@storybook/core-events@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/core-events/-/core-events-6.3.0.tgz#5e220a866db5b93550b5c3464774a7b10ad036a6" + integrity sha512-ZGTm5nQvFLlc2LVgoDyxo99MbQcFqQzkxIQReFkO7hPwwkcjcwmdBtnlmkn9/p5QQ5/8aU0k+ceCkrBNu1M83w== + dependencies: + core-js "^3.8.2" + "@storybook/core@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/core/-/core-6.1.15.tgz#7ff8c314d3857497bf2e26c69a1fa93ef37301aa" @@ -4926,18 +4931,6 @@ memoizerific "^1.11.3" qs "^6.6.0" -"@storybook/router@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/router/-/router-6.1.17.tgz#96746878c50c6c97c7de5a1b23a9503c5d648775" - integrity sha512-wLqSOB5yLXgNyDGy008RUvjVRtVMq7lhmMRicSIxgJpkakPrMRN8n/nK7pxgQc/xDTphnS0u1nT01i97WszhCg== - dependencies: - "@reach/router" "^1.3.3" - "@types/reach__router" "^1.3.7" - core-js "^3.0.1" - global "^4.3.2" - memoizerific "^1.11.3" - qs "^6.6.0" - "@storybook/router@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/router/-/router-6.2.9.tgz#547543031dd8330870bb6b473dcf7e51982e841c" @@ -4954,6 +4947,22 @@ qs "^6.10.0" ts-dedent "^2.0.0" +"@storybook/router@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/router/-/router-6.3.0.tgz#8b63773f11fe4c6749ccfda5d725c94275f0a459" + integrity sha512-RJcRVI6IqffLOU6k9GrlB3cXLLK5TRmFSIjwW3lEHVhj313e56uLRYTylT11aBf8bPEQ+MeQVe2sqQUBG3Ugng== + dependencies: + "@reach/router" "^1.3.4" + "@storybook/client-logger" "6.3.0" + "@types/reach__router" "^1.3.7" + core-js "^3.8.2" + fast-deep-equal "^3.1.3" + global "^4.4.0" + lodash "^4.17.20" + memoizerific "^1.11.3" + qs "^6.10.0" + ts-dedent "^2.0.0" + "@storybook/semver@^7.3.2": version "7.3.2" resolved "https://registry.npmjs.org/@storybook/semver/-/semver-7.3.2.tgz#f3b9c44a1c9a0b933c04e66d0048fcf2fa10dac0" @@ -5014,24 +5023,6 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" -"@storybook/theming@6.1.17": - version "6.1.17" - resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.1.17.tgz#99cc120a230c30458d833b40c806b9b4dff7b34a" - integrity sha512-LpRuY2aIh2td+qZi7E8cp2oM88LudNMmTsBT6N2/Id69u/a9qQd2cYCA9k9fAsg7rjor+wR/N695jk3SGtoFTw== - dependencies: - "@emotion/core" "^10.1.1" - "@emotion/is-prop-valid" "^0.8.6" - "@emotion/styled" "^10.0.23" - "@storybook/client-logger" "6.1.17" - core-js "^3.0.1" - deep-object-diff "^1.1.0" - emotion-theming "^10.0.19" - global "^4.3.2" - memoizerific "^1.11.3" - polished "^3.4.4" - resolve-from "^5.0.0" - ts-dedent "^2.0.0" - "@storybook/theming@6.2.9": version "6.2.9" resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.2.9.tgz#16bf40180861f222c7ed1d80abd5d1e3cb315660" @@ -5050,6 +5041,24 @@ resolve-from "^5.0.0" ts-dedent "^2.0.0" +"@storybook/theming@6.3.0": + version "6.3.0" + resolved "https://registry.npmjs.org/@storybook/theming/-/theming-6.3.0.tgz#4b6ef023631663d8e50f1348469b9a9641244cd0" + integrity sha512-Mtnq8qFv/TTtnl1sB6DGBCg/kJq7sR2e2uh/Uy2sHyksnhVITVJxEIFHSBo2L+IE6y0S2Oh6F9WdddWAO4Ao2g== + dependencies: + "@emotion/core" "^10.1.1" + "@emotion/is-prop-valid" "^0.8.6" + "@emotion/styled" "^10.0.27" + "@storybook/client-logger" "6.3.0" + core-js "^3.8.2" + deep-object-diff "^1.1.0" + emotion-theming "^10.0.27" + global "^4.4.0" + memoizerific "^1.11.3" + polished "^4.0.5" + resolve-from "^5.0.0" + ts-dedent "^2.0.0" + "@storybook/ui@6.1.15": version "6.1.15" resolved "https://registry.npmjs.org/@storybook/ui/-/ui-6.1.15.tgz#a0f6c49fcf81cf172cd2de4c8dba2be1296891f6" @@ -6250,6 +6259,11 @@ resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.4.tgz#a59e851c1ba16c0513ea123830dd639a0a15cb6a" integrity sha512-+wYo+L6ZF6BMoEjtf8zB2esQsqdV6WsjRK/GP9WOgLPrq87PbNWgIxS76dS5uvl/QXtHGakZmwTznIfcPXcKlQ== +"@types/qs@^6.9.5": + version "6.9.6" + resolved "https://registry.npmjs.org/@types/qs/-/qs-6.9.6.tgz#df9c3c8b31a247ec315e6996566be3171df4b3b1" + integrity sha512-0/HnwIfW4ki2D8L8c9GVcG5I72s9jP5GSLVF0VIXDW00kmIpA6O33G7a8n59Tmh7Nz0WUC3rSb7PTY/sdW2JzA== + "@types/raf@^3.4.0": version "3.4.0" resolved "https://registry.npmjs.org/@types/raf/-/raf-3.4.0.tgz#2b72cbd55405e071f1c4d29992638e022b20acc2" @@ -6670,7 +6684,7 @@ "@types/webpack" "^4" http-proxy-middleware "^1.0.0" -"@types/webpack-env@^1.15.2", "@types/webpack-env@^1.15.3": +"@types/webpack-env@^1.15.2", "@types/webpack-env@^1.15.3", "@types/webpack-env@^1.16.0": version "1.16.0" resolved "https://registry.npmjs.org/@types/webpack-env/-/webpack-env-1.16.0.tgz#8c0a9435dfa7b3b1be76562f3070efb3f92637b4" integrity sha512-Fx+NpfOO0CpeYX2g9bkvX8O5qh9wrU1sOF4g8sft4Mu7z+qfe387YlyY8w8daDyDsKY5vUxM0yxkAYnbkRbZEw== @@ -15402,7 +15416,7 @@ is-docker@^2.0.0: resolved "https://registry.npmjs.org/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== -is-dom@^1.0.9, is-dom@^1.1.0: +is-dom@^1.0.0, is-dom@^1.0.9: version "1.1.0" resolved "https://registry.npmjs.org/is-dom/-/is-dom-1.1.0.tgz#af1fced292742443bb59ca3f76ab5e80907b4e8a" integrity sha512-u82f6mvhYxRPKpw8V1N0W8ce1xXwOrQtgGcxl6UCL5zBmZu3is/18K0rR7uFCnMDuAsS/3W54mGL4vsaFUQlEQ== @@ -17992,7 +18006,7 @@ markdown-to-jsx@^6.11.4: prop-types "^15.6.2" unquote "^1.1.0" -markdown-to-jsx@^7.1.0: +markdown-to-jsx@^7.1.0, markdown-to-jsx@^7.1.3: version "7.1.3" resolved "https://registry.npmjs.org/markdown-to-jsx/-/markdown-to-jsx-7.1.3.tgz#f00bae66c0abe7dd2d274123f84cb6bd2a2c7c6a" integrity sha512-jtQ6VyT7rMT5tPV0g2EJakEnXLiPksnvlYtwQsVVZ611JsWGN8bQ1tVSDX4s6JllfEH6wmsYxNjTUAMrPmNA8w== @@ -21319,7 +21333,7 @@ promzard@^0.3.0: dependencies: read "1" -prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: +prop-types@^15.0.0, prop-types@^15.5.10, prop-types@^15.5.7, prop-types@^15.5.8, prop-types@^15.6.0, prop-types@^15.6.1, prop-types@^15.6.2, prop-types@^15.7.2: version "15.7.2" resolved "https://registry.npmjs.org/prop-types/-/prop-types-15.7.2.tgz#52c41e75b8c87e72b9d9360e0206b99dcbffa6c5" integrity sha512-8QQikdH7//R2vurIJSutZ1smHYTcLpRWEOlHnzcWHmBYrOGUysKwSsrC89BCiFj3CbrfJ/nXFdJepOVrY1GCHQ== @@ -21706,7 +21720,7 @@ react-color@^2.17.0: reactcss "^1.2.0" tinycolor2 "^1.4.1" -react-colorful@^5.0.1: +react-colorful@^5.0.1, react-colorful@^5.1.2: version "5.2.2" resolved "https://registry.npmjs.org/react-colorful/-/react-colorful-5.2.2.tgz#0a69d0648db47e51359d343854d83d250a742243" integrity sha512-Xdb1Rl6lZ5SMdNBH59eE0lGqR1g2LVD8IgPlw0WeMDrOC65lYI8fgMEwj/0dDpVRVMh5qp73ciISDst/t2O2iQ== @@ -21930,14 +21944,14 @@ react-inspector@^2.3.0: is-dom "^1.0.9" prop-types "^15.6.1" -react-inspector@^5.0.1: - version "5.0.1" - resolved "https://registry.npmjs.org/react-inspector/-/react-inspector-5.0.1.tgz#8a30f3d488c4f40203624bbe24800f508ae05d3a" - integrity sha512-qRIENuAIcRaytrmg/TL5nN5igYZMzyQqIKlWA8zoYRDltULsZC1bWy2Ua5wYJuwEYnC3gK4FCjcIQnb+5OyLsQ== +react-inspector@^5.1.0: + version "5.1.1" + resolved "https://registry.npmjs.org/react-inspector/-/react-inspector-5.1.1.tgz#58476c78fde05d5055646ed8ec02030af42953c8" + integrity sha512-GURDaYzoLbW8pMGXwYPDBIv6nqei4kK7LPRZ9q9HCZF54wqXz/dnylBp/kfE9XmekBhHvLDdcYeyIwSrvtOiWg== dependencies: - "@babel/runtime" "^7.8.7" - is-dom "^1.1.0" - prop-types "^15.6.1" + "@babel/runtime" "^7.0.0" + is-dom "^1.0.0" + prop-types "^15.0.0" react-is@^16.7.0, react-is@^16.8.0, react-is@^16.8.1, react-is@^16.8.4, react-is@^16.8.6, react-is@^16.9.0: version "16.13.1" @@ -24914,7 +24928,7 @@ telejson@^5.0.2: lodash "^4.17.19" memoizerific "^1.11.3" -telejson@^5.1.0: +telejson@^5.1.0, telejson@^5.3.2: version "5.3.3" resolved "https://registry.npmjs.org/telejson/-/telejson-5.3.3.tgz#fa8ca84543e336576d8734123876a9f02bf41d2e" integrity sha512-PjqkJZpzEggA9TBpVtJi1LVptP7tYtXB6rEubwlHap76AMjzvOdKX41CxyaW7ahhzDU1aftXnMCx5kAPDZTQBA== @@ -26097,6 +26111,11 @@ utils-merge@1.0.1, utils-merge@1.x.x: resolved "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz#9f95710f50a267947b2ccc124741c1028427e713" integrity sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM= +uuid-browser@^3.1.0: + version "3.1.0" + resolved "https://registry.npmjs.org/uuid-browser/-/uuid-browser-3.1.0.tgz#0f05a40aef74f9e5951e20efbf44b11871e56410" + integrity sha1-DwWkCu90+eWVHiDvv0SxGHHlZBA= + uuid@3.3.2: version "3.3.2" resolved "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz#1b4af4955eb3077c501c23872fc6513811587131" From 0ec31e5965f6521acece1889fe4fcec4edbf69a0 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 22 Jun 2021 04:16:04 +0000 Subject: [PATCH 29/89] chore(deps): bump @rollup/plugin-node-resolve from 11.2.1 to 13.0.0 Bumps [@rollup/plugin-node-resolve](https://github.com/rollup/plugins/tree/HEAD/packages/node-resolve) from 11.2.1 to 13.0.0. - [Release notes](https://github.com/rollup/plugins/releases) - [Changelog](https://github.com/rollup/plugins/blob/master/packages/node-resolve/CHANGELOG.md) - [Commits](https://github.com/rollup/plugins/commits/commonjs-v13.0.0/packages/node-resolve) --- updated-dependencies: - dependency-name: "@rollup/plugin-node-resolve" dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .changeset/shaggy-forks-pump.md | 5 +++++ packages/cli/package.json | 2 +- yarn.lock | 8 ++++---- 3 files changed, 10 insertions(+), 5 deletions(-) create mode 100644 .changeset/shaggy-forks-pump.md diff --git a/.changeset/shaggy-forks-pump.md b/.changeset/shaggy-forks-pump.md new file mode 100644 index 0000000000..dd3b0740dd --- /dev/null +++ b/.changeset/shaggy-forks-pump.md @@ -0,0 +1,5 @@ +--- +'@backstage/cli': patch +--- + +chore: bump `@rollup/plugin-node-resolve` from 11.2.1 to 13.0.0 diff --git a/packages/cli/package.json b/packages/cli/package.json index 6198da69d4..8d687cee49 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -39,7 +39,7 @@ "@octokit/request": "^5.4.12", "@rollup/plugin-commonjs": "^17.1.0", "@rollup/plugin-json": "^4.0.2", - "@rollup/plugin-node-resolve": "^11.2.0", + "@rollup/plugin-node-resolve": "^13.0.0", "@rollup/plugin-yaml": "^2.1.1", "@spotify/eslint-config-base": "^9.0.0", "@spotify/eslint-config-react": "^10.0.0", diff --git a/yarn.lock b/yarn.lock index 15ddd7de4a..ecf8fba2fb 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4124,10 +4124,10 @@ dependencies: "@rollup/pluginutils" "^3.0.8" -"@rollup/plugin-node-resolve@^11.2.0": - version "11.2.1" - resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-11.2.1.tgz#82aa59397a29cd4e13248b106e6a4a1880362a60" - integrity sha512-yc2n43jcqVyGE2sqV5/YCmocy9ArjVAP/BeXyTtADTBBX6V0e5UMqwO8CdQ0kzjb6zu5P1qMzsScCMRvE9OlVg== +"@rollup/plugin-node-resolve@^13.0.0": + version "13.0.0" + resolved "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-13.0.0.tgz#352f07e430ff377809ec8ec8a6fd636547162dc4" + integrity sha512-41X411HJ3oikIDivT5OKe9EZ6ud6DXudtfNrGbC4nniaxx2esiWjkLOzgnZsWq1IM8YIeL2rzRGLZLBjlhnZtQ== dependencies: "@rollup/pluginutils" "^3.1.0" "@types/resolve" "1.17.1" From 4fac7f045d6a07d88017db65284a33f8e2e34197 Mon Sep 17 00:00:00 2001 From: Diego Marangoni Date: Thu, 24 Jun 2021 09:21:21 +0200 Subject: [PATCH 30/89] Add HelloFresh to list of adopters Signed-off-by: Diego Marangoni --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index 9f59835cbc..723906ba7f 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -33,3 +33,4 @@ | [MoonShiner](https://moonshiner.at) | [Fabian Hippmann](https://github.com/FabianHippmann) | Developer portal - helps us keep track of our customer projects, onboard new developers & improve our development process 🌕🚀🧑‍🚀 | | [FundApps](https://www.fundapps.co/) | [Elliot Greenwood](https://github.com/egnwd) | Developer Portal - A place for us to keep track of our projects and documentation for all services and processes | | [DAZN](https://dazn.com/) | [Lou Bichard](https://twitter.com/loujaybee), [Marco Crivellaro](https://github.com/crivetechie) | Ingesting all of DAZN's repos for the catalog, migrating our internal platform apps (pull request boards, release information, inner source marketplace etc) to Backstage plugins (where applicable). | +| [HelloFresh](https://www.hellofresh.de/) | [@iammuho](https://github.com/iammuho), [@ElenaForester](https://github.com/ElenaForester), [@diegomarangoni](https://github.com/diegomarangoni) | Our developer portal at HelloFresh - Spread across an organisation of 500+ engineers globally. | From 13da7be3c184e15bc1803cc96605dd9a73f9bb9e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 24 Jun 2021 11:22:26 +0200 Subject: [PATCH 31/89] proxy-backend: cleaner and more informative log messages Signed-off-by: Patrik Oldsberg --- .changeset/neat-laws-hide.md | 5 +++++ plugins/proxy-backend/src/service/router.ts | 2 ++ 2 files changed, 7 insertions(+) create mode 100644 .changeset/neat-laws-hide.md diff --git a/.changeset/neat-laws-hide.md b/.changeset/neat-laws-hide.md new file mode 100644 index 0000000000..0719de9430 --- /dev/null +++ b/.changeset/neat-laws-hide.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-proxy-backend': patch +--- + +Clean up proxy creation log messages and make them include the mount path. diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 66a3152865..c2123ae4b1 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -148,6 +148,8 @@ export function buildMiddleware( return fullConfig?.allowedMethods?.includes(req.method!) ?? true; }; + // Makes http-proxy-middleware logs look nicer and include the mount path + filter.toString = () => route; // Only forward the allowed HTTP headers to not forward unwanted secret headers const responseHeaderAllowList = new Set( From e7b5cf888f1b28a44d696ae0ceb0a0906bc53f2f Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 24 Jun 2021 11:32:57 +0200 Subject: [PATCH 32/89] remove unnecessary check for missing provider factory Signed-off-by: Himanshu Mishra --- plugins/auth-backend/src/service/router.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 1945e6cb2d..9841df399a 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -88,15 +88,12 @@ export async function createRouter({ const providersConfig = config.getConfig('auth.providers'); const configuredProviders = providersConfig.keys(); - for (const providerId of Object.keys(allProviderFactories)) { + for (const [providerId, providerFactory] of Object.entries( + allProviderFactories, + )) { if (configuredProviders.includes(providerId)) { logger.info(`Configuring provider, ${providerId}`); try { - const providerFactory = allProviderFactories[providerId]; - if (!providerFactory) { - throw Error(`No auth provider available for '${providerId}'`); - } - const provider = providerFactory({ providerId, globalConfig: { baseUrl: authUrl, appUrl }, From ab554e2ace8b116eff8708bc26fad5e21ec0f002 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 24 Jun 2021 11:36:10 +0200 Subject: [PATCH 33/89] changesets: update config to not bump on in-range peer dep bumps Signed-off-by: Patrik Oldsberg --- .changeset/config.json | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.changeset/config.json b/.changeset/config.json index 86963b7d09..067dcb5f9f 100644 --- a/.changeset/config.json +++ b/.changeset/config.json @@ -6,5 +6,8 @@ "access": "public", "baseBranch": "master", "updateInternalDependencies": "patch", - "ignore": [] + "ignore": [], + "___experimentalUnsafeOptions_WILL_CHANGE_IN_PATCH": { + "onlyUpdatePeerDependentsWhenOutOfRange": true + } } From 686a740eb935ef0ee5e0f6ad0364fc49619d01e0 Mon Sep 17 00:00:00 2001 From: Himanshu Mishra Date: Thu, 24 Jun 2021 11:37:49 +0200 Subject: [PATCH 34/89] show better 404 message for unknown provider endpoints Signed-off-by: Himanshu Mishra --- plugins/auth-backend/src/service/router.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/plugins/auth-backend/src/service/router.ts b/plugins/auth-backend/src/service/router.ts index 9841df399a..e988fde97c 100644 --- a/plugins/auth-backend/src/service/router.ts +++ b/plugins/auth-backend/src/service/router.ts @@ -144,6 +144,11 @@ export async function createRouter({ } } + router.use('/:provider/', req => { + const { provider } = req.params; + throw new NotFoundError(`Unknown auth provider '${provider}'`); + }); + router.use( createOidcRouter({ tokenIssuer, From 5a64fe7ea5b71227a8448f1153fee2a8e9ca59c1 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Thu, 24 Jun 2021 09:58:38 +0000 Subject: [PATCH 35/89] Version Packages --- .changeset/beige-papayas-relax.md | 5 -- .changeset/blue-planes-fail.md | 5 -- .changeset/breezy-books-matter.md | 5 -- .changeset/cold-rockets-mix.md | 5 -- .changeset/cuddly-donuts-whisper.md | 54 -------------- .changeset/fair-knives-relax.md | 5 -- .changeset/few-windows-whisper.md | 8 --- .changeset/five-baboons-explain.md | 5 -- .changeset/forty-dodos-own.md | 5 -- .changeset/friendly-bikes-double.md | 5 -- .changeset/good-jars-turn.md | 5 -- .changeset/great-ads-yawn.md | 5 -- .changeset/heavy-numbers-refuse.md | 10 --- .changeset/many-mugs-bow.md | 5 -- .changeset/metal-badgers-carry.md | 25 ------- .changeset/metal-cycles-run.md | 5 -- .changeset/neat-laws-hide.md | 5 -- .changeset/odd-humans-exercise.md | 5 -- .changeset/perfect-gifts-compete.md | 5 -- .changeset/plenty-kings-shout.md | 5 -- .changeset/popular-rice-wonder.md | 8 --- .changeset/red-apricots-perform.md | 5 -- .changeset/rich-trees-chew.md | 5 -- .changeset/search-tasty-crews-tie.md | 5 -- .changeset/seven-adults-act.md | 25 ------- .changeset/shaggy-forks-pump.md | 5 -- .changeset/short-numbers-carry.md | 5 -- .changeset/silent-ways-laugh.md | 6 -- .changeset/smart-insects-care.md | 5 -- .changeset/sour-donuts-tickle.md | 5 -- .changeset/stupid-scissors-brake.md | 51 ------------- .changeset/techdocs-cool-rivers-suffer.md | 5 -- .changeset/techdocs-poor-forks-repeat.md | 5 -- .changeset/tender-trees-notice.md | 5 -- .changeset/three-tables-smash.md | 5 -- .changeset/violet-experts-design.md | 5 -- .changeset/weak-needles-peel.md | 5 -- .changeset/wet-suits-attack.md | 6 -- packages/app/CHANGELOG.md | 40 +++++++++++ packages/app/package.json | 72 +++++++++---------- packages/catalog-client/CHANGELOG.md | 8 +++ packages/catalog-client/package.json | 6 +- packages/catalog-model/CHANGELOG.md | 6 ++ packages/catalog-model/package.json | 4 +- packages/cli/CHANGELOG.md | 13 ++++ packages/cli/package.json | 10 +-- packages/codemods/CHANGELOG.md | 8 +++ packages/codemods/package.json | 2 +- packages/core-api/CHANGELOG.md | 8 +++ packages/core-api/package.json | 8 +-- packages/core-app-api/CHANGELOG.md | 9 +++ packages/core-app-api/package.json | 8 +-- packages/core-plugin-api/CHANGELOG.md | 6 ++ packages/core-plugin-api/package.json | 8 +-- packages/core/CHANGELOG.md | 8 +++ packages/core/package.json | 8 +-- packages/create-app/CHANGELOG.md | 6 ++ packages/create-app/package.json | 2 +- packages/dev-utils/CHANGELOG.md | 17 +++++ packages/dev-utils/package.json | 16 ++--- packages/integration-react/CHANGELOG.md | 8 +++ packages/integration-react/package.json | 10 +-- packages/techdocs-common/CHANGELOG.md | 8 +++ packages/techdocs-common/package.json | 6 +- packages/test-utils/CHANGELOG.md | 9 +++ packages/test-utils/package.json | 8 +-- plugins/api-docs/CHANGELOG.md | 16 +++++ plugins/api-docs/package.json | 18 ++--- plugins/auth-backend/CHANGELOG.md | 32 +++++++++ plugins/auth-backend/package.json | 10 +-- plugins/badges/CHANGELOG.md | 10 +++ plugins/badges/package.json | 16 ++--- plugins/bitrise/CHANGELOG.md | 10 +++ plugins/bitrise/package.json | 16 ++--- .../CHANGELOG.md | 32 +++++++++ .../package.json | 10 +-- plugins/catalog-backend/CHANGELOG.md | 31 ++++++++ plugins/catalog-backend/package.json | 12 ++-- plugins/catalog-import/CHANGELOG.md | 12 ++++ plugins/catalog-import/package.json | 20 +++--- plugins/catalog-react/CHANGELOG.md | 11 +++ plugins/catalog-react/package.json | 16 ++--- plugins/catalog/CHANGELOG.md | 19 +++++ plugins/catalog/package.json | 20 +++--- plugins/circleci/CHANGELOG.md | 10 +++ plugins/circleci/package.json | 16 ++--- plugins/cloudbuild/CHANGELOG.md | 10 +++ plugins/cloudbuild/package.json | 16 ++--- plugins/code-coverage/CHANGELOG.md | 10 +++ plugins/code-coverage/package.json | 16 ++--- plugins/config-schema/CHANGELOG.md | 8 +++ plugins/config-schema/package.json | 12 ++-- plugins/cost-insights/CHANGELOG.md | 12 ++++ plugins/cost-insights/package.json | 12 ++-- plugins/explore-react/CHANGELOG.md | 8 +++ plugins/explore-react/package.json | 10 +-- plugins/explore/CHANGELOG.md | 62 ++++++++++++++++ plugins/explore/package.json | 18 ++--- plugins/fossa/CHANGELOG.md | 10 +++ plugins/fossa/package.json | 16 ++--- plugins/gcp-projects/CHANGELOG.md | 12 ++++ plugins/gcp-projects/package.json | 12 ++-- plugins/git-release-manager/CHANGELOG.md | 8 +++ plugins/git-release-manager/package.json | 12 ++-- plugins/github-actions/CHANGELOG.md | 10 +++ plugins/github-actions/package.json | 16 ++--- plugins/github-deployments/CHANGELOG.md | 11 +++ plugins/github-deployments/package.json | 18 ++--- plugins/gitops-profiles/CHANGELOG.md | 12 ++++ plugins/gitops-profiles/package.json | 12 ++-- plugins/graphiql/CHANGELOG.md | 8 +++ plugins/graphiql/package.json | 12 ++-- plugins/ilert/CHANGELOG.md | 10 +++ plugins/ilert/package.json | 16 ++--- plugins/jenkins/CHANGELOG.md | 10 +++ plugins/jenkins/package.json | 16 ++--- plugins/kafka/CHANGELOG.md | 10 +++ plugins/kafka/package.json | 16 ++--- plugins/kubernetes/CHANGELOG.md | 10 +++ plugins/kubernetes/package.json | 16 ++--- plugins/lighthouse/CHANGELOG.md | 10 +++ plugins/lighthouse/package.json | 16 ++--- plugins/newrelic/CHANGELOG.md | 12 ++++ plugins/newrelic/package.json | 12 ++-- plugins/org/CHANGELOG.md | 11 +++ plugins/org/package.json | 16 ++--- plugins/pagerduty/CHANGELOG.md | 10 +++ plugins/pagerduty/package.json | 16 ++--- plugins/proxy-backend/CHANGELOG.md | 6 ++ plugins/proxy-backend/package.json | 4 +- plugins/register-component/CHANGELOG.md | 10 +++ plugins/register-component/package.json | 16 ++--- plugins/rollbar/CHANGELOG.md | 10 +++ plugins/rollbar/package.json | 16 ++--- plugins/scaffolder-backend/CHANGELOG.md | 11 +++ plugins/scaffolder-backend/package.json | 10 +-- plugins/scaffolder/CHANGELOG.md | 14 ++++ plugins/scaffolder/package.json | 20 +++--- plugins/search-backend-node/CHANGELOG.md | 7 ++ plugins/search-backend-node/package.json | 4 +- plugins/search/CHANGELOG.md | 12 ++++ plugins/search/package.json | 16 ++--- plugins/sentry/CHANGELOG.md | 10 +++ plugins/sentry/package.json | 16 ++--- plugins/shortcuts/CHANGELOG.md | 8 +++ plugins/shortcuts/package.json | 12 ++-- plugins/sonarqube/CHANGELOG.md | 10 +++ plugins/sonarqube/package.json | 16 ++--- plugins/splunk-on-call/CHANGELOG.md | 10 +++ plugins/splunk-on-call/package.json | 16 ++--- plugins/tech-radar/CHANGELOG.md | 8 +++ plugins/tech-radar/package.json | 12 ++-- plugins/techdocs-backend/CHANGELOG.md | 9 +++ plugins/techdocs-backend/package.json | 8 +-- plugins/techdocs/CHANGELOG.md | 13 ++++ plugins/techdocs/package.json | 18 ++--- plugins/todo/CHANGELOG.md | 10 +++ plugins/todo/package.json | 16 ++--- plugins/user-settings/CHANGELOG.md | 8 +++ plugins/user-settings/package.json | 14 ++-- plugins/welcome/CHANGELOG.md | 12 ++++ plugins/welcome/package.json | 12 ++-- 162 files changed, 1196 insertions(+), 765 deletions(-) delete mode 100644 .changeset/beige-papayas-relax.md delete mode 100644 .changeset/blue-planes-fail.md delete mode 100644 .changeset/breezy-books-matter.md delete mode 100644 .changeset/cold-rockets-mix.md delete mode 100644 .changeset/cuddly-donuts-whisper.md delete mode 100644 .changeset/fair-knives-relax.md delete mode 100644 .changeset/few-windows-whisper.md delete mode 100644 .changeset/five-baboons-explain.md delete mode 100644 .changeset/forty-dodos-own.md delete mode 100644 .changeset/friendly-bikes-double.md delete mode 100644 .changeset/good-jars-turn.md delete mode 100644 .changeset/great-ads-yawn.md delete mode 100644 .changeset/heavy-numbers-refuse.md delete mode 100644 .changeset/many-mugs-bow.md delete mode 100644 .changeset/metal-badgers-carry.md delete mode 100644 .changeset/metal-cycles-run.md delete mode 100644 .changeset/neat-laws-hide.md delete mode 100644 .changeset/odd-humans-exercise.md delete mode 100644 .changeset/perfect-gifts-compete.md delete mode 100644 .changeset/plenty-kings-shout.md delete mode 100644 .changeset/popular-rice-wonder.md delete mode 100644 .changeset/red-apricots-perform.md delete mode 100644 .changeset/rich-trees-chew.md delete mode 100644 .changeset/search-tasty-crews-tie.md delete mode 100644 .changeset/seven-adults-act.md delete mode 100644 .changeset/shaggy-forks-pump.md delete mode 100644 .changeset/short-numbers-carry.md delete mode 100644 .changeset/silent-ways-laugh.md delete mode 100644 .changeset/smart-insects-care.md delete mode 100644 .changeset/sour-donuts-tickle.md delete mode 100644 .changeset/stupid-scissors-brake.md delete mode 100644 .changeset/techdocs-cool-rivers-suffer.md delete mode 100644 .changeset/techdocs-poor-forks-repeat.md delete mode 100644 .changeset/tender-trees-notice.md delete mode 100644 .changeset/three-tables-smash.md delete mode 100644 .changeset/violet-experts-design.md delete mode 100644 .changeset/weak-needles-peel.md delete mode 100644 .changeset/wet-suits-attack.md create mode 100644 plugins/catalog-backend-module-msgraph/CHANGELOG.md diff --git a/.changeset/beige-papayas-relax.md b/.changeset/beige-papayas-relax.md deleted file mode 100644 index 9fb6fc1c12..0000000000 --- a/.changeset/beige-papayas-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -created an action to write a catalog-info file diff --git a/.changeset/blue-planes-fail.md b/.changeset/blue-planes-fail.md deleted file mode 100644 index ba8eca812a..0000000000 --- a/.changeset/blue-planes-fail.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -improve the wrapping behavior of long entity links diff --git a/.changeset/breezy-books-matter.md b/.changeset/breezy-books-matter.md deleted file mode 100644 index 1bc206bdc5..0000000000 --- a/.changeset/breezy-books-matter.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Fix empty state not being displayed on missing results. diff --git a/.changeset/cold-rockets-mix.md b/.changeset/cold-rockets-mix.md deleted file mode 100644 index f38f5f61e9..0000000000 --- a/.changeset/cold-rockets-mix.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/techdocs-common': patch ---- - -Adding additional checks on tech docs to prevent folder traversal via mkdocs.yml docs_dir value. diff --git a/.changeset/cuddly-donuts-whisper.md b/.changeset/cuddly-donuts-whisper.md deleted file mode 100644 index fd1b665b99..0000000000 --- a/.changeset/cuddly-donuts-whisper.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -'@backstage/plugin-explore': patch ---- - -Refactors the explore plugin to be more customizable. This includes the following non-breaking changes: - -- Introduce new `ExploreLayout` page which can be used to create a custom `ExplorePage` -- Refactor `ExplorePage` to use a new `ExploreLayout` component -- Exports existing `DomainExplorerContent`, `GroupsExplorerContent`, & `ToolExplorerContent` components -- Allows `title` props to be customized - -Create a custom explore page in `packages/app/src/components/explore/ExplorePage.tsx`. - -```tsx -import { - DomainExplorerContent, - ExploreLayout, -} from '@backstage/plugin-explore'; -import React from 'react'; -import { InnserSourceExplorerContent } from './InnserSourceExplorerContent'; - -export const ExplorePage = () => { - return ( - - - - - - - - - ); -}; - -export const explorePage = ; -``` - -Now register the new explore page in `packages/app/src/App.tsx`. - -```diff -+ import { explorePage } from './components/explore/ExplorePage'; - -const routes = ( - -- } /> -+ }> -+ {explorePage} -+ - -); -``` diff --git a/.changeset/fair-knives-relax.md b/.changeset/fair-knives-relax.md deleted file mode 100644 index bc1357d841..0000000000 --- a/.changeset/fair-knives-relax.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/core-app-api': patch ---- - -Introducing new UnhandledErrorForwarder installed by default. For catching unhandled promise rejections, you can override the API to align with general error handling. diff --git a/.changeset/few-windows-whisper.md b/.changeset/few-windows-whisper.md deleted file mode 100644 index 565e8798a1..0000000000 --- a/.changeset/few-windows-whisper.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/core-app-api': patch -'@backstage/core-plugin-api': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-scaffolder': patch ---- - -Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API diff --git a/.changeset/five-baboons-explain.md b/.changeset/five-baboons-explain.md deleted file mode 100644 index b30c1a6237..0000000000 --- a/.changeset/five-baboons-explain.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/dev-utils': minor ---- - -Removed support for deprecated registered plugin routes. All routes now need to be added using `addPage` instead. diff --git a/.changeset/forty-dodos-own.md b/.changeset/forty-dodos-own.md deleted file mode 100644 index 6b60872f9d..0000000000 --- a/.changeset/forty-dodos-own.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/catalog-client': patch ---- - -Return entities sorted alphabetically by ref diff --git a/.changeset/friendly-bikes-double.md b/.changeset/friendly-bikes-double.md deleted file mode 100644 index 5ee9f959c1..0000000000 --- a/.changeset/friendly-bikes-double.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -updated plugin template to generate path equals plugin id for the root page diff --git a/.changeset/good-jars-turn.md b/.changeset/good-jars-turn.md deleted file mode 100644 index 0bbe344ad2..0000000000 --- a/.changeset/good-jars-turn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search': patch ---- - -Use the `identityApi` to forward authorization headers to the `search-backend` diff --git a/.changeset/great-ads-yawn.md b/.changeset/great-ads-yawn.md deleted file mode 100644 index 3271dde108..0000000000 --- a/.changeset/great-ads-yawn.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Allow `defaultKind` from `CatalogTable.column.creatNameColumn` to be configurable diff --git a/.changeset/heavy-numbers-refuse.md b/.changeset/heavy-numbers-refuse.md deleted file mode 100644 index c5d9dc6deb..0000000000 --- a/.changeset/heavy-numbers-refuse.md +++ /dev/null @@ -1,10 +0,0 @@ ---- -'@backstage/plugin-api-docs': minor -'@backstage/plugin-cost-insights': minor -'@backstage/plugin-gcp-projects': minor -'@backstage/plugin-gitops-profiles': minor -'@backstage/plugin-newrelic': minor -'@backstage/plugin-welcome': minor ---- - -**BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. diff --git a/.changeset/many-mugs-bow.md b/.changeset/many-mugs-bow.md deleted file mode 100644 index 39614b044a..0000000000 --- a/.changeset/many-mugs-bow.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `msw` dependency in `create-plugin` diff --git a/.changeset/metal-badgers-carry.md b/.changeset/metal-badgers-carry.md deleted file mode 100644 index b82e638c6f..0000000000 --- a/.changeset/metal-badgers-carry.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Move `MicrosoftGraphOrgReaderProcessor` from `@backstage/plugin-catalog-backend` -to `@backstage/plugin-catalog-backend-module-msgraph`. - -The `MicrosoftGraphOrgReaderProcessor` isn't registered by default anymore, if -you want to continue using it you have to register it manually at the catalog -builder: - -1. Add dependency to `@backstage/plugin-catalog-backend-module-msgraph` to the `package.json` of your backend. -2. Add the processor to the catalog builder: - -```typescript -// packages/backend/src/plugins/catalog.ts -builder.addProcessor( - MicrosoftGraphOrgReaderProcessor.fromConfig(config, { - logger, - }), -); -``` - -For more configuration details, see the [README of the `@backstage/plugin-catalog-backend-module-msgraph` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md). diff --git a/.changeset/metal-cycles-run.md b/.changeset/metal-cycles-run.md deleted file mode 100644 index b4a2597aa2..0000000000 --- a/.changeset/metal-cycles-run.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Fix the overlapping between the sidebar and the tabs navigation when enabled in mkdocs (features: navigation.tabs) diff --git a/.changeset/neat-laws-hide.md b/.changeset/neat-laws-hide.md deleted file mode 100644 index 0719de9430..0000000000 --- a/.changeset/neat-laws-hide.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-proxy-backend': patch ---- - -Clean up proxy creation log messages and make them include the mount path. diff --git a/.changeset/odd-humans-exercise.md b/.changeset/odd-humans-exercise.md deleted file mode 100644 index 136ec86b69..0000000000 --- a/.changeset/odd-humans-exercise.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -Make the `create-github-app` command disable webhooks by default. diff --git a/.changeset/perfect-gifts-compete.md b/.changeset/perfect-gifts-compete.md deleted file mode 100644 index 7c03798888..0000000000 --- a/.changeset/perfect-gifts-compete.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Don't export the `defaultGoogleAuthProvider` diff --git a/.changeset/plenty-kings-shout.md b/.changeset/plenty-kings-shout.md deleted file mode 100644 index 16453b96de..0000000000 --- a/.changeset/plenty-kings-shout.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `@typescript-eslint/eslint-plugin` from 4.26.0 to 4.27.0 diff --git a/.changeset/popular-rice-wonder.md b/.changeset/popular-rice-wonder.md deleted file mode 100644 index f815ad949a..0000000000 --- a/.changeset/popular-rice-wonder.md +++ /dev/null @@ -1,8 +0,0 @@ ---- -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-backend': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-scaffolder-backend': patch ---- - -Moved installation instructions from the main [backstage.io](https://backstage.io) documentation to the package README file. These instructions are not generally needed, since the plugin comes installed by default with `npx @backstage/create-app`. diff --git a/.changeset/red-apricots-perform.md b/.changeset/red-apricots-perform.md deleted file mode 100644 index 3d27381e6f..0000000000 --- a/.changeset/red-apricots-perform.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `del` from 5.1.0 to 6.0.0 diff --git a/.changeset/rich-trees-chew.md b/.changeset/rich-trees-chew.md deleted file mode 100644 index 5ec79c36d0..0000000000 --- a/.changeset/rich-trees-chew.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `@spotify/eslint-config-typescript` from 9.0.0 to 10.0.0 diff --git a/.changeset/search-tasty-crews-tie.md b/.changeset/search-tasty-crews-tie.md deleted file mode 100644 index 3d5b1ad1b0..0000000000 --- a/.changeset/search-tasty-crews-tie.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch ---- - -Handle errors in collators and decorators and log them. diff --git a/.changeset/seven-adults-act.md b/.changeset/seven-adults-act.md deleted file mode 100644 index fa2839aad2..0000000000 --- a/.changeset/seven-adults-act.md +++ /dev/null @@ -1,25 +0,0 @@ ---- -'@backstage/plugin-auth-backend': patch ---- - -Adds support for custom sign-in resolvers and profile transformations for the -Google auth provider. - -Adds an `ent` claim in Backstage tokens, with a list of -[entity references](https://backstage.io/docs/features/software-catalog/references) -related to your signed-in user's identities and groups across multiple systems. - -Adds an optional `providerFactories` argument to the `createRouter` exported by -the `auth-backend` plugin. - -Updates `BackstageIdentity` so that - -- `idToken` is deprecated in favor of `token` -- An optional `entity` field is added which represents the entity that the user is represented by within Backstage. - -More information: - -- [The identity resolver documentation](https://backstage.io/docs/auth/identity-resolver) - explains the concepts and shows how to implement your own. -- The [From Identity to Ownership](https://github.com/backstage/backstage/issues/4089) - RFC contains details about how this affects ownership in the catalog diff --git a/.changeset/shaggy-forks-pump.md b/.changeset/shaggy-forks-pump.md deleted file mode 100644 index dd3b0740dd..0000000000 --- a/.changeset/shaggy-forks-pump.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/cli': patch ---- - -chore: bump `@rollup/plugin-node-resolve` from 11.2.1 to 13.0.0 diff --git a/.changeset/short-numbers-carry.md b/.changeset/short-numbers-carry.md deleted file mode 100644 index 0b2fbe80ae..0000000000 --- a/.changeset/short-numbers-carry.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-search-backend-node': patch ---- - -Fixed bug preventing searches with filter values containing `:` from returning results. diff --git a/.changeset/silent-ways-laugh.md b/.changeset/silent-ways-laugh.md deleted file mode 100644 index ea152c68ef..0000000000 --- a/.changeset/silent-ways-laugh.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/plugin-catalog-backend-module-msgraph': patch ---- - -Allow customizations of `MicrosoftGraphOrgReaderProcessor` by passing an -optional `groupTransformer`, `userTransformer`, and `organizationTransformer`. diff --git a/.changeset/smart-insects-care.md b/.changeset/smart-insects-care.md deleted file mode 100644 index 2d1fc8f2aa..0000000000 --- a/.changeset/smart-insects-care.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Disambiguated titles of `EntityDependencyOfComponentsCard` and `EntityDependsOnComponentsCard`. diff --git a/.changeset/sour-donuts-tickle.md b/.changeset/sour-donuts-tickle.md deleted file mode 100644 index 5cc3323662..0000000000 --- a/.changeset/sour-donuts-tickle.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Adds an optional `actions` prop to `CatalogTable` and `CatalogPage` to support supplying custom actions for each entity row in the table. This uses the default actions if not provided. diff --git a/.changeset/stupid-scissors-brake.md b/.changeset/stupid-scissors-brake.md deleted file mode 100644 index 25c948a632..0000000000 --- a/.changeset/stupid-scissors-brake.md +++ /dev/null @@ -1,51 +0,0 @@ ---- -'@backstage/catalog-model': patch -'@backstage/cli': patch -'@backstage/create-app': patch -'@backstage/dev-utils': patch -'@backstage/integration-react': patch -'@backstage/test-utils': patch -'@backstage/plugin-api-docs': patch -'@backstage/plugin-badges': patch -'@backstage/plugin-bitrise': patch -'@backstage/plugin-catalog': patch -'@backstage/plugin-catalog-import': patch -'@backstage/plugin-catalog-react': patch -'@backstage/plugin-circleci': patch -'@backstage/plugin-cloudbuild': patch -'@backstage/plugin-code-coverage': patch -'@backstage/plugin-config-schema': patch -'@backstage/plugin-cost-insights': patch -'@backstage/plugin-explore': patch -'@backstage/plugin-explore-react': patch -'@backstage/plugin-fossa': patch -'@backstage/plugin-gcp-projects': patch -'@backstage/plugin-git-release-manager': patch -'@backstage/plugin-github-actions': patch -'@backstage/plugin-github-deployments': patch -'@backstage/plugin-gitops-profiles': patch -'@backstage/plugin-graphiql': patch -'@backstage/plugin-ilert': patch -'@backstage/plugin-jenkins': patch -'@backstage/plugin-kafka': patch -'@backstage/plugin-kubernetes': patch -'@backstage/plugin-lighthouse': patch -'@backstage/plugin-newrelic': patch -'@backstage/plugin-org': patch -'@backstage/plugin-pagerduty': patch -'@backstage/plugin-register-component': patch -'@backstage/plugin-rollbar': patch -'@backstage/plugin-scaffolder': patch -'@backstage/plugin-search': patch -'@backstage/plugin-sentry': patch -'@backstage/plugin-shortcuts': patch -'@backstage/plugin-sonarqube': patch -'@backstage/plugin-splunk-on-call': patch -'@backstage/plugin-tech-radar': patch -'@backstage/plugin-techdocs': patch -'@backstage/plugin-todo': patch -'@backstage/plugin-user-settings': patch -'@backstage/plugin-welcome': patch ---- - -Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. diff --git a/.changeset/techdocs-cool-rivers-suffer.md b/.changeset/techdocs-cool-rivers-suffer.md deleted file mode 100644 index 2386d27525..0000000000 --- a/.changeset/techdocs-cool-rivers-suffer.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs': patch ---- - -Refactor the implicit logic from `` into an explicit state machine. This resolves some state synchronization issues when content is refreshed or rebuilt in the backend. diff --git a/.changeset/techdocs-poor-forks-repeat.md b/.changeset/techdocs-poor-forks-repeat.md deleted file mode 100644 index 89c4e0dc5a..0000000000 --- a/.changeset/techdocs-poor-forks-repeat.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-techdocs-backend': patch ---- - -Return a `304 Not Modified` from the `/sync/:namespace/:kind/:name` endpoint if nothing was built. This enables the caller to know whether a refresh of the docs page will return updated content (-> `201 Created`) or not (-> `304 Not Modified`). diff --git a/.changeset/tender-trees-notice.md b/.changeset/tender-trees-notice.md deleted file mode 100644 index cf8c395a2d..0000000000 --- a/.changeset/tender-trees-notice.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-api-docs': patch ---- - -Move `EntityTypePicker` to be consistent with `CatalogPage` and remove `api:` prefix from entity names diff --git a/.changeset/three-tables-smash.md b/.changeset/three-tables-smash.md deleted file mode 100644 index 647647b666..0000000000 --- a/.changeset/three-tables-smash.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-catalog': patch ---- - -Clearer titles for the relationship cards diff --git a/.changeset/violet-experts-design.md b/.changeset/violet-experts-design.md deleted file mode 100644 index 1db44aaf4b..0000000000 --- a/.changeset/violet-experts-design.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-org': patch ---- - -Display a tooltip for ownership cards listing the related entities diff --git a/.changeset/weak-needles-peel.md b/.changeset/weak-needles-peel.md deleted file mode 100644 index 5315e2cb66..0000000000 --- a/.changeset/weak-needles-peel.md +++ /dev/null @@ -1,5 +0,0 @@ ---- -'@backstage/plugin-scaffolder-backend': patch ---- - -Correctly recognize whether the cookiecutter command exists diff --git a/.changeset/wet-suits-attack.md b/.changeset/wet-suits-attack.md deleted file mode 100644 index 8506381dc5..0000000000 --- a/.changeset/wet-suits-attack.md +++ /dev/null @@ -1,6 +0,0 @@ ---- -'@backstage/core': patch -'@backstage/core-api': patch ---- - -Add deprecation warning to package README. diff --git a/packages/app/CHANGELOG.md b/packages/app/CHANGELOG.md index f6d69f050c..ca623d0ba1 100644 --- a/packages/app/CHANGELOG.md +++ b/packages/app/CHANGELOG.md @@ -1,5 +1,45 @@ # example-app +## 0.2.34 + +### Patch Changes + +- Updated dependencies + - @backstage/plugin-catalog@0.6.4 + - @backstage/plugin-search@0.4.1 + - @backstage/plugin-explore@0.3.7 + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + - @backstage/plugin-scaffolder@0.9.9 + - @backstage/cli@0.7.2 + - @backstage/plugin-api-docs@0.6.0 + - @backstage/plugin-cost-insights@0.11.0 + - @backstage/plugin-gcp-projects@0.3.0 + - @backstage/plugin-newrelic@0.3.0 + - @backstage/plugin-techdocs@0.9.7 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-badges@0.2.3 + - @backstage/plugin-catalog-import@0.5.11 + - @backstage/plugin-catalog-react@0.2.4 + - @backstage/plugin-circleci@0.2.17 + - @backstage/plugin-cloudbuild@0.2.17 + - @backstage/plugin-code-coverage@0.1.5 + - @backstage/plugin-github-actions@0.4.10 + - @backstage/plugin-graphiql@0.2.12 + - @backstage/plugin-jenkins@0.4.6 + - @backstage/plugin-kafka@0.2.9 + - @backstage/plugin-kubernetes@0.4.6 + - @backstage/plugin-lighthouse@0.2.18 + - @backstage/plugin-org@0.3.15 + - @backstage/plugin-pagerduty@0.3.6 + - @backstage/plugin-rollbar@0.3.7 + - @backstage/plugin-sentry@0.3.13 + - @backstage/plugin-shortcuts@0.1.4 + - @backstage/plugin-tech-radar@0.4.1 + - @backstage/plugin-todo@0.1.3 + - @backstage/plugin-user-settings@0.2.12 + ## 0.2.33 ### Patch Changes diff --git a/packages/app/package.json b/packages/app/package.json index 1a9f0c4add..d239615b64 100644 --- a/packages/app/package.json +++ b/packages/app/package.json @@ -1,44 +1,44 @@ { "name": "example-app", - "version": "0.2.33", + "version": "0.2.34", "private": true, "bundled": true, "dependencies": { - "@backstage/catalog-model": "^0.8.3", - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", + "@backstage/catalog-model": "^0.8.4", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-api-docs": "^0.5.0", - "@backstage/plugin-badges": "^0.2.2", - "@backstage/plugin-catalog": "^0.6.3", - "@backstage/plugin-catalog-import": "^0.5.10", - "@backstage/plugin-catalog-react": "^0.2.3", - "@backstage/plugin-circleci": "^0.2.16", - "@backstage/plugin-cloudbuild": "^0.2.16", - "@backstage/plugin-code-coverage": "^0.1.4", - "@backstage/plugin-cost-insights": "^0.10.2", - "@backstage/plugin-explore": "^0.3.6", - "@backstage/plugin-gcp-projects": "^0.2.6", - "@backstage/plugin-github-actions": "^0.4.9", - "@backstage/plugin-graphiql": "^0.2.11", - "@backstage/plugin-jenkins": "^0.4.5", - "@backstage/plugin-kafka": "^0.2.8", - "@backstage/plugin-kubernetes": "^0.4.5", - "@backstage/plugin-lighthouse": "^0.2.17", - "@backstage/plugin-newrelic": "^0.2.7", - "@backstage/plugin-org": "^0.3.14", - "@backstage/plugin-pagerduty": "0.3.5", - "@backstage/plugin-rollbar": "^0.3.6", - "@backstage/plugin-scaffolder": "^0.9.8", - "@backstage/plugin-search": "^0.4.0", - "@backstage/plugin-sentry": "^0.3.12", - "@backstage/plugin-shortcuts": "^0.1.2", - "@backstage/plugin-tech-radar": "^0.4.0", - "@backstage/plugin-techdocs": "^0.9.6", - "@backstage/plugin-todo": "^0.1.2", - "@backstage/plugin-user-settings": "^0.2.11", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-api-docs": "^0.6.0", + "@backstage/plugin-badges": "^0.2.3", + "@backstage/plugin-catalog": "^0.6.4", + "@backstage/plugin-catalog-import": "^0.5.11", + "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-circleci": "^0.2.17", + "@backstage/plugin-cloudbuild": "^0.2.17", + "@backstage/plugin-code-coverage": "^0.1.5", + "@backstage/plugin-cost-insights": "^0.11.0", + "@backstage/plugin-explore": "^0.3.7", + "@backstage/plugin-gcp-projects": "^0.3.0", + "@backstage/plugin-github-actions": "^0.4.10", + "@backstage/plugin-graphiql": "^0.2.12", + "@backstage/plugin-jenkins": "^0.4.6", + "@backstage/plugin-kafka": "^0.2.9", + "@backstage/plugin-kubernetes": "^0.4.6", + "@backstage/plugin-lighthouse": "^0.2.18", + "@backstage/plugin-newrelic": "^0.3.0", + "@backstage/plugin-org": "^0.3.15", + "@backstage/plugin-pagerduty": "0.3.6", + "@backstage/plugin-rollbar": "^0.3.7", + "@backstage/plugin-scaffolder": "^0.9.9", + "@backstage/plugin-search": "^0.4.1", + "@backstage/plugin-sentry": "^0.3.13", + "@backstage/plugin-shortcuts": "^0.1.4", + "@backstage/plugin-tech-radar": "^0.4.1", + "@backstage/plugin-techdocs": "^0.9.7", + "@backstage/plugin-todo": "^0.1.3", + "@backstage/plugin-user-settings": "^0.2.12", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -58,7 +58,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/test-utils": "^0.1.13", + "@backstage/test-utils": "^0.1.14", "@testing-library/cypress": "^7.0.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/catalog-client/CHANGELOG.md b/packages/catalog-client/CHANGELOG.md index 847924b67b..830edbb600 100644 --- a/packages/catalog-client/CHANGELOG.md +++ b/packages/catalog-client/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/catalog-client +## 0.3.14 + +### Patch Changes + +- 45ef515d0: Return entities sorted alphabetically by ref +- Updated dependencies + - @backstage/catalog-model@0.8.4 + ## 0.3.13 ### Patch Changes diff --git a/packages/catalog-client/package.json b/packages/catalog-client/package.json index 655c88eff6..24ac72bb67 100644 --- a/packages/catalog-client/package.json +++ b/packages/catalog-client/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-client", - "version": "0.3.13", + "version": "0.3.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "cross-fetch": "^3.0.6" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.2", "@types/jest": "^26.0.7", "msw": "^0.29.0" }, diff --git a/packages/catalog-model/CHANGELOG.md b/packages/catalog-model/CHANGELOG.md index 3c4a2e073c..7a6cce0a6b 100644 --- a/packages/catalog-model/CHANGELOG.md +++ b/packages/catalog-model/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/catalog-model +## 0.8.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. + ## 0.8.3 ### Patch Changes diff --git a/packages/catalog-model/package.json b/packages/catalog-model/package.json index 3477d57e71..1ddf1b89ae 100644 --- a/packages/catalog-model/package.json +++ b/packages/catalog-model/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/catalog-model", - "version": "0.8.3", + "version": "0.8.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -40,7 +40,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.2", "@types/express": "^4.17.6", "@types/jest": "^26.0.7", "@types/lodash": "^4.14.151", diff --git a/packages/cli/CHANGELOG.md b/packages/cli/CHANGELOG.md index 32d028a32d..87b726600b 100644 --- a/packages/cli/CHANGELOG.md +++ b/packages/cli/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/cli +## 0.7.2 + +### Patch Changes + +- 953a7e66f: updated plugin template to generate path equals plugin id for the root page +- 04248b8f9: chore: bump `msw` dependency in `create-plugin` +- e3d31b381: Make the `create-github-app` command disable webhooks by default. +- 8f100db75: chore: bump `@typescript-eslint/eslint-plugin` from 4.26.0 to 4.27.0 +- 95e572305: chore: bump `del` from 5.1.0 to 6.0.0 +- ece2b5dd1: chore: bump `@spotify/eslint-config-typescript` from 9.0.0 to 10.0.0 +- 0ec31e596: chore: bump `@rollup/plugin-node-resolve` from 11.2.1 to 13.0.0 +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. + ## 0.7.1 ### Patch Changes diff --git a/packages/cli/package.json b/packages/cli/package.json index c092f8b9fc..b0e6b021cc 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/cli", "description": "CLI for developing Backstage plugins and apps", - "version": "0.7.1", + "version": "0.7.2", "private": false, "publishConfig": { "access": "public" @@ -121,10 +121,10 @@ "@backstage/backend-common": "^0.8.3", "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@backstage/theme": "^0.2.8", "@types/diff": "^5.0.0", "@types/express": "^4.17.6", diff --git a/packages/codemods/CHANGELOG.md b/packages/codemods/CHANGELOG.md index 524ffc18a1..3b617f4623 100644 --- a/packages/codemods/CHANGELOG.md +++ b/packages/codemods/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/codemods +## 0.1.3 + +### Patch Changes + +- Updated dependencies + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + ## 0.1.2 Fixed a publish issue, making this package available to the public. diff --git a/packages/codemods/package.json b/packages/codemods/package.json index f97d9cdde0..d9ea7fb1b7 100644 --- a/packages/codemods/package.json +++ b/packages/codemods/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/codemods", "description": "A collection of codemods for Backstage projects", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md index 2282976d74..016dff8a10 100644 --- a/packages/core-api/CHANGELOG.md +++ b/packages/core-api/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core-api +## 0.2.23 + +### Patch Changes + +- a1c30d7ea: Add deprecation warning to package README. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.22 ### Patch Changes diff --git a/packages/core-api/package.json b/packages/core-api/package.json index 14a3f11f69..c6a6b43401 100644 --- a/packages/core-api/package.json +++ b/packages/core-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-api", "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.22", + "version": "0.2.23", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.4", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,8 +43,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-app-api/CHANGELOG.md b/packages/core-app-api/CHANGELOG.md index 4d7b7541d0..c96fbb5aba 100644 --- a/packages/core-app-api/CHANGELOG.md +++ b/packages/core-app-api/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/core-app-api +## 0.1.3 + +### Patch Changes + +- dc3e7ce68: Introducing new UnhandledErrorForwarder installed by default. For catching unhandled promise rejections, you can override the API to align with general error handling. +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.2 ### Patch Changes diff --git a/packages/core-app-api/package.json b/packages/core-app-api/package.json index c9944922d9..42db08a136 100644 --- a/packages/core-app-api/package.json +++ b/packages/core-app-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-app-api", "description": "Core app API used by Backstage apps", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", @@ -31,7 +31,7 @@ "dependencies": { "@backstage/core-components": "^0.1.2", "@backstage/config": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,8 +44,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core-plugin-api/CHANGELOG.md b/packages/core-plugin-api/CHANGELOG.md index 8d45bb8d70..740f42708f 100644 --- a/packages/core-plugin-api/CHANGELOG.md +++ b/packages/core-plugin-api/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/core-plugin-api +## 0.1.3 + +### Patch Changes + +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API + ## 0.1.2 ### Patch Changes diff --git a/packages/core-plugin-api/package.json b/packages/core-plugin-api/package.json index 86088b22f7..7b2aa3deea 100644 --- a/packages/core-plugin-api/package.json +++ b/packages/core-plugin-api/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core-plugin-api", "description": "Core API used by Backstage plugins", - "version": "0.1.2", + "version": "0.1.3", "private": false, "publishConfig": { "access": "public", @@ -41,9 +41,9 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/core-app-api": "^0.1.2", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/test-utils": "^0.1.14", "@backstage/test-utils-core": "^0.1.1", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md index 359573ef01..789aeddc29 100644 --- a/packages/core/CHANGELOG.md +++ b/packages/core/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/core +## 0.7.14 + +### Patch Changes + +- a1c30d7ea: Add deprecation warning to package README. +- Updated dependencies + - @backstage/core-api@0.2.23 + ## 0.7.13 ### Patch Changes diff --git a/packages/core/package.json b/packages/core/package.json index 9048de576c..a53a45dbab 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/core", "description": "Core API used by Backstage plugins and apps", - "version": "0.7.13", + "version": "0.7.14", "private": false, "publishConfig": { "access": "public", @@ -30,7 +30,7 @@ }, "dependencies": { "@backstage/config": "^0.1.5", - "@backstage/core-api": "^0.2.22", + "@backstage/core-api": "^0.2.23", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -71,8 +71,8 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/create-app/CHANGELOG.md b/packages/create-app/CHANGELOG.md index 4658e413c1..b68ed6f85b 100644 --- a/packages/create-app/CHANGELOG.md +++ b/packages/create-app/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/create-app +## 0.3.28 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. + ## 0.3.27 ### Patch Changes diff --git a/packages/create-app/package.json b/packages/create-app/package.json index 0eb5d9e29c..8b03e01233 100644 --- a/packages/create-app/package.json +++ b/packages/create-app/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/create-app", "description": "Create app package for Backstage", - "version": "0.3.27", + "version": "0.3.28", "private": false, "publishConfig": { "access": "public" diff --git a/packages/dev-utils/CHANGELOG.md b/packages/dev-utils/CHANGELOG.md index 6f8b164371..6f7c33e46c 100644 --- a/packages/dev-utils/CHANGELOG.md +++ b/packages/dev-utils/CHANGELOG.md @@ -1,5 +1,22 @@ # @backstage/dev-utils +## 0.2.0 + +### Minor Changes + +- 76db86c45: Removed support for deprecated registered plugin routes. All routes now need to be added using `addPage` instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/test-utils@0.1.14 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.17 ### Patch Changes diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index 1f3a408e04..f9af637830 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/dev-utils", "description": "Utilities for developing Backstage plugins.", - "version": "0.1.17", + "version": "0.2.0", "private": false, "publishConfig": { "access": "public", @@ -29,13 +29,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.2", + "@backstage/core-app-api": "^0.1.3", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/catalog-model": "^0.8.2", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.2", - "@backstage/test-utils": "^0.1.13", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/catalog-model": "^0.8.4", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/test-utils": "^0.1.14", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,7 +50,7 @@ "react-router-dom": "6.0.0-beta.0" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.2", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/packages/integration-react/CHANGELOG.md b/packages/integration-react/CHANGELOG.md index 711d6d8203..48c0260fc5 100644 --- a/packages/integration-react/CHANGELOG.md +++ b/packages/integration-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/integration-react +## 0.1.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.3 ### Patch Changes diff --git a/packages/integration-react/package.json b/packages/integration-react/package.json index f7f20f3aec..e0161cb5ab 100644 --- a/packages/integration-react/package.json +++ b/packages/integration-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/integration-react", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ "dependencies": { "@backstage/config": "^0.1.2", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@backstage/theme": "^0.2.3", "@material-ui/core": "^4.11.0", @@ -33,9 +33,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/dev-utils": "^0.1.14", - "@backstage/test-utils": "^0.1.11", + "@backstage/cli": "^0.7.2", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/packages/techdocs-common/CHANGELOG.md b/packages/techdocs-common/CHANGELOG.md index 3d38612851..80d48f9dd7 100644 --- a/packages/techdocs-common/CHANGELOG.md +++ b/packages/techdocs-common/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/techdocs-common +## 0.6.5 + +### Patch Changes + +- c17c0fcf9: Adding additional checks on tech docs to prevent folder traversal via mkdocs.yml docs_dir value. +- Updated dependencies + - @backstage/catalog-model@0.8.4 + ## 0.6.4 ### Patch Changes diff --git a/packages/techdocs-common/package.json b/packages/techdocs-common/package.json index ad9d2c6ac2..0a545ce805 100644 --- a/packages/techdocs-common/package.json +++ b/packages/techdocs-common/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/techdocs-common", "description": "Common functionalities for TechDocs, to be shared between techdocs-backend plugin and techdocs-cli", - "version": "0.6.4", + "version": "0.6.5", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -39,7 +39,7 @@ "@azure/identity": "^1.2.2", "@azure/storage-blob": "^12.4.0", "@backstage/backend-common": "^0.8.2", - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", @@ -60,7 +60,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.2", "@types/fs-extra": "^9.0.5", "@types/git-url-parse": "^9.0.0", "@types/js-yaml": "^4.0.0", diff --git a/packages/test-utils/CHANGELOG.md b/packages/test-utils/CHANGELOG.md index 6ff92e79ae..68e45cd831 100644 --- a/packages/test-utils/CHANGELOG.md +++ b/packages/test-utils/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/test-utils +## 0.1.14 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + ## 0.1.13 ### Patch Changes diff --git a/packages/test-utils/package.json b/packages/test-utils/package.json index e183bf1430..797a23b17f 100644 --- a/packages/test-utils/package.json +++ b/packages/test-utils/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/test-utils", "description": "Utilities to test Backstage plugins and apps.", - "version": "0.1.13", + "version": "0.1.14", "private": false, "publishConfig": { "access": "public", @@ -29,8 +29,8 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/core-app-api": "^0.1.2", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/test-utils-core": "^0.1.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -46,7 +46,7 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.0", + "@backstage/cli": "^0.7.2", "@types/jest": "^26.0.7", "@types/node": "^14.14.32" }, diff --git a/plugins/api-docs/CHANGELOG.md b/plugins/api-docs/CHANGELOG.md index d51b0b0595..236517b105 100644 --- a/plugins/api-docs/CHANGELOG.md +++ b/plugins/api-docs/CHANGELOG.md @@ -1,5 +1,21 @@ # @backstage/plugin-api-docs +## 0.6.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- 7bd46b19d: Move `EntityTypePicker` to be consistent with `CatalogPage` and remove `api:` prefix from entity names +- Updated dependencies + - @backstage/plugin-catalog@0.6.4 + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.5.0 ### Minor Changes diff --git a/plugins/api-docs/package.json b/plugins/api-docs/package.json index 935adda96c..b8fa785122 100644 --- a/plugins/api-docs/package.json +++ b/plugins/api-docs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-api-docs", - "version": "0.5.0", + "version": "0.6.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ }, "dependencies": { "@asyncapi/react-component": "^0.23.0", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog": "^0.6.3", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog": "^0.6.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-icons/font": "^1.0.2", "@material-ui/core": "^4.11.0", @@ -51,10 +51,10 @@ "swagger-ui-react": "^3.37.2" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/auth-backend/CHANGELOG.md b/plugins/auth-backend/CHANGELOG.md index af9b4ca419..07662d46ff 100644 --- a/plugins/auth-backend/CHANGELOG.md +++ b/plugins/auth-backend/CHANGELOG.md @@ -1,5 +1,37 @@ # @backstage/plugin-auth-backend +## 0.3.14 + +### Patch Changes + +- 36e9a4084: Don't export the `defaultGoogleAuthProvider` +- c467cc4b9: Adds support for custom sign-in resolvers and profile transformations for the + Google auth provider. + + Adds an `ent` claim in Backstage tokens, with a list of + [entity references](https://backstage.io/docs/features/software-catalog/references) + related to your signed-in user's identities and groups across multiple systems. + + Adds an optional `providerFactories` argument to the `createRouter` exported by + the `auth-backend` plugin. + + Updates `BackstageIdentity` so that + + - `idToken` is deprecated in favor of `token` + - An optional `entity` field is added which represents the entity that the user is represented by within Backstage. + + More information: + + - [The identity resolver documentation](https://backstage.io/docs/auth/identity-resolver) + explains the concepts and shows how to implement your own. + - The [From Identity to Ownership](https://github.com/backstage/backstage/issues/4089) + RFC contains details about how this affects ownership in the catalog + +- Updated dependencies + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + - @backstage/test-utils@0.1.14 + ## 0.3.13 ### Patch Changes diff --git a/plugins/auth-backend/package.json b/plugins/auth-backend/package.json index 7c3492992f..d2b15a9d2d 100644 --- a/plugins/auth-backend/package.json +++ b/plugins/auth-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-auth-backend", - "version": "0.3.13", + "version": "0.3.14", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ }, "dependencies": { "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/test-utils": "^0.1.12", + "@backstage/test-utils": "^0.1.14", "@types/express": "^4.17.6", "@types/passport": "^1.0.3", "compression": "^1.7.4", @@ -68,7 +68,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.2", "@types/body-parser": "^1.19.0", "@types/cookie-parser": "^1.4.2", "@types/express-session": "^1.17.2", diff --git a/plugins/badges/CHANGELOG.md b/plugins/badges/CHANGELOG.md index 65e04fd931..914c29deec 100644 --- a/plugins/badges/CHANGELOG.md +++ b/plugins/badges/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-badges +## 0.2.3 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.2 ### Patch Changes diff --git a/plugins/badges/package.json b/plugins/badges/package.json index 8717719f8f..79111df09e 100644 --- a/plugins/badges/package.json +++ b/plugins/badges/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-badges", - "version": "0.2.2", + "version": "0.2.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,11 +20,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/bitrise/CHANGELOG.md b/plugins/bitrise/CHANGELOG.md index 94dba69629..aaf6e8658c 100644 --- a/plugins/bitrise/CHANGELOG.md +++ b/plugins/bitrise/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-bitrise +## 0.1.5 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.4 ### Patch Changes diff --git a/plugins/bitrise/package.json b/plugins/bitrise/package.json index aef5ee05f5..3d970a3e8b 100644 --- a/plugins/bitrise/package.json +++ b/plugins/bitrise/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-bitrise", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,10 +38,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/catalog-backend-module-msgraph/CHANGELOG.md b/plugins/catalog-backend-module-msgraph/CHANGELOG.md new file mode 100644 index 0000000000..e85601ab7c --- /dev/null +++ b/plugins/catalog-backend-module-msgraph/CHANGELOG.md @@ -0,0 +1,32 @@ +# @backstage/plugin-catalog-backend-module-msgraph + +## 0.1.1 + +### Patch Changes + +- 127048f92: Move `MicrosoftGraphOrgReaderProcessor` from `@backstage/plugin-catalog-backend` + to `@backstage/plugin-catalog-backend-module-msgraph`. + + The `MicrosoftGraphOrgReaderProcessor` isn't registered by default anymore, if + you want to continue using it you have to register it manually at the catalog + builder: + + 1. Add dependency to `@backstage/plugin-catalog-backend-module-msgraph` to the `package.json` of your backend. + 2. Add the processor to the catalog builder: + + ```typescript + // packages/backend/src/plugins/catalog.ts + builder.addProcessor( + MicrosoftGraphOrgReaderProcessor.fromConfig(config, { + logger, + }), + ); + ``` + + For more configuration details, see the [README of the `@backstage/plugin-catalog-backend-module-msgraph` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md). + +- 127048f92: Allow customizations of `MicrosoftGraphOrgReaderProcessor` by passing an + optional `groupTransformer`, `userTransformer`, and `organizationTransformer`. +- Updated dependencies + - @backstage/plugin-catalog-backend@0.10.4 + - @backstage/catalog-model@0.8.4 diff --git a/plugins/catalog-backend-module-msgraph/package.json b/plugins/catalog-backend-module-msgraph/package.json index 48df5a948f..f37b954706 100644 --- a/plugins/catalog-backend-module-msgraph/package.json +++ b/plugins/catalog-backend-module-msgraph/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend-module-msgraph", - "version": "0.1.0", + "version": "0.1.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,9 +29,9 @@ }, "dependencies": { "@azure/msal-node": "^1.1.0", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", - "@backstage/plugin-catalog-backend": "^0.10.2", + "@backstage/plugin-catalog-backend": "^0.10.4", "@microsoft/microsoft-graph-types": "^1.25.0", "cross-fetch": "^3.0.6", "lodash": "^4.17.15", @@ -40,8 +40,8 @@ "qs": "^6.9.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@types/lodash": "^4.14.151", "msw": "^0.29.0" }, diff --git a/plugins/catalog-backend/CHANGELOG.md b/plugins/catalog-backend/CHANGELOG.md index 1dc73669cd..0f2ca0a54d 100644 --- a/plugins/catalog-backend/CHANGELOG.md +++ b/plugins/catalog-backend/CHANGELOG.md @@ -1,5 +1,36 @@ # @backstage/plugin-catalog-backend +## 0.10.4 + +### Patch Changes + +- 127048f92: Move `MicrosoftGraphOrgReaderProcessor` from `@backstage/plugin-catalog-backend` + to `@backstage/plugin-catalog-backend-module-msgraph`. + + The `MicrosoftGraphOrgReaderProcessor` isn't registered by default anymore, if + you want to continue using it you have to register it manually at the catalog + builder: + + 1. Add dependency to `@backstage/plugin-catalog-backend-module-msgraph` to the `package.json` of your backend. + 2. Add the processor to the catalog builder: + + ```typescript + // packages/backend/src/plugins/catalog.ts + builder.addProcessor( + MicrosoftGraphOrgReaderProcessor.fromConfig(config, { + logger, + }), + ); + ``` + + For more configuration details, see the [README of the `@backstage/plugin-catalog-backend-module-msgraph` package](https://github.com/backstage/backstage/blob/master/plugins/catalog-backend-module-msgraph/README.md). + +- 71416fb64: Moved installation instructions from the main [backstage.io](https://backstage.io) documentation to the package README file. These instructions are not generally needed, since the plugin comes installed by default with `npx @backstage/create-app`. +- Updated dependencies + - @backstage/catalog-client@0.3.14 + - @backstage/plugin-search-backend-node@0.2.2 + - @backstage/catalog-model@0.8.4 + ## 0.10.3 ### Patch Changes diff --git a/plugins/catalog-backend/package.json b/plugins/catalog-backend/package.json index 517738d317..1ad33b1589 100644 --- a/plugins/catalog-backend/package.json +++ b/plugins/catalog-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-backend", - "version": "0.10.3", + "version": "0.10.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,12 +30,12 @@ }, "dependencies": { "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", - "@backstage/plugin-search-backend-node": "^0.2.1", + "@backstage/plugin-search-backend-node": "^0.2.2", "@backstage/search-common": "^0.1.2", "@octokit/graphql": "^4.5.8", "@types/express": "^4.17.6", @@ -64,8 +64,8 @@ }, "devDependencies": { "@backstage/backend-test-utils": "^0.1.3", - "@backstage/cli": "^0.7.1", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@types/core-js": "^2.5.4", "@types/git-url-parse": "^9.0.0", "@types/lodash": "^4.14.151", diff --git a/plugins/catalog-import/CHANGELOG.md b/plugins/catalog-import/CHANGELOG.md index 6cd89dbf82..67a512df55 100644 --- a/plugins/catalog-import/CHANGELOG.md +++ b/plugins/catalog-import/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-catalog-import +## 0.5.11 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.5.10 ### Patch Changes diff --git a/plugins/catalog-import/package.json b/plugins/catalog-import/package.json index 51aa1e5c13..abe8e988e9 100644 --- a/plugins/catalog-import/package.json +++ b/plugins/catalog-import/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-import", - "version": "0.5.10", + "version": "0.5.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,13 +30,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog-react/CHANGELOG.md b/plugins/catalog-react/CHANGELOG.md index c532a11ac7..57cfc0d800 100644 --- a/plugins/catalog-react/CHANGELOG.md +++ b/plugins/catalog-react/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-catalog-react +## 0.2.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-app-api@0.1.3 + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + ## 0.2.3 ### Patch Changes diff --git a/plugins/catalog-react/package.json b/plugins/catalog-react/package.json index 4e6d4374d2..e028516689 100644 --- a/plugins/catalog-react/package.json +++ b/plugins/catalog-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog-react", - "version": "0.2.3", + "version": "0.2.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,11 +28,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", - "@backstage/core-app-api": "^0.1.2", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", + "@backstage/core-app-api": "^0.1.3", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -45,9 +45,9 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/catalog/CHANGELOG.md b/plugins/catalog/CHANGELOG.md index e0ccbd068c..c14c458aaa 100644 --- a/plugins/catalog/CHANGELOG.md +++ b/plugins/catalog/CHANGELOG.md @@ -1,5 +1,24 @@ # @backstage/plugin-catalog +## 0.6.4 + +### Patch Changes + +- bba9df7f9: improve the wrapping behavior of long entity links +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API +- 7bd46b19d: Allow `defaultKind` from `CatalogTable.column.creatNameColumn` to be configurable +- 71416fb64: Moved installation instructions from the main [backstage.io](https://backstage.io) documentation to the package README file. These instructions are not generally needed, since the plugin comes installed by default with `npx @backstage/create-app`. +- e3cbfa8c2: Disambiguated titles of `EntityDependencyOfComponentsCard` and `EntityDependsOnComponentsCard`. +- 3d7b1c9f0: Adds an optional `actions` prop to `CatalogTable` and `CatalogPage` to support supplying custom actions for each entity row in the table. This uses the default actions if not provided. +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- 80a82ffce: Clearer titles for the relationship cards +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.6.3 ### Patch Changes diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index be89ae1422..3c3095e369 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-catalog", - "version": "0.6.3", + "version": "0.6.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,14 +30,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -54,10 +54,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/circleci/CHANGELOG.md b/plugins/circleci/CHANGELOG.md index dcbf6ad2b1..fbc8570479 100644 --- a/plugins/circleci/CHANGELOG.md +++ b/plugins/circleci/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-circleci +## 0.2.17 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.16 ### Patch Changes diff --git a/plugins/circleci/package.json b/plugins/circleci/package.json index eed2435a37..008df16b0f 100644 --- a/plugins/circleci/package.json +++ b/plugins/circleci/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-circleci", - "version": "0.2.16", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cloudbuild/CHANGELOG.md b/plugins/cloudbuild/CHANGELOG.md index 26cbe1c6da..c097091f18 100644 --- a/plugins/cloudbuild/CHANGELOG.md +++ b/plugins/cloudbuild/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-cloudbuild +## 0.2.17 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.16 ### Patch Changes diff --git a/plugins/cloudbuild/package.json b/plugins/cloudbuild/package.json index 43d0a3d01e..6eab7364a2 100644 --- a/plugins/cloudbuild/package.json +++ b/plugins/cloudbuild/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cloudbuild", - "version": "0.2.16", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/code-coverage/CHANGELOG.md b/plugins/code-coverage/CHANGELOG.md index c4d7e3bcc4..0d892a08c6 100644 --- a/plugins/code-coverage/CHANGELOG.md +++ b/plugins/code-coverage/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-code-coverage +## 0.1.5 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.4 ### Patch Changes diff --git a/plugins/code-coverage/package.json b/plugins/code-coverage/package.json index 3eb6e1a36d..eef3f439c8 100644 --- a/plugins/code-coverage/package.json +++ b/plugins/code-coverage/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-code-coverage", - "version": "0.1.4", + "version": "0.1.5", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,12 +20,12 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,10 +40,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/config-schema/CHANGELOG.md b/plugins/config-schema/CHANGELOG.md index e54fc1a802..e594b4dbc6 100644 --- a/plugins/config-schema/CHANGELOG.md +++ b/plugins/config-schema/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-config-schema +## 0.1.3 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.2 ### Patch Changes diff --git a/plugins/config-schema/package.json b/plugins/config-schema/package.json index cc5d638713..478e85c2a3 100644 --- a/plugins/config-schema/package.json +++ b/plugins/config-schema/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-config-schema", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -22,7 +22,7 @@ "dependencies": { "@backstage/config": "^0.1.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -35,10 +35,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/cost-insights/CHANGELOG.md b/plugins/cost-insights/CHANGELOG.md index f1d0b9b7c8..c73174a374 100644 --- a/plugins/cost-insights/CHANGELOG.md +++ b/plugins/cost-insights/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-cost-insights +## 0.11.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.10.2 ### Patch Changes diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index faba0ddf6d..8f371a2390 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-cost-insights", - "version": "0.10.2", + "version": "0.11.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ "dependencies": { "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -55,10 +55,10 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore-react/CHANGELOG.md b/plugins/explore-react/CHANGELOG.md index 17e3775c6d..4af81f6d8b 100644 --- a/plugins/explore-react/CHANGELOG.md +++ b/plugins/explore-react/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-explore-react +## 0.0.6 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.0.5 ### Patch Changes diff --git a/plugins/explore-react/package.json b/plugins/explore-react/package.json index 1f3c5c16bb..a7e9a7ffe2 100644 --- a/plugins/explore-react/package.json +++ b/plugins/explore-react/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore-react", - "version": "0.0.5", + "version": "0.0.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -28,12 +28,12 @@ "postpack": "backstage-cli postpack" }, "dependencies": { - "@backstage/core-plugin-api": "^0.1.2" + "@backstage/core-plugin-api": "^0.1.3" }, "devDependencies": { - "@backstage/cli": "^0.7.0", - "@backstage/dev-utils": "^0.1.14", - "@backstage/test-utils": "^0.1.11", + "@backstage/cli": "^0.7.2", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/explore/CHANGELOG.md b/plugins/explore/CHANGELOG.md index d760c70f8c..eef7e3ef98 100644 --- a/plugins/explore/CHANGELOG.md +++ b/plugins/explore/CHANGELOG.md @@ -1,5 +1,67 @@ # @backstage/plugin-explore +## 0.3.7 + +### Patch Changes + +- 5c4e6aee2: Refactors the explore plugin to be more customizable. This includes the following non-breaking changes: + + - Introduce new `ExploreLayout` page which can be used to create a custom `ExplorePage` + - Refactor `ExplorePage` to use a new `ExploreLayout` component + - Exports existing `DomainExplorerContent`, `GroupsExplorerContent`, & `ToolExplorerContent` components + - Allows `title` props to be customized + + Create a custom explore page in `packages/app/src/components/explore/ExplorePage.tsx`. + + ```tsx + import { + DomainExplorerContent, + ExploreLayout, + } from '@backstage/plugin-explore'; + import React from 'react'; + import { InnserSourceExplorerContent } from './InnserSourceExplorerContent'; + + export const ExplorePage = () => { + return ( + + + + + + + + + ); + }; + + export const explorePage = ; + ``` + + Now register the new explore page in `packages/app/src/App.tsx`. + + ```diff + + import { explorePage } from './components/explore/ExplorePage'; + + const routes = ( + + - } /> + + }> + + {explorePage} + + + + ); + ``` + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + - @backstage/plugin-explore-react@0.0.6 + ## 0.3.6 ### Patch Changes diff --git a/plugins/explore/package.json b/plugins/explore/package.json index c4a1ac59c0..cb21694748 100644 --- a/plugins/explore/package.json +++ b/plugins/explore/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-explore", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", - "@backstage/plugin-explore-react": "^0.0.5", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", + "@backstage/plugin-explore-react": "^0.0.6", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/fossa/CHANGELOG.md b/plugins/fossa/CHANGELOG.md index 897a70151a..59541f8d65 100644 --- a/plugins/fossa/CHANGELOG.md +++ b/plugins/fossa/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-fossa +## 0.2.9 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.8 ### Patch Changes diff --git a/plugins/fossa/package.json b/plugins/fossa/package.json index 4878f247c3..7c74f26281 100644 --- a/plugins/fossa/package.json +++ b/plugins/fossa/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-fossa", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gcp-projects/CHANGELOG.md b/plugins/gcp-projects/CHANGELOG.md index 3843a9519a..fc99dcbe09 100644 --- a/plugins/gcp-projects/CHANGELOG.md +++ b/plugins/gcp-projects/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gcp-projects +## 0.3.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.6 ### Patch Changes diff --git a/plugins/gcp-projects/package.json b/plugins/gcp-projects/package.json index ffd68dcf7a..623ff72c49 100644 --- a/plugins/gcp-projects/package.json +++ b/plugins/gcp-projects/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gcp-projects", - "version": "0.2.6", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/git-release-manager/CHANGELOG.md b/plugins/git-release-manager/CHANGELOG.md index 65018d7925..19538e822a 100644 --- a/plugins/git-release-manager/CHANGELOG.md +++ b/plugins/git-release-manager/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-git-release-manager +## 0.1.3 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.2 ### Patch Changes diff --git a/plugins/git-release-manager/package.json b/plugins/git-release-manager/package.json index 269ee2432c..446ecaf8fe 100644 --- a/plugins/git-release-manager/package.json +++ b/plugins/git-release-manager/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-git-release-manager", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -37,10 +37,10 @@ "recharts": "^1.8.5" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/github-actions/CHANGELOG.md b/plugins/github-actions/CHANGELOG.md index c404501902..95bf5e10cb 100644 --- a/plugins/github-actions/CHANGELOG.md +++ b/plugins/github-actions/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-github-actions +## 0.4.10 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.4.9 ### Patch Changes diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index bbbbc23d7e..1948b8babe 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-actions", - "version": "0.4.9", + "version": "0.4.10", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,11 +32,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/integration": "^0.5.6", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -51,10 +51,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/github-deployments/CHANGELOG.md b/plugins/github-deployments/CHANGELOG.md index 4a2dab8798..fb1a0cc11d 100644 --- a/plugins/github-deployments/CHANGELOG.md +++ b/plugins/github-deployments/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-github-deployments +## 0.1.9 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.8 ### Patch Changes diff --git a/plugins/github-deployments/package.json b/plugins/github-deployments/package.json index 67cdadc223..92b9c78c6a 100644 --- a/plugins/github-deployments/package.json +++ b/plugins/github-deployments/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-github-deployments", - "version": "0.1.8", + "version": "0.1.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,13 +20,13 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -38,10 +38,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/gitops-profiles/CHANGELOG.md b/plugins/gitops-profiles/CHANGELOG.md index ae6672581f..83cea3c807 100644 --- a/plugins/gitops-profiles/CHANGELOG.md +++ b/plugins/gitops-profiles/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-gitops-profiles +## 0.3.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.7 ### Patch Changes diff --git a/plugins/gitops-profiles/package.json b/plugins/gitops-profiles/package.json index 105d6ec2b4..fb9d674b5f 100644 --- a/plugins/gitops-profiles/package.json +++ b/plugins/gitops-profiles/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-gitops-profiles", - "version": "0.2.7", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -43,10 +43,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/graphiql/CHANGELOG.md b/plugins/graphiql/CHANGELOG.md index e0086af107..cfbc2de00a 100644 --- a/plugins/graphiql/CHANGELOG.md +++ b/plugins/graphiql/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-graphiql +## 0.2.12 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.11 ### Patch Changes diff --git a/plugins/graphiql/package.json b/plugins/graphiql/package.json index c2baf9f720..0723062115 100644 --- a/plugins/graphiql/package.json +++ b/plugins/graphiql/package.json @@ -1,7 +1,7 @@ { "name": "@backstage/plugin-graphiql", "description": "Backstage plugin for browsing GraphQL APIs", - "version": "0.2.11", + "version": "0.2.12", "private": false, "publishConfig": { "access": "public", @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/ilert/CHANGELOG.md b/plugins/ilert/CHANGELOG.md index 2bbcc71424..041091f136 100644 --- a/plugins/ilert/CHANGELOG.md +++ b/plugins/ilert/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-ilert +## 0.1.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.3 ### Patch Changes diff --git a/plugins/ilert/package.json b/plugins/ilert/package.json index 7dbe6d1469..479fd2223d 100644 --- a/plugins/ilert/package.json +++ b/plugins/ilert/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-ilert", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,11 +20,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@date-io/luxon": "1.x", "@material-ui/core": "^4.11.0", @@ -38,10 +38,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/jenkins/CHANGELOG.md b/plugins/jenkins/CHANGELOG.md index 9fbe7959ff..eabbecfa19 100644 --- a/plugins/jenkins/CHANGELOG.md +++ b/plugins/jenkins/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-jenkins +## 0.4.6 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.4.5 ### Patch Changes diff --git a/plugins/jenkins/package.json b/plugins/jenkins/package.json index e2772ac118..ae1535b371 100644 --- a/plugins/jenkins/package.json +++ b/plugins/jenkins/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-jenkins", - "version": "0.4.5", + "version": "0.4.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/kafka/CHANGELOG.md b/plugins/kafka/CHANGELOG.md index ca1fa48e60..3aa2ec1c5c 100644 --- a/plugins/kafka/CHANGELOG.md +++ b/plugins/kafka/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kafka +## 0.2.9 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.8 ### Patch Changes diff --git a/plugins/kafka/package.json b/plugins/kafka/package.json index 4d243b268f..9d8880720e 100644 --- a/plugins/kafka/package.json +++ b/plugins/kafka/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kafka", - "version": "0.2.8", + "version": "0.2.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -34,10 +34,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/kubernetes/CHANGELOG.md b/plugins/kubernetes/CHANGELOG.md index db8448ea7e..375abba79c 100644 --- a/plugins/kubernetes/CHANGELOG.md +++ b/plugins/kubernetes/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-kubernetes +## 0.4.6 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.4.5 ### Patch Changes diff --git a/plugins/kubernetes/package.json b/plugins/kubernetes/package.json index 0e53e7febd..7d4149cea2 100644 --- a/plugins/kubernetes/package.json +++ b/plugins/kubernetes/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-kubernetes", - "version": "0.4.5", + "version": "0.4.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,11 +30,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/plugin-kubernetes-common": "^0.1.1", "@backstage/theme": "^0.2.8", "@kubernetes/client-node": "^0.14.0", @@ -50,10 +50,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/lighthouse/CHANGELOG.md b/plugins/lighthouse/CHANGELOG.md index daa54d122a..0742d51b4c 100644 --- a/plugins/lighthouse/CHANGELOG.md +++ b/plugins/lighthouse/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-lighthouse +## 0.2.18 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.17 ### Patch Changes diff --git a/plugins/lighthouse/package.json b/plugins/lighthouse/package.json index 300643cb19..1d5415a521 100644 --- a/plugins/lighthouse/package.json +++ b/plugins/lighthouse/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-lighthouse", - "version": "0.2.17", + "version": "0.2.18", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,11 +31,11 @@ "start": "backstage-cli plugin:serve" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/newrelic/CHANGELOG.md b/plugins/newrelic/CHANGELOG.md index 2091ef6a1d..9f5d70354a 100644 --- a/plugins/newrelic/CHANGELOG.md +++ b/plugins/newrelic/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-newrelic +## 0.3.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.7 ### Patch Changes diff --git a/plugins/newrelic/package.json b/plugins/newrelic/package.json index 72619a4341..4bbcd13440 100644 --- a/plugins/newrelic/package.json +++ b/plugins/newrelic/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-newrelic", - "version": "0.2.7", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,7 +32,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/org/CHANGELOG.md b/plugins/org/CHANGELOG.md index f2a87b7d1c..c87461ae32 100644 --- a/plugins/org/CHANGELOG.md +++ b/plugins/org/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-org +## 0.3.15 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- ce4abc1e0: Display a tooltip for ownership cards listing the related entities +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.3.14 ### Patch Changes diff --git a/plugins/org/package.json b/plugins/org/package.json index 22ca7beaa8..2ff0fac43b 100644 --- a/plugins/org/package.json +++ b/plugins/org/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-org", - "version": "0.3.14", + "version": "0.3.15", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -20,10 +20,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -35,10 +35,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/pagerduty/CHANGELOG.md b/plugins/pagerduty/CHANGELOG.md index 14ac2e0674..48abeffb17 100644 --- a/plugins/pagerduty/CHANGELOG.md +++ b/plugins/pagerduty/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-pagerduty +## 0.3.6 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.3.5 ### Patch Changes diff --git a/plugins/pagerduty/package.json b/plugins/pagerduty/package.json index 11cc330ab1..d56d162f03 100644 --- a/plugins/pagerduty/package.json +++ b/plugins/pagerduty/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-pagerduty", - "version": "0.3.5", + "version": "0.3.6", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/proxy-backend/CHANGELOG.md b/plugins/proxy-backend/CHANGELOG.md index cdf55997d0..6b76b1f26e 100644 --- a/plugins/proxy-backend/CHANGELOG.md +++ b/plugins/proxy-backend/CHANGELOG.md @@ -1,5 +1,11 @@ # @backstage/plugin-proxy-backend +## 0.2.11 + +### Patch Changes + +- 13da7be3c: Clean up proxy creation log messages and make them include the mount path. + ## 0.2.10 ### Patch Changes diff --git a/plugins/proxy-backend/package.json b/plugins/proxy-backend/package.json index e3a1849231..389aedfb99 100644 --- a/plugins/proxy-backend/package.json +++ b/plugins/proxy-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-proxy-backend", - "version": "0.2.10", + "version": "0.2.11", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -42,7 +42,7 @@ "yup": "^0.29.3" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.2", "@types/http-proxy-middleware": "^0.19.3", "@types/supertest": "^2.0.8", "@types/uuid": "^8.0.0", diff --git a/plugins/register-component/CHANGELOG.md b/plugins/register-component/CHANGELOG.md index 4dbb5fc7a6..e151c82ce9 100644 --- a/plugins/register-component/CHANGELOG.md +++ b/plugins/register-component/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-register-component +## 0.2.17 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.2.16 ### Patch Changes diff --git a/plugins/register-component/package.json b/plugins/register-component/package.json index 35d1310200..cb4369ca84 100644 --- a/plugins/register-component/package.json +++ b/plugins/register-component/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-register-component", - "version": "0.2.16", + "version": "0.2.17", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,10 +46,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/rollbar/CHANGELOG.md b/plugins/rollbar/CHANGELOG.md index c936d216bd..b4635777ee 100644 --- a/plugins/rollbar/CHANGELOG.md +++ b/plugins/rollbar/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-rollbar +## 0.3.7 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.3.6 ### Patch Changes diff --git a/plugins/rollbar/package.json b/plugins/rollbar/package.json index f94a72c703..68be84b55d 100644 --- a/plugins/rollbar/package.json +++ b/plugins/rollbar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-rollbar", - "version": "0.3.6", + "version": "0.3.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/scaffolder-backend/CHANGELOG.md b/plugins/scaffolder-backend/CHANGELOG.md index f747fce610..1a843931a1 100644 --- a/plugins/scaffolder-backend/CHANGELOG.md +++ b/plugins/scaffolder-backend/CHANGELOG.md @@ -1,5 +1,16 @@ # @backstage/plugin-scaffolder-backend +## 0.12.3 + +### Patch Changes + +- a7f5fe7d7: created an action to write a catalog-info file +- 71416fb64: Moved installation instructions from the main [backstage.io](https://backstage.io) documentation to the package README file. These instructions are not generally needed, since the plugin comes installed by default with `npx @backstage/create-app`. +- c18a3c2ae: Correctly recognize whether the cookiecutter command exists +- Updated dependencies + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + ## 0.12.2 ### Patch Changes diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 22979d42e8..1ad60b7248 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder-backend", - "version": "0.12.2", + "version": "0.12.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,8 +30,8 @@ }, "dependencies": { "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", @@ -64,8 +64,8 @@ "yaml": "^1.10.0" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/test-utils": "^0.1.14", "@types/command-exists": "^1.2.0", "@types/fs-extra": "^9.0.1", "@types/mock-fs": "^4.13.0", diff --git a/plugins/scaffolder/CHANGELOG.md b/plugins/scaffolder/CHANGELOG.md index 41603dbafa..ebe8a0c0ba 100644 --- a/plugins/scaffolder/CHANGELOG.md +++ b/plugins/scaffolder/CHANGELOG.md @@ -1,5 +1,19 @@ # @backstage/plugin-scaffolder +## 0.9.9 + +### Patch Changes + +- 5f4339b8c: Adding `FeatureFlag` component and treating `FeatureFlags` as first class citizens to composability API +- 71416fb64: Moved installation instructions from the main [backstage.io](https://backstage.io) documentation to the package README file. These instructions are not generally needed, since the plugin comes installed by default with `npx @backstage/create-app`. +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-client@0.3.14 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.9.8 ### Patch Changes diff --git a/plugins/scaffolder/package.json b/plugins/scaffolder/package.json index 1506b25fa2..88845b402b 100644 --- a/plugins/scaffolder/package.json +++ b/plugins/scaffolder/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-scaffolder", - "version": "0.9.8", + "version": "0.9.9", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,15 +30,15 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-client": "^0.3.13", - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-client": "^0.3.14", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -62,10 +62,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.3.0", diff --git a/plugins/search-backend-node/CHANGELOG.md b/plugins/search-backend-node/CHANGELOG.md index a0e81d81ba..9973191a24 100644 --- a/plugins/search-backend-node/CHANGELOG.md +++ b/plugins/search-backend-node/CHANGELOG.md @@ -1,5 +1,12 @@ # @backstage/plugin-search-backend-node +## 0.2.2 + +### Patch Changes + +- 9c8ea7e24: Handle errors in collators and decorators and log them. +- 7e7cec86a: Fixed bug preventing searches with filter values containing `:` from returning results. + ## 0.2.1 ### Patch Changes diff --git a/plugins/search-backend-node/package.json b/plugins/search-backend-node/package.json index fbbe02778f..c8a8eef143 100644 --- a/plugins/search-backend-node/package.json +++ b/plugins/search-backend-node/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search-backend-node", - "version": "0.2.1", + "version": "0.2.2", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,7 +26,7 @@ }, "devDependencies": { "@backstage/backend-common": "^0.8.3", - "@backstage/cli": "^0.7.1" + "@backstage/cli": "^0.7.2" }, "files": [ "dist" diff --git a/plugins/search/CHANGELOG.md b/plugins/search/CHANGELOG.md index 29320a1c79..3292afa2d8 100644 --- a/plugins/search/CHANGELOG.md +++ b/plugins/search/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-search +## 0.4.1 + +### Patch Changes + +- df51a5507: Fix empty state not being displayed on missing results. +- d5db15efb: Use the `identityApi` to forward authorization headers to the `search-backend` +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.4.0 ### Minor Changes diff --git a/plugins/search/package.json b/plugins/search/package.json index 4ae6a778fa..0cf27e8409 100644 --- a/plugins/search/package.json +++ b/plugins/search/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-search", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -29,11 +29,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/search-common": "^0.1.2", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/sentry/CHANGELOG.md b/plugins/sentry/CHANGELOG.md index 02e90ce9a7..b51632b812 100644 --- a/plugins/sentry/CHANGELOG.md +++ b/plugins/sentry/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sentry +## 0.3.13 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.3.12 ### Patch Changes diff --git a/plugins/sentry/package.json b/plugins/sentry/package.json index 465ed115b7..c69c233a23 100644 --- a/plugins/sentry/package.json +++ b/plugins/sentry/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sentry", - "version": "0.3.12", + "version": "0.3.13", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -47,10 +47,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/shortcuts/CHANGELOG.md b/plugins/shortcuts/CHANGELOG.md index 9b1f51940c..77748cd722 100644 --- a/plugins/shortcuts/CHANGELOG.md +++ b/plugins/shortcuts/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-shortcuts +## 0.1.4 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.1.3 ### Patch Changes diff --git a/plugins/shortcuts/package.json b/plugins/shortcuts/package.json index 2a957572b9..8c255c39a4 100644 --- a/plugins/shortcuts/package.json +++ b/plugins/shortcuts/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-shortcuts", - "version": "0.1.3", + "version": "0.1.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,7 +21,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -36,10 +36,10 @@ "zen-observable": "^0.8.15" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/sonarqube/CHANGELOG.md b/plugins/sonarqube/CHANGELOG.md index a5d9656f88..8df5372fa9 100644 --- a/plugins/sonarqube/CHANGELOG.md +++ b/plugins/sonarqube/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-sonarqube +## 0.1.20 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.19 ### Patch Changes diff --git a/plugins/sonarqube/package.json b/plugins/sonarqube/package.json index 4f37b05d67..b83fcf1669 100644 --- a/plugins/sonarqube/package.json +++ b/plugins/sonarqube/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-sonarqube", - "version": "0.1.19", + "version": "0.1.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -32,10 +32,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -48,10 +48,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/splunk-on-call/CHANGELOG.md b/plugins/splunk-on-call/CHANGELOG.md index dead1f80a1..037be680c9 100644 --- a/plugins/splunk-on-call/CHANGELOG.md +++ b/plugins/splunk-on-call/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-splunk-on-call +## 0.3.3 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.3.2 ### Patch Changes diff --git a/plugins/splunk-on-call/package.json b/plugins/splunk-on-call/package.json index 4a6257184a..d1bfbfa9b8 100644 --- a/plugins/splunk-on-call/package.json +++ b/plugins/splunk-on-call/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-splunk-on-call", - "version": "0.3.2", + "version": "0.3.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -30,10 +30,10 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -46,10 +46,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/tech-radar/CHANGELOG.md b/plugins/tech-radar/CHANGELOG.md index 5a0592b078..5b9465a109 100644 --- a/plugins/tech-radar/CHANGELOG.md +++ b/plugins/tech-radar/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-tech-radar +## 0.4.1 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.4.0 ### Minor Changes diff --git a/plugins/tech-radar/package.json b/plugins/tech-radar/package.json index 539a1ee3c5..e4dd9de462 100644 --- a/plugins/tech-radar/package.json +++ b/plugins/tech-radar/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-tech-radar", - "version": "0.4.0", + "version": "0.4.1", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -44,10 +44,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/techdocs-backend/CHANGELOG.md b/plugins/techdocs-backend/CHANGELOG.md index 1169655028..d9992c8467 100644 --- a/plugins/techdocs-backend/CHANGELOG.md +++ b/plugins/techdocs-backend/CHANGELOG.md @@ -1,5 +1,14 @@ # @backstage/plugin-techdocs-backend +## 0.8.4 + +### Patch Changes + +- fea7fa0ba: Return a `304 Not Modified` from the `/sync/:namespace/:kind/:name` endpoint if nothing was built. This enables the caller to know whether a refresh of the docs page will return updated content (-> `201 Created`) or not (-> `304 Not Modified`). +- Updated dependencies + - @backstage/techdocs-common@0.6.5 + - @backstage/catalog-model@0.8.4 + ## 0.8.3 ### Patch Changes diff --git a/plugins/techdocs-backend/package.json b/plugins/techdocs-backend/package.json index 9284ac9ac8..2dbfb17b23 100644 --- a/plugins/techdocs-backend/package.json +++ b/plugins/techdocs-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs-backend", - "version": "0.8.3", + "version": "0.8.4", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,10 +31,10 @@ }, "dependencies": { "@backstage/backend-common": "^0.8.3", - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/errors": "^0.1.1", - "@backstage/techdocs-common": "^0.6.3", + "@backstage/techdocs-common": "^0.6.5", "@types/express": "^4.17.6", "cross-fetch": "^3.0.6", "dockerode": "^3.2.1", @@ -45,7 +45,7 @@ "winston": "^3.2.1" }, "devDependencies": { - "@backstage/cli": "^0.7.1", + "@backstage/cli": "^0.7.2", "@types/dockerode": "^3.2.1", "supertest": "^6.1.3" }, diff --git a/plugins/techdocs/CHANGELOG.md b/plugins/techdocs/CHANGELOG.md index c6d61c411b..94ae68a560 100644 --- a/plugins/techdocs/CHANGELOG.md +++ b/plugins/techdocs/CHANGELOG.md @@ -1,5 +1,18 @@ # @backstage/plugin-techdocs +## 0.9.7 + +### Patch Changes + +- aefd54da6: Fix the overlapping between the sidebar and the tabs navigation when enabled in mkdocs (features: navigation.tabs) +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- 1dfec7a2a: Refactor the implicit logic from `` into an explicit state machine. This resolves some state synchronization issues when content is refreshed or rebuilt in the backend. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/integration-react@0.1.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.9.6 ### Patch Changes diff --git a/plugins/techdocs/package.json b/plugins/techdocs/package.json index 0f1931b736..2f15ffe3db 100644 --- a/plugins/techdocs/package.json +++ b/plugins/techdocs/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-techdocs", - "version": "0.9.6", + "version": "0.9.7", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,14 +31,14 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.3", + "@backstage/catalog-model": "^0.8.4", "@backstage/config": "^0.1.5", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", "@backstage/integration": "^0.5.6", - "@backstage/integration-react": "^0.1.3", - "@backstage/plugin-catalog-react": "^0.2.3", + "@backstage/integration-react": "^0.1.4", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -52,10 +52,10 @@ "sanitize-html": "^2.3.2" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/react-hooks": "^3.4.2", diff --git a/plugins/todo/CHANGELOG.md b/plugins/todo/CHANGELOG.md index 901fc314c7..d9b5ac52d1 100644 --- a/plugins/todo/CHANGELOG.md +++ b/plugins/todo/CHANGELOG.md @@ -1,5 +1,15 @@ # @backstage/plugin-todo +## 0.1.3 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + - @backstage/catalog-model@0.8.4 + - @backstage/plugin-catalog-react@0.2.4 + ## 0.1.2 ### Patch Changes diff --git a/plugins/todo/package.json b/plugins/todo/package.json index 291da759e5..4c86b5b524 100644 --- a/plugins/todo/package.json +++ b/plugins/todo/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-todo", - "version": "0.1.2", + "version": "0.1.3", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -26,11 +26,11 @@ "clean": "backstage-cli clean" }, "dependencies": { - "@backstage/catalog-model": "^0.8.2", + "@backstage/catalog-model": "^0.8.4", "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/errors": "^0.1.1", - "@backstage/plugin-catalog-react": "^0.2.2", + "@backstage/plugin-catalog-react": "^0.2.4", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -40,10 +40,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/user-settings/CHANGELOG.md b/plugins/user-settings/CHANGELOG.md index 1c0dbb2821..95344dd0a5 100644 --- a/plugins/user-settings/CHANGELOG.md +++ b/plugins/user-settings/CHANGELOG.md @@ -1,5 +1,13 @@ # @backstage/plugin-user-settings +## 0.2.12 + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.11 ### Patch Changes diff --git a/plugins/user-settings/package.json b/plugins/user-settings/package.json index 09a177c659..e4720dec08 100644 --- a/plugins/user-settings/package.json +++ b/plugins/user-settings/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-user-settings", - "version": "0.2.11", + "version": "0.2.12", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,11 +42,11 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/core-plugin-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/core-plugin-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", diff --git a/plugins/welcome/CHANGELOG.md b/plugins/welcome/CHANGELOG.md index 53ffd59264..25420009d9 100644 --- a/plugins/welcome/CHANGELOG.md +++ b/plugins/welcome/CHANGELOG.md @@ -1,5 +1,17 @@ # @backstage/plugin-welcome +## 0.3.0 + +### Minor Changes + +- d719926d2: **BREAKING CHANGE** Remove deprecated route registrations, meaning that it is no longer enough to only import the plugin in the app and the exported page extension must be used instead. + +### Patch Changes + +- 48c9fcd33: Migrated to use the new `@backstage/core-*` packages rather than `@backstage/core`. +- Updated dependencies + - @backstage/core-plugin-api@0.1.3 + ## 0.2.8 ### Patch Changes diff --git a/plugins/welcome/package.json b/plugins/welcome/package.json index 067d557a7e..b99897b4bf 100644 --- a/plugins/welcome/package.json +++ b/plugins/welcome/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-welcome", - "version": "0.2.8", + "version": "0.3.0", "main": "src/index.ts", "types": "src/index.ts", "private": false, @@ -31,7 +31,7 @@ }, "dependencies": { "@backstage/core-components": "^0.1.3", - "@backstage/core-plugin-api": "^0.1.2", + "@backstage/core-plugin-api": "^0.1.3", "@backstage/theme": "^0.2.8", "@material-ui/core": "^4.11.0", "@material-ui/icons": "^4.9.1", @@ -42,10 +42,10 @@ "react-use": "^17.2.4" }, "devDependencies": { - "@backstage/cli": "^0.7.1", - "@backstage/core-app-api": "^0.1.2", - "@backstage/dev-utils": "^0.1.17", - "@backstage/test-utils": "^0.1.13", + "@backstage/cli": "^0.7.2", + "@backstage/core-app-api": "^0.1.3", + "@backstage/dev-utils": "^0.2.0", + "@backstage/test-utils": "^0.1.14", "@testing-library/jest-dom": "^5.10.1", "@testing-library/react": "^11.2.5", "@testing-library/user-event": "^13.1.8", From 4ee343966a0ec59e84c9a2c7a9b8f4675a7c9195 Mon Sep 17 00:00:00 2001 From: Vannarath Poeu Date: Tue, 22 Jun 2021 16:51:46 +0800 Subject: [PATCH 36/89] Replaced moment and dayjs with luxon Signed-off-by: Vannarath Poeu --- plugins/cost-insights/package.json | 3 +- .../CostOverviewBreakdownChart.tsx | 11 ++--- .../CostOverviewCard/CostOverviewChart.tsx | 11 ++--- .../ProjectGrowthAlertChart.tsx | 10 +++-- .../ProjectGrowthInstructionsPage.tsx | 4 +- plugins/cost-insights/src/example/client.ts | 10 ++--- .../src/forms/AlertSnoozeForm.tsx | 4 +- .../cost-insights/src/testUtils/testUtils.ts | 16 ++++--- plugins/cost-insights/src/types/Duration.ts | 2 +- plugins/cost-insights/src/utils/change.ts | 15 ++----- plugins/cost-insights/src/utils/duration.ts | 44 +++++++++++-------- plugins/cost-insights/src/utils/formatters.ts | 22 +++++----- 12 files changed, 80 insertions(+), 72 deletions(-) diff --git a/plugins/cost-insights/package.json b/plugins/cost-insights/package.json index faba0ddf6d..1eb0f613e8 100644 --- a/plugins/cost-insights/package.json +++ b/plugins/cost-insights/package.json @@ -41,9 +41,8 @@ "@types/react": "^16.9", "@types/recharts": "^1.8.14", "classnames": "^2.2.6", - "dayjs": "^1.9.4", "history": "^5.0.0", - "moment": "^2.27.0", + "luxon": "^1.26.0", "pluralize": "^8.0.0", "qs": "^6.9.4", "react": "^16.13.1", diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx index 7202c069f5..d0731326f2 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewBreakdownChart.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ import React, { useState } from 'react'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; +import { DateTime } from 'luxon'; import { useTheme, Box, @@ -54,8 +53,6 @@ import { formatPeriod } from '../../utils/formatters'; import { aggregationSum } from '../../utils/sum'; import { BarChartLegendOptions } from '../BarChart/BarChartLegend'; -dayjs.extend(utc); - export type CostOverviewBreakdownChartProps = { costBreakdown: Cost[]; }; @@ -184,7 +181,11 @@ export const CostOverviewBreakdownChart = ({ }) => { if (isInvalid({ label, payload })) return null; - const dateTitle = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); + const date = + typeof label === 'number' + ? DateTime.fromMillis(label) + : DateTime.fromISO(label!); + const dateTitle = date.toUTC().toFormat(DEFAULT_DATE_FORMAT); const items = payload.map(p => ({ label: p.dataKey as string, value: formatGraphValue(p.value as number), diff --git a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx index 4f806658be..e7b478ba07 100644 --- a/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx +++ b/plugins/cost-insights/src/components/CostOverviewCard/CostOverviewChart.tsx @@ -14,8 +14,7 @@ * limitations under the License. */ import React from 'react'; -import dayjs from 'dayjs'; -import utc from 'dayjs/plugin/utc'; +import { DateTime } from 'luxon'; import { useTheme, Box } from '@material-ui/core'; import { ComposedChart, @@ -52,8 +51,6 @@ import { groupByDate, toDataMax, trendFrom } from '../../utils/charts'; import { aggregationSort } from '../../utils/sort'; import { CostOverviewLegend } from './CostOverviewLegend'; -dayjs.extend(utc); - type CostOverviewChartProps = { metric: Maybe; metricData: Maybe; @@ -108,7 +105,11 @@ export const CostOverviewChart = ({ if (isInvalid({ label, payload })) return null; const dataKeys = [data.dailyCost.dataKey, data.metric.dataKey]; - const title = dayjs(label).utc().format(DEFAULT_DATE_FORMAT); + const date = + typeof label === 'number' + ? DateTime.fromMillis(label) + : DateTime.fromISO(label!); + const title = date.toUTC().toFormat(DEFAULT_DATE_FORMAT); const items = payload .filter(p => dataKeys.includes(p.dataKey as string)) .map(p => ({ diff --git a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx index bdfb27b8e8..74c0cea34b 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthAlertCard/ProjectGrowthAlertChart.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Box } from '@material-ui/core'; import { BarChart, BarChartLegend, BarChartLegendOptions } from '../BarChart'; import { LegendItem } from '../LegendItem'; @@ -38,8 +38,12 @@ export const ProjectGrowthAlertChart = ({ const resourceData = alert.products.map(resourceOf); const options: Partial = { - previousName: moment(alert.periodStart, 'YYYY-[Q]Q').format('[Q]Q YYYY'), - currentName: moment(alert.periodEnd, 'YYYY-[Q]Q').format('[Q]Q YYYY'), + previousName: DateTime.fromFormat(alert.periodStart, "yyyy-'Q'q").toFormat( + "'Q'q yyyy", + ), + currentName: DateTime.fromFormat(alert.periodEnd, "yyyy-'Q'q").toFormat( + "'Q'q yyyy", + ), }; return ( diff --git a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx index 89d2f28a45..35b00ce6f6 100644 --- a/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx +++ b/plugins/cost-insights/src/components/ProjectGrowthInstructionsPage/ProjectGrowthInstructionsPage.tsx @@ -15,7 +15,7 @@ */ import React from 'react'; -import moment from 'moment'; +import { DateTime } from 'luxon'; import { Box, Typography } from '@material-ui/core'; import { AlertInstructionsLayout } from '../AlertInstructionsLayout'; import { ProductInsightsChart } from '../ProductInsightsCard'; @@ -30,7 +30,7 @@ import { import { ProjectGrowthAlert } from '../../alerts'; import { InfoCard } from '@backstage/core-components'; -const today = moment().format(DEFAULT_DATE_FORMAT); +const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT); export const ProjectGrowthInstructionsPage = () => { const alertData: ProjectGrowthData = { diff --git a/plugins/cost-insights/src/example/client.ts b/plugins/cost-insights/src/example/client.ts index dec3864ce6..a7a2a0e22f 100644 --- a/plugins/cost-insights/src/example/client.ts +++ b/plugins/cost-insights/src/example/client.ts @@ -15,7 +15,7 @@ */ /* eslint-disable no-restricted-imports */ -import dayjs from 'dayjs'; +import { DateTime } from 'luxon'; import { CostInsightsApi, ProductInsightsOptions } from '../api'; import { Alert, @@ -46,7 +46,7 @@ export class ExampleCostInsightsClient implements CostInsightsApi { getLastCompleteBillingDate(): Promise { return Promise.resolve( - dayjs().subtract(1, 'day').format(DEFAULT_DATE_FORMAT), + DateTime.now().minus({ days: 1 }).toFormat(DEFAULT_DATE_FORMAT), ); } @@ -175,13 +175,13 @@ export class ExampleCostInsightsClient implements CostInsightsApi { ], }; - const today = dayjs(); + const today = DateTime.now(); const alerts: Alert[] = await this.request({ group }, [ new ProjectGrowthAlert(projectGrowthData), new UnlabeledDataflowAlert(unlabeledDataflowData), new KubernetesMigrationAlert(this, { - startDate: today.subtract(30, 'day').format(DEFAULT_DATE_FORMAT), - endDate: today.format(DEFAULT_DATE_FORMAT), + startDate: today.minus({ days: 30 }).toFormat(DEFAULT_DATE_FORMAT), + endDate: today.toFormat(DEFAULT_DATE_FORMAT), change: { ratio: 0, amount: 0, diff --git a/plugins/cost-insights/src/forms/AlertSnoozeForm.tsx b/plugins/cost-insights/src/forms/AlertSnoozeForm.tsx index eb78327dc9..0dcc0a949e 100644 --- a/plugins/cost-insights/src/forms/AlertSnoozeForm.tsx +++ b/plugins/cost-insights/src/forms/AlertSnoozeForm.tsx @@ -21,7 +21,7 @@ import React, { forwardRef, FormEventHandler, } from 'react'; -import dayjs from 'dayjs'; +import { DateTime } from 'luxon'; import { Box, FormControl, @@ -57,7 +57,7 @@ export const AlertSnoozeForm = forwardRef< e.preventDefault(); if (duration) { const repeatInterval = 1; - const today = dayjs().format(DEFAULT_DATE_FORMAT); + const today = DateTime.now().toFormat(DEFAULT_DATE_FORMAT); onSubmit({ intervals: intervalsOf(duration, today, repeatInterval), }); diff --git a/plugins/cost-insights/src/testUtils/testUtils.ts b/plugins/cost-insights/src/testUtils/testUtils.ts index a2210b67cf..2c6f6b5ef2 100644 --- a/plugins/cost-insights/src/testUtils/testUtils.ts +++ b/plugins/cost-insights/src/testUtils/testUtils.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import dayjs from 'dayjs'; +import { DateTime } from 'luxon'; import regression, { DataPoint } from 'regression'; import { ChangeStatistic, @@ -58,9 +58,9 @@ export function aggregationFor( ): DateAggregation[] { const { duration, endDate } = parseIntervals(intervals); const inclusiveEndDate = inclusiveEndDateOf(duration, endDate); - const days = dayjs(endDate).diff( - inclusiveStartDateOf(duration, inclusiveEndDate), - 'day', + const days = DateTime.fromISO(endDate).diff( + DateTime.fromISO(inclusiveStartDateOf(duration, inclusiveEndDate)), + 'days', ); function nextDelta(): number { @@ -74,9 +74,11 @@ export function aggregationFor( return [...Array(days).keys()].reduce( (values: DateAggregation[], i: number): DateAggregation[] => { const last = values.length ? values[values.length - 1].amount : baseline; - const date = dayjs(inclusiveStartDateOf(duration, inclusiveEndDate)) - .add(i, 'day') - .format(DEFAULT_DATE_FORMAT); + const date = DateTime.fromISO( + inclusiveStartDateOf(duration, inclusiveEndDate), + ) + .plus({ days: i }) + .toFormat(DEFAULT_DATE_FORMAT); const amount = Math.max(0, last + nextDelta()); values.push({ date: date, diff --git a/plugins/cost-insights/src/types/Duration.ts b/plugins/cost-insights/src/types/Duration.ts index dcc0d9390c..94297ac00a 100644 --- a/plugins/cost-insights/src/types/Duration.ts +++ b/plugins/cost-insights/src/types/Duration.ts @@ -27,4 +27,4 @@ export enum Duration { P3M = 'P3M', } -export const DEFAULT_DATE_FORMAT = 'YYYY-MM-DD'; +export const DEFAULT_DATE_FORMAT = 'yyyy-LL-dd'; diff --git a/plugins/cost-insights/src/utils/change.ts b/plugins/cost-insights/src/utils/change.ts index 8ab927ecf6..3a0d19b123 100644 --- a/plugins/cost-insights/src/utils/change.ts +++ b/plugins/cost-insights/src/utils/change.ts @@ -24,13 +24,10 @@ import { Duration, DateAggregation, } from '../types'; -import dayjs, { OpUnitType } from 'dayjs'; -import durationPlugin from 'dayjs/plugin/duration'; +import { DateTime, Duration as LuxonDuration } from 'luxon'; import { inclusiveStartDateOf } from './duration'; import { notEmpty } from './assert'; -dayjs.extend(durationPlugin); - // Used for displaying status colors export function growthOf(change: ChangeStatistic): GrowthType { const exceedsEngineerThreshold = Math.abs(change.amount) >= EngineerThreshold; @@ -85,16 +82,12 @@ export function getPreviousPeriodTotalCost( duration: Duration, inclusiveEndDate: string, ): number { - const dayjsDuration = dayjs.duration(duration); + const luxonDuration = LuxonDuration.fromISO(duration); const startDate = inclusiveStartDateOf(duration, inclusiveEndDate); - // dayjs doesn't allow adding an ISO 8601 period to dates. - const [amount, type]: [number, OpUnitType] = dayjsDuration.days() - ? [dayjsDuration.days(), 'day'] - : [dayjsDuration.months(), 'month']; - const nextPeriodStart = dayjs(startDate).add(amount, type); + const nextPeriodStart = DateTime.fromISO(startDate).plus(luxonDuration); // Add up costs that incurred before the start of the next period. return aggregation.reduce((acc, costByDate) => { - return dayjs(costByDate.date).isBefore(nextPeriodStart) + return DateTime.fromISO(costByDate.date) < nextPeriodStart ? acc + costByDate.amount : acc; }, 0); diff --git a/plugins/cost-insights/src/utils/duration.ts b/plugins/cost-insights/src/utils/duration.ts index 17d0661c2b..6d7c8d4765 100644 --- a/plugins/cost-insights/src/utils/duration.ts +++ b/plugins/cost-insights/src/utils/duration.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import moment from 'moment'; +import { DateTime, Duration as LuxonDuration } from 'luxon'; import { Duration, DEFAULT_DATE_FORMAT } from '../types'; import { assertNever } from './assert'; @@ -34,14 +34,18 @@ export function inclusiveStartDateOf( case Duration.P7D: case Duration.P30D: case Duration.P90D: - return moment(inclusiveEndDate) - .subtract(moment.duration(duration).add(moment.duration(duration))) - .format(DEFAULT_DATE_FORMAT); + return DateTime.fromISO(inclusiveEndDate) + .minus( + LuxonDuration.fromISO(duration).plus(LuxonDuration.fromISO(duration)), + ) + .toFormat(DEFAULT_DATE_FORMAT); case Duration.P3M: - return moment(inclusiveEndDate) + return DateTime.fromISO(inclusiveEndDate) .startOf('quarter') - .subtract(moment.duration(duration).add(moment.duration(duration))) - .format(DEFAULT_DATE_FORMAT); + .minus( + LuxonDuration.fromISO(duration).plus(LuxonDuration.fromISO(duration)), + ) + .toFormat(DEFAULT_DATE_FORMAT); default: return assertNever(duration); } @@ -55,11 +59,13 @@ export function exclusiveEndDateOf( case Duration.P7D: case Duration.P30D: case Duration.P90D: - return moment(inclusiveEndDate).add(1, 'day').format(DEFAULT_DATE_FORMAT); + return DateTime.fromISO(inclusiveEndDate) + .plus({ days: 1 }) + .toFormat(DEFAULT_DATE_FORMAT); case Duration.P3M: - return moment(quarterEndDate(inclusiveEndDate)) - .add(1, 'day') - .format(DEFAULT_DATE_FORMAT); + return DateTime.fromISO(quarterEndDate(inclusiveEndDate)) + .plus({ days: 1 }) + .toFormat(DEFAULT_DATE_FORMAT); default: return assertNever(duration); } @@ -69,9 +75,9 @@ export function inclusiveEndDateOf( duration: Duration, inclusiveEndDate: string, ): string { - return moment(exclusiveEndDateOf(duration, inclusiveEndDate)) - .subtract(1, 'day') - .format(DEFAULT_DATE_FORMAT); + return DateTime.fromISO(exclusiveEndDateOf(duration, inclusiveEndDate)) + .minus({ days: 1 }) + .toFormat(DEFAULT_DATE_FORMAT); } // https://en.wikipedia.org/wiki/ISO_8601#Repeating_intervals @@ -87,13 +93,13 @@ export function intervalsOf( } export function quarterEndDate(inclusiveEndDate: string): string { - const endDate = moment(inclusiveEndDate); - const endOfQuarter = endDate.endOf('quarter').format(DEFAULT_DATE_FORMAT); + const endDate = DateTime.fromISO(inclusiveEndDate); + const endOfQuarter = endDate.endOf('quarter').toFormat(DEFAULT_DATE_FORMAT); if (endOfQuarter === inclusiveEndDate) { - return endDate.format(DEFAULT_DATE_FORMAT); + return endDate.toFormat(DEFAULT_DATE_FORMAT); } return endDate .startOf('quarter') - .subtract(1, 'day') - .format(DEFAULT_DATE_FORMAT); + .minus({ days: 1 }) + .toFormat(DEFAULT_DATE_FORMAT); } diff --git a/plugins/cost-insights/src/utils/formatters.ts b/plugins/cost-insights/src/utils/formatters.ts index 7733046165..70782123cd 100644 --- a/plugins/cost-insights/src/utils/formatters.ts +++ b/plugins/cost-insights/src/utils/formatters.ts @@ -14,7 +14,7 @@ * limitations under the License. */ -import moment from 'moment'; +import { DateTime, Duration as LuxonDuration } from 'luxon'; import pluralize from 'pluralize'; import { ChangeStatistic, Duration } from '../types'; import { inclusiveEndDateOf, inclusiveStartDateOf } from '../utils/duration'; @@ -67,9 +67,11 @@ export const monthOf = (date: string): string => { }; export const quarterOf = (date: string): string => { - // Supports formatting YYYY-MM-DD and YYYY-[Q]Q returned in alerts - const d = moment(date).isValid() ? moment(date) : moment(date, 'YYYY-[Q]Q'); - return d.format('[Q]Q YYYY'); + // Supports formatting yyyy-LL-dd and yyyy-'Q'q returned in alerts + const d = DateTime.fromISO(date).isValid + ? DateTime.fromISO(date) + : DateTime.fromFormat(date, "yyyy-'Q'q"); + return d.toFormat("'Q'q yyyy"); }; export function formatCurrency(amount: number, currency?: string): string { @@ -100,12 +102,12 @@ export function formatPercent(n: number): string { } export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string) { - const start = moment( + const start = DateTime.fromISO( inclusiveStartDateOf(Duration.P3M, inclusiveEndDate), - ).format('[Q]Q YYYY'); - const end = moment(inclusiveEndDateOf(Duration.P3M, inclusiveEndDate)).format( - '[Q]Q YYYY', - ); + ).toFormat("'Q'q yyyy"); + const end = DateTime.fromISO( + inclusiveEndDateOf(Duration.P3M, inclusiveEndDate), + ).toFormat("'Q'q yyyy"); return `${start} vs ${end}`; } @@ -116,7 +118,7 @@ const formatRelativePeriod = ( ): string => { const periodStart = isEndDate ? inclusiveStartDateOf(duration, date) : date; const periodEnd = isEndDate ? date : inclusiveEndDateOf(duration, date); - const days = moment.duration(duration).asDays(); + const days = LuxonDuration.fromISO(duration).days; if (![periodStart, periodEnd].includes(date)) { throw new Error(`Invalid relative date ${date} for duration ${duration}`); } From 0b4d00687c9557324ecd4766b445a8579839ad18 Mon Sep 17 00:00:00 2001 From: Vannarath Poeu Date: Tue, 22 Jun 2021 20:17:51 +0800 Subject: [PATCH 37/89] Added changeset Signed-off-by: Vannarath Poeu --- .changeset/nasty-crews-boil.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/nasty-crews-boil.md diff --git a/.changeset/nasty-crews-boil.md b/.changeset/nasty-crews-boil.md new file mode 100644 index 0000000000..80919a9918 --- /dev/null +++ b/.changeset/nasty-crews-boil.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-cost-insights': patch +--- + +Replaced moment and dayjs with luxon From 4c979f3e15da61620de523e3caaab82096bd62d5 Mon Sep 17 00:00:00 2001 From: Vannarath Poeu Date: Thu, 24 Jun 2021 09:58:05 +0800 Subject: [PATCH 38/89] Updated changeset description and vocab Signed-off-by: Vannarath Poeu --- .changeset/nasty-crews-boil.md | 2 +- .github/styles/vocab.txt | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/.changeset/nasty-crews-boil.md b/.changeset/nasty-crews-boil.md index 80919a9918..5b4108c972 100644 --- a/.changeset/nasty-crews-boil.md +++ b/.changeset/nasty-crews-boil.md @@ -2,4 +2,4 @@ '@backstage/plugin-cost-insights': patch --- -Replaced moment and dayjs with luxon +Replaced moment and dayjs with Luxon diff --git a/.github/styles/vocab.txt b/.github/styles/vocab.txt index 9350a73120..dcad0952f1 100644 --- a/.github/styles/vocab.txt +++ b/.github/styles/vocab.txt @@ -52,6 +52,7 @@ cookiecutter css Datadog dataflow +dayjs deadnaming debounce Debounce From 3a2993504ee422eca13ccd025babd6695e1f7e37 Mon Sep 17 00:00:00 2001 From: Vannarath Poeu Date: Thu, 24 Jun 2021 10:06:55 +0800 Subject: [PATCH 39/89] Regenerated api-report Signed-off-by: Vannarath Poeu --- plugins/cost-insights/api-report.md | 394 +++++++++++++++++++++++++++- 1 file changed, 393 insertions(+), 1 deletion(-) diff --git a/plugins/cost-insights/api-report.md b/plugins/cost-insights/api-report.md index d21159a845..9092791d9b 100644 --- a/plugins/cost-insights/api-report.md +++ b/plugins/cost-insights/api-report.md @@ -8,6 +8,7 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePalette } from '@backstage/theme'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; +import { ClassNameMap } from '@material-ui/styles'; import { ContentRenderer } from 'recharts'; import { Dispatch } from 'react'; import { ForwardRefExoticComponent } from 'react'; @@ -18,9 +19,16 @@ import { RechartsFunction } from 'recharts'; import { RefAttributes } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SetStateAction } from 'react'; +import { TooltipPayload } from 'recharts'; import { TooltipProps } from 'recharts'; import { TypographyProps } from '@material-ui/core'; +// @public (undocumented) +export const aggregationSort: (a: DateAggregation, b: DateAggregation) => number; + +// @public (undocumented) +export const aggregationSum: (aggregation: DateAggregation[]) => number; + // @public export type Alert = { title: string | JSX.Element; @@ -125,6 +133,12 @@ export enum AlertStatus { Snoozed = "snoozed" } +// @public (undocumented) +export function assertAlways(argument: T | undefined): T; + +// @public (undocumented) +export function assertNever(x: never): never; + // @public (undocumented) export const BarChart: ({ resources, responsive, displayAmount, options, tooltip, onClick, onMouseMove, }: BarChartProps) => JSX.Element; @@ -194,6 +208,9 @@ export type BarChartTooltipProps = { actions?: ReactNode; }; +// @public (undocumented) +export function brighten(color: string, coefficient?: number): string; + // @public (undocumented) export interface ChangeStatistic { // (undocumented) @@ -218,6 +235,9 @@ export type ChartData = { [key: string]: number; }; +// @public (undocumented) +export function choose([savings, excess]: [T, T], change: ChangeStatistic): T; + // @public (undocumented) export interface Cost { // (undocumented) @@ -232,6 +252,9 @@ export interface Cost { trendline?: Trendline; } +// @public (undocumented) +export const costFormatter: Intl.NumberFormat; + // @public (undocumented) export const CostGrowth: ({ change, duration }: CostGrowthProps) => JSX.Element; @@ -265,9 +288,15 @@ export type CostInsightsApi = { // @public (undocumented) export const costInsightsApiRef: ApiRef; +// @public (undocumented) +export const costInsightsDarkTheme: CostInsightsThemeOptions; + // @public (undocumented) export const CostInsightsLabelDataflowInstructionsPage: () => JSX.Element; +// @public (undocumented) +export const costInsightsLightTheme: CostInsightsThemeOptions; + // @public (undocumented) export const CostInsightsPage: () => JSX.Element; @@ -317,6 +346,9 @@ export interface Currency { unit: string; } +// @public (undocumented) +export const currencyFormatter: Intl.NumberFormat; + // @public (undocumented) export enum CurrencyType { // (undocumented) @@ -346,7 +378,40 @@ export type DateAggregation = { }; // @public (undocumented) -export const DEFAULT_DATE_FORMAT = "YYYY-MM-DD"; +export const dateFormatter: Intl.DateTimeFormat; + +// @public (undocumented) +export const DEFAULT_DATE_FORMAT = "yyyy-LL-dd"; + +// @public (undocumented) +export const DEFAULT_DURATION = Duration.P30D; + +// @public (undocumented) +export const defaultCurrencies: Currency[]; + +// @public (undocumented) +export enum DefaultLoadingAction { + // (undocumented) + CostInsightsAlerts = "cost-insights-alerts", + // (undocumented) + CostInsightsInitial = "cost-insights-initial", + // (undocumented) + CostInsightsPage = "cost-insights-page", + // (undocumented) + CostInsightsProducts = "cost-insights-products", + // (undocumented) + LastCompleteBillingDate = "billing-date", + // (undocumented) + UserGroups = "user-groups" +} + +// @public (undocumented) +export enum DefaultNavigation { + // (undocumented) + AlertInsightsHeader = "alert-insights-header", + // (undocumented) + CostOverviewCard = "cost-overview-card" +} // @public export enum Duration { @@ -395,11 +460,82 @@ export class ExampleCostInsightsClient implements CostInsightsApi { getUserGroups(userId: string): Promise; } +// @public (undocumented) +export function exclusiveEndDateOf(duration: Duration, inclusiveEndDate: string): string; + +// @public (undocumented) +export function findAlways(collection: T[], callback: (el: T) => boolean): T; + +// @public (undocumented) +export function findAnyKey(record: Record | undefined): string | undefined; + +// @public (undocumented) +export function formatChange(change: ChangeStatistic): string; + +// @public (undocumented) +export function formatCurrency(amount: number, currency?: string): string; + +// @public (undocumented) +export function formatGraphValue(value: number, format?: string): string; + +// @public (undocumented) +export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string): string; + +// @public (undocumented) +export function formatPercent(n: number): string; + +// @public (undocumented) +export function formatPeriod(duration: Duration, date: string, isEndDate: boolean): string; + +// @public (undocumented) +export function getComparedChange(dailyCost: Cost, metricData: MetricData, duration: Duration, lastCompleteBillingDate: string): ChangeStatistic; + +// @public (undocumented) +export const getDefaultNavigationItems: (alerts: number) => NavigationItem[]; + +// @public (undocumented) +export function getDefaultPageFilters(groups: Group[]): PageFilters; + +// @public (undocumented) +export const getDefaultState: (loadingActions: string[]) => Loading; + +// @public (undocumented) +export function getIcon(icon?: string): JSX.Element; + +// @public (undocumented) +export const getInitialPageState: (groups: Group[], queryParams?: Partial) => { + group: Maybe; + project: Maybe; + duration: Duration; + metric: string | null; +}; + +// @public (undocumented) +export const getInitialProductState: (config: ConfigContextProps) => { + productType: string; + duration: Duration; +}[]; + +// @public (undocumented) +export function getPreviousPeriodTotalCost(aggregation: DateAggregation[], duration: Duration, inclusiveEndDate: string): number; + +// @public (undocumented) +export const getResetState: (loadingActions: string[]) => Loading; + +// @public (undocumented) +export const getResetStateWithoutInitial: (loadingActions: string[]) => Loading; + // @public (undocumented) export type Group = { id: string; }; +// @public (undocumented) +export function groupByDate(acc: Record, entry: DateAggregation): Record; + +// @public (undocumented) +export function growthOf(change: ChangeStatistic): GrowthType; + // @public (undocumented) export enum GrowthType { // (undocumented) @@ -432,6 +568,39 @@ export enum IconType { Storage = "storage" } +// @public (undocumented) +export function inclusiveEndDateOf(duration: Duration, inclusiveEndDate: string): string; + +// @public +export function inclusiveStartDateOf(duration: Duration, inclusiveEndDate: string): string; + +// @public (undocumented) +export const indefiniteArticleOf: (articles: [string, string], word: string) => string; + +// @public (undocumented) +export const INITIAL_LOADING_ACTIONS: DefaultLoadingAction[]; + +// @public (undocumented) +export const initialStatesOf: (products: Product[], responses: Array>) => ProductState[]; + +// @public (undocumented) +export function intervalsOf(duration: Duration, inclusiveEndDate: string, repeating?: number): string; + +// @public (undocumented) +export const isInvalid: ({ label, payload }: TooltipProps) => boolean; + +// @public (undocumented) +export const isLabeled: (data?: Record<"activeLabel", string | undefined> | undefined) => boolean | "" | undefined; + +// @public (undocumented) +export function isNull(value: any): boolean; + +// @public (undocumented) +export function isUndefined(value: any): value is undefined; + +// @public (undocumented) +export const isUnlabeled: (data?: Record<"activeLabel", string | undefined> | undefined) => boolean; + // @public (undocumented) export const LegendItem: ({ title, tooltipText, markerColor, children, }: PropsWithChildren) => JSX.Element; @@ -442,6 +611,9 @@ export type LegendItemProps = { markerColor?: string; }; +// @public (undocumented) +export const lengthyCurrencyFormatter: Intl.NumberFormat; + // @public (undocumented) export type Loading = Record; @@ -473,6 +645,28 @@ export const MockConfigProvider: ({ children, ...context }: MockConfigProviderPr // @public (undocumented) export const MockCurrencyProvider: ({ children, ...context }: MockCurrencyProviderProps) => JSX.Element; +// @public (undocumented) +export const monthFormatter: Intl.DateTimeFormat; + +// @public (undocumented) +export const monthOf: (date: string) => string; + +// @public (undocumented) +export type NavigationItem = { + navigation: string; + icon: JSX.Element; + title: string; +}; + +// @public (undocumented) +export function notEmpty(value: TValue | null | undefined): value is TValue; + +// @public (undocumented) +export const numberFormatter: Intl.NumberFormat; + +// @public (undocumented) +export const overviewGraphTickFormatter: (millis: string | number) => string; + // @public (undocumented) export interface PageFilters { // (undocumented) @@ -485,6 +679,15 @@ export interface PageFilters { project: Maybe; } +// @public (undocumented) +export const parse: (queryString: string) => Partial; + +// @public (undocumented) +export type Period = { + periodStart: string; + periodEnd: string; +}; + // @public (undocumented) export interface Product { // (undocumented) @@ -512,6 +715,13 @@ export interface ProductPeriod { productType: string; } +// @public (undocumented) +export type ProductState = { + product: Product; + entity: Maybe; + duration: Duration; +}; + // @public (undocumented) export interface Project { // (undocumented) @@ -551,6 +761,15 @@ export interface ProjectGrowthData { project: string; } +// @public (undocumented) +export function quarterEndDate(inclusiveEndDate: string): string; + +// @public (undocumented) +export const quarterOf: (date: string) => string; + +// @public (undocumented) +export const rateOf: (cost: number, duration: Duration) => number; + // @public (undocumented) export interface ResourceData { // (undocumented) @@ -561,6 +780,40 @@ export interface ResourceData { previous: number; } +// @public (undocumented) +export const resourceOf: (entity: Entity | AlertCost) => ResourceData; + +// @public (undocumented) +export const resourceSort: (a: ResourceData, b: ResourceData) => number; + +// @public (undocumented) +export const ScrollAnchor: ({ id, left, top, block, inline, behavior, }: ScrollAnchorProps) => JSX.Element; + +// @public (undocumented) +export interface ScrollAnchorProps extends ScrollIntoViewOptions { + // (undocumented) + id: ScrollTo; + // (undocumented) + left?: number; + // (undocumented) + top?: number; +} + +// @public (undocumented) +export const settledResponseOf: (responses: PromiseSettledResult[]) => Array>; + +// @public (undocumented) +export const stringify: (queryParams: Partial) => string; + +// @public (undocumented) +export const titleOf: (label?: string | number | undefined) => string; + +// @public (undocumented) +export function toDataMax(metric: string, data: ChartData[]): number; + +// @public (undocumented) +export function toMaxCost(acc: ChartData, entry: ChartData): ChartData; + // @public (undocumented) export type TooltipItem = { fill: string; @@ -568,6 +821,19 @@ export type TooltipItem = { value: string; }; +// @public (undocumented) +export const tooltipItemOf: (payload: TooltipPayload) => { + label: string; + value: string; + fill: string; +} | null; + +// @public (undocumented) +export function totalAggregationSort(a: ProductState, b: ProductState): number; + +// @public (undocumented) +export function trendFrom(trendline: Trendline, date: number): number; + // @public (undocumented) export type Trendline = { slope: number; @@ -615,6 +881,132 @@ export interface UnlabeledDataflowData { unlabeledCost: number; } +// @public (undocumented) +export const useActionItemCardStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useAlertCardActionHeaderStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useAlertDialogStyles: (props?: any) => ClassNameMap<"radio" | "icon" | "content" | "actions">; + +// @public (undocumented) +export const useAlertInsightsSectionStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useAlertStatusSummaryButtonStyles: (props?: any) => ClassNameMap<"icon" | "clicked">; + +// @public (undocumented) +export const useBackdropStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useBarChartLabelStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useBarChartLayoutStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useBarChartStepperButtonStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useBarChartStepperStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useBarChartStyles: (theme: CostInsightsTheme) => { + axis: { + fill: string; + }; + barChart: { + margin: { + left: number; + right: number; + }; + }; + cartesianGrid: { + stroke: string; + }; + cursor: { + fill: string; + fillOpacity: number; + }; + container: { + height: number; + width: number; + }; + infoIcon: { + marginLeft: number; + fontSize: string; + }; + xAxis: { + height: number; + }; +}; + +// @public (undocumented) +export const useCostGrowthLegendStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useCostGrowthStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useCostInsightsStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useCostInsightsTabsStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useCostOverviewStyles: (theme: CostInsightsTheme) => { + axis: { + fill: string; + }; + container: { + height: number; + width: number; + }; + cartesianGrid: { + stroke: string; + }; + chart: { + margin: { + right: number; + top: number; + }; + }; + yAxis: { + width: number; + }; +}; + +// @public (undocumented) +export const useEntityDialogStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useNavigationStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useOverviewTabsStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useProductInsightsCardStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useProductInsightsChartStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useSelectStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useSubtleTypographyStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const useTooltipStyles: (props?: any) => ClassNameMap; + +// @public (undocumented) +export const validate: (queryString: string) => Promise; + +// @public (undocumented) +export function validateMetrics(metrics: Metric[]): void; + // (No @packageDocumentation comment for this package) From a61310b1dc7199f26ca3e21f52fe2749fd1ae0fa Mon Sep 17 00:00:00 2001 From: Vannarath Poeu Date: Thu, 24 Jun 2021 18:19:07 +0800 Subject: [PATCH 40/89] Regenerated api-report after rebase Signed-off-by: Vannarath Poeu --- plugins/cost-insights/api-report.md | 392 ---------------------------- 1 file changed, 392 deletions(-) diff --git a/plugins/cost-insights/api-report.md b/plugins/cost-insights/api-report.md index 9092791d9b..97dd33e5ad 100644 --- a/plugins/cost-insights/api-report.md +++ b/plugins/cost-insights/api-report.md @@ -8,7 +8,6 @@ import { ApiRef } from '@backstage/core-plugin-api'; import { BackstagePalette } from '@backstage/theme'; import { BackstagePlugin } from '@backstage/core-plugin-api'; import { BackstageTheme } from '@backstage/theme'; -import { ClassNameMap } from '@material-ui/styles'; import { ContentRenderer } from 'recharts'; import { Dispatch } from 'react'; import { ForwardRefExoticComponent } from 'react'; @@ -19,16 +18,9 @@ import { RechartsFunction } from 'recharts'; import { RefAttributes } from 'react'; import { RouteRef } from '@backstage/core-plugin-api'; import { SetStateAction } from 'react'; -import { TooltipPayload } from 'recharts'; import { TooltipProps } from 'recharts'; import { TypographyProps } from '@material-ui/core'; -// @public (undocumented) -export const aggregationSort: (a: DateAggregation, b: DateAggregation) => number; - -// @public (undocumented) -export const aggregationSum: (aggregation: DateAggregation[]) => number; - // @public export type Alert = { title: string | JSX.Element; @@ -133,12 +125,6 @@ export enum AlertStatus { Snoozed = "snoozed" } -// @public (undocumented) -export function assertAlways(argument: T | undefined): T; - -// @public (undocumented) -export function assertNever(x: never): never; - // @public (undocumented) export const BarChart: ({ resources, responsive, displayAmount, options, tooltip, onClick, onMouseMove, }: BarChartProps) => JSX.Element; @@ -208,9 +194,6 @@ export type BarChartTooltipProps = { actions?: ReactNode; }; -// @public (undocumented) -export function brighten(color: string, coefficient?: number): string; - // @public (undocumented) export interface ChangeStatistic { // (undocumented) @@ -235,9 +218,6 @@ export type ChartData = { [key: string]: number; }; -// @public (undocumented) -export function choose([savings, excess]: [T, T], change: ChangeStatistic): T; - // @public (undocumented) export interface Cost { // (undocumented) @@ -252,9 +232,6 @@ export interface Cost { trendline?: Trendline; } -// @public (undocumented) -export const costFormatter: Intl.NumberFormat; - // @public (undocumented) export const CostGrowth: ({ change, duration }: CostGrowthProps) => JSX.Element; @@ -288,15 +265,9 @@ export type CostInsightsApi = { // @public (undocumented) export const costInsightsApiRef: ApiRef; -// @public (undocumented) -export const costInsightsDarkTheme: CostInsightsThemeOptions; - // @public (undocumented) export const CostInsightsLabelDataflowInstructionsPage: () => JSX.Element; -// @public (undocumented) -export const costInsightsLightTheme: CostInsightsThemeOptions; - // @public (undocumented) export const CostInsightsPage: () => JSX.Element; @@ -346,9 +317,6 @@ export interface Currency { unit: string; } -// @public (undocumented) -export const currencyFormatter: Intl.NumberFormat; - // @public (undocumented) export enum CurrencyType { // (undocumented) @@ -377,42 +345,9 @@ export type DateAggregation = { amount: number; }; -// @public (undocumented) -export const dateFormatter: Intl.DateTimeFormat; - // @public (undocumented) export const DEFAULT_DATE_FORMAT = "yyyy-LL-dd"; -// @public (undocumented) -export const DEFAULT_DURATION = Duration.P30D; - -// @public (undocumented) -export const defaultCurrencies: Currency[]; - -// @public (undocumented) -export enum DefaultLoadingAction { - // (undocumented) - CostInsightsAlerts = "cost-insights-alerts", - // (undocumented) - CostInsightsInitial = "cost-insights-initial", - // (undocumented) - CostInsightsPage = "cost-insights-page", - // (undocumented) - CostInsightsProducts = "cost-insights-products", - // (undocumented) - LastCompleteBillingDate = "billing-date", - // (undocumented) - UserGroups = "user-groups" -} - -// @public (undocumented) -export enum DefaultNavigation { - // (undocumented) - AlertInsightsHeader = "alert-insights-header", - // (undocumented) - CostOverviewCard = "cost-overview-card" -} - // @public export enum Duration { // (undocumented) @@ -460,82 +395,11 @@ export class ExampleCostInsightsClient implements CostInsightsApi { getUserGroups(userId: string): Promise; } -// @public (undocumented) -export function exclusiveEndDateOf(duration: Duration, inclusiveEndDate: string): string; - -// @public (undocumented) -export function findAlways(collection: T[], callback: (el: T) => boolean): T; - -// @public (undocumented) -export function findAnyKey(record: Record | undefined): string | undefined; - -// @public (undocumented) -export function formatChange(change: ChangeStatistic): string; - -// @public (undocumented) -export function formatCurrency(amount: number, currency?: string): string; - -// @public (undocumented) -export function formatGraphValue(value: number, format?: string): string; - -// @public (undocumented) -export function formatLastTwoLookaheadQuarters(inclusiveEndDate: string): string; - -// @public (undocumented) -export function formatPercent(n: number): string; - -// @public (undocumented) -export function formatPeriod(duration: Duration, date: string, isEndDate: boolean): string; - -// @public (undocumented) -export function getComparedChange(dailyCost: Cost, metricData: MetricData, duration: Duration, lastCompleteBillingDate: string): ChangeStatistic; - -// @public (undocumented) -export const getDefaultNavigationItems: (alerts: number) => NavigationItem[]; - -// @public (undocumented) -export function getDefaultPageFilters(groups: Group[]): PageFilters; - -// @public (undocumented) -export const getDefaultState: (loadingActions: string[]) => Loading; - -// @public (undocumented) -export function getIcon(icon?: string): JSX.Element; - -// @public (undocumented) -export const getInitialPageState: (groups: Group[], queryParams?: Partial) => { - group: Maybe; - project: Maybe; - duration: Duration; - metric: string | null; -}; - -// @public (undocumented) -export const getInitialProductState: (config: ConfigContextProps) => { - productType: string; - duration: Duration; -}[]; - -// @public (undocumented) -export function getPreviousPeriodTotalCost(aggregation: DateAggregation[], duration: Duration, inclusiveEndDate: string): number; - -// @public (undocumented) -export const getResetState: (loadingActions: string[]) => Loading; - -// @public (undocumented) -export const getResetStateWithoutInitial: (loadingActions: string[]) => Loading; - // @public (undocumented) export type Group = { id: string; }; -// @public (undocumented) -export function groupByDate(acc: Record, entry: DateAggregation): Record; - -// @public (undocumented) -export function growthOf(change: ChangeStatistic): GrowthType; - // @public (undocumented) export enum GrowthType { // (undocumented) @@ -568,39 +432,6 @@ export enum IconType { Storage = "storage" } -// @public (undocumented) -export function inclusiveEndDateOf(duration: Duration, inclusiveEndDate: string): string; - -// @public -export function inclusiveStartDateOf(duration: Duration, inclusiveEndDate: string): string; - -// @public (undocumented) -export const indefiniteArticleOf: (articles: [string, string], word: string) => string; - -// @public (undocumented) -export const INITIAL_LOADING_ACTIONS: DefaultLoadingAction[]; - -// @public (undocumented) -export const initialStatesOf: (products: Product[], responses: Array>) => ProductState[]; - -// @public (undocumented) -export function intervalsOf(duration: Duration, inclusiveEndDate: string, repeating?: number): string; - -// @public (undocumented) -export const isInvalid: ({ label, payload }: TooltipProps) => boolean; - -// @public (undocumented) -export const isLabeled: (data?: Record<"activeLabel", string | undefined> | undefined) => boolean | "" | undefined; - -// @public (undocumented) -export function isNull(value: any): boolean; - -// @public (undocumented) -export function isUndefined(value: any): value is undefined; - -// @public (undocumented) -export const isUnlabeled: (data?: Record<"activeLabel", string | undefined> | undefined) => boolean; - // @public (undocumented) export const LegendItem: ({ title, tooltipText, markerColor, children, }: PropsWithChildren) => JSX.Element; @@ -611,9 +442,6 @@ export type LegendItemProps = { markerColor?: string; }; -// @public (undocumented) -export const lengthyCurrencyFormatter: Intl.NumberFormat; - // @public (undocumented) export type Loading = Record; @@ -645,28 +473,6 @@ export const MockConfigProvider: ({ children, ...context }: MockConfigProviderPr // @public (undocumented) export const MockCurrencyProvider: ({ children, ...context }: MockCurrencyProviderProps) => JSX.Element; -// @public (undocumented) -export const monthFormatter: Intl.DateTimeFormat; - -// @public (undocumented) -export const monthOf: (date: string) => string; - -// @public (undocumented) -export type NavigationItem = { - navigation: string; - icon: JSX.Element; - title: string; -}; - -// @public (undocumented) -export function notEmpty(value: TValue | null | undefined): value is TValue; - -// @public (undocumented) -export const numberFormatter: Intl.NumberFormat; - -// @public (undocumented) -export const overviewGraphTickFormatter: (millis: string | number) => string; - // @public (undocumented) export interface PageFilters { // (undocumented) @@ -679,15 +485,6 @@ export interface PageFilters { project: Maybe; } -// @public (undocumented) -export const parse: (queryString: string) => Partial; - -// @public (undocumented) -export type Period = { - periodStart: string; - periodEnd: string; -}; - // @public (undocumented) export interface Product { // (undocumented) @@ -715,13 +512,6 @@ export interface ProductPeriod { productType: string; } -// @public (undocumented) -export type ProductState = { - product: Product; - entity: Maybe; - duration: Duration; -}; - // @public (undocumented) export interface Project { // (undocumented) @@ -761,15 +551,6 @@ export interface ProjectGrowthData { project: string; } -// @public (undocumented) -export function quarterEndDate(inclusiveEndDate: string): string; - -// @public (undocumented) -export const quarterOf: (date: string) => string; - -// @public (undocumented) -export const rateOf: (cost: number, duration: Duration) => number; - // @public (undocumented) export interface ResourceData { // (undocumented) @@ -780,40 +561,6 @@ export interface ResourceData { previous: number; } -// @public (undocumented) -export const resourceOf: (entity: Entity | AlertCost) => ResourceData; - -// @public (undocumented) -export const resourceSort: (a: ResourceData, b: ResourceData) => number; - -// @public (undocumented) -export const ScrollAnchor: ({ id, left, top, block, inline, behavior, }: ScrollAnchorProps) => JSX.Element; - -// @public (undocumented) -export interface ScrollAnchorProps extends ScrollIntoViewOptions { - // (undocumented) - id: ScrollTo; - // (undocumented) - left?: number; - // (undocumented) - top?: number; -} - -// @public (undocumented) -export const settledResponseOf: (responses: PromiseSettledResult[]) => Array>; - -// @public (undocumented) -export const stringify: (queryParams: Partial) => string; - -// @public (undocumented) -export const titleOf: (label?: string | number | undefined) => string; - -// @public (undocumented) -export function toDataMax(metric: string, data: ChartData[]): number; - -// @public (undocumented) -export function toMaxCost(acc: ChartData, entry: ChartData): ChartData; - // @public (undocumented) export type TooltipItem = { fill: string; @@ -821,19 +568,6 @@ export type TooltipItem = { value: string; }; -// @public (undocumented) -export const tooltipItemOf: (payload: TooltipPayload) => { - label: string; - value: string; - fill: string; -} | null; - -// @public (undocumented) -export function totalAggregationSort(a: ProductState, b: ProductState): number; - -// @public (undocumented) -export function trendFrom(trendline: Trendline, date: number): number; - // @public (undocumented) export type Trendline = { slope: number; @@ -881,132 +615,6 @@ export interface UnlabeledDataflowData { unlabeledCost: number; } -// @public (undocumented) -export const useActionItemCardStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useAlertCardActionHeaderStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useAlertDialogStyles: (props?: any) => ClassNameMap<"radio" | "icon" | "content" | "actions">; - -// @public (undocumented) -export const useAlertInsightsSectionStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useAlertStatusSummaryButtonStyles: (props?: any) => ClassNameMap<"icon" | "clicked">; - -// @public (undocumented) -export const useBackdropStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useBarChartLabelStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useBarChartLayoutStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useBarChartStepperButtonStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useBarChartStepperStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useBarChartStyles: (theme: CostInsightsTheme) => { - axis: { - fill: string; - }; - barChart: { - margin: { - left: number; - right: number; - }; - }; - cartesianGrid: { - stroke: string; - }; - cursor: { - fill: string; - fillOpacity: number; - }; - container: { - height: number; - width: number; - }; - infoIcon: { - marginLeft: number; - fontSize: string; - }; - xAxis: { - height: number; - }; -}; - -// @public (undocumented) -export const useCostGrowthLegendStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useCostGrowthStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useCostInsightsStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useCostInsightsTabsStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useCostOverviewStyles: (theme: CostInsightsTheme) => { - axis: { - fill: string; - }; - container: { - height: number; - width: number; - }; - cartesianGrid: { - stroke: string; - }; - chart: { - margin: { - right: number; - top: number; - }; - }; - yAxis: { - width: number; - }; -}; - -// @public (undocumented) -export const useEntityDialogStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useNavigationStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useOverviewTabsStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useProductInsightsCardStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useProductInsightsChartStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useSelectStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useSubtleTypographyStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const useTooltipStyles: (props?: any) => ClassNameMap; - -// @public (undocumented) -export const validate: (queryString: string) => Promise; - -// @public (undocumented) -export function validateMetrics(metrics: Metric[]): void; - // (No @packageDocumentation comment for this package) From 27bc65bf11d540e79a5992d1eaf4e35ed9dcac0a Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Tue, 22 Jun 2021 17:29:26 +0200 Subject: [PATCH 41/89] packages: remove core and core-api Signed-off-by: Patrik Oldsberg --- packages/core-api/.eslintrc.js | 8 - packages/core-api/CHANGELOG.md | 390 ------------- packages/core-api/README.md | 12 - packages/core-api/package.json | 62 --- .../core-api/src/apis/definitions/AlertApi.ts | 44 -- .../src/apis/definitions/AppThemeApi.ts | 83 --- .../src/apis/definitions/ConfigApi.ts | 28 - .../src/apis/definitions/DiscoveryApi.ts | 47 -- .../core-api/src/apis/definitions/ErrorApi.ts | 68 --- .../src/apis/definitions/FeatureFlagsApi.ts | 86 --- .../src/apis/definitions/IdentityApi.ts | 56 -- .../src/apis/definitions/OAuthRequestApi.ts | 133 ----- .../src/apis/definitions/StorageApi.ts | 71 --- .../core-api/src/apis/definitions/auth.ts | 347 ------------ .../core-api/src/apis/definitions/index.ts | 33 -- .../AlertApi/AlertApiForwarder.ts | 33 -- .../apis/implementations/AlertApi/index.ts | 17 - .../AppThemeApi/AppThemeSelector.test.ts | 85 --- .../AppThemeApi/AppThemeSelector.ts | 75 --- .../apis/implementations/AppThemeApi/index.ts | 17 - .../apis/implementations/ConfigApi/index.ts | 17 - .../DiscoveryApi/UrlPatternDiscovery.test.ts | 84 --- .../DiscoveryApi/UrlPatternDiscovery.ts | 58 -- .../implementations/DiscoveryApi/index.ts | 17 - .../implementations/ErrorApi/ErrorAlerter.ts | 39 -- .../ErrorApi/ErrorApiForwarder.ts | 36 -- .../apis/implementations/ErrorApi/index.ts | 18 - .../LocalStorageFeatureFlags.test.tsx | 222 -------- .../LocalStorageFeatureFlags.tsx | 109 ---- .../implementations/FeatureFlagsApi/index.ts | 17 - .../OAuthRequestApi/MockOAuthApi.test.ts | 101 ---- .../OAuthRequestApi/MockOAuthApi.ts | 55 -- .../OAuthPendingRequests.test.ts | 96 ---- .../OAuthRequestApi/OAuthPendingRequests.ts | 126 ----- .../OAuthRequestManager.test.ts | 59 -- .../OAuthRequestApi/OAuthRequestManager.ts | 92 --- .../implementations/OAuthRequestApi/index.ts | 17 - .../StorageApi/WebStorage.test.ts | 173 ------ .../implementations/StorageApi/WebStorage.ts | 94 ---- .../apis/implementations/StorageApi/index.ts | 17 - .../implementations/auth/auth0/Auth0Auth.ts | 46 -- .../apis/implementations/auth/auth0/index.ts | 17 - .../auth/github/GithubAuth.test.ts | 29 - .../implementations/auth/github/GithubAuth.ts | 140 ----- .../apis/implementations/auth/github/index.ts | 18 - .../apis/implementations/auth/github/types.ts | 27 - .../auth/gitlab/GitlabAuth.test.ts | 50 -- .../implementations/auth/gitlab/GitlabAuth.ts | 45 -- .../apis/implementations/auth/gitlab/index.ts | 17 - .../auth/google/GoogleAuth.test.ts | 69 --- .../implementations/auth/google/GoogleAuth.ts | 68 --- .../apis/implementations/auth/google/index.ts | 17 - .../src/apis/implementations/auth/index.ts | 25 - .../auth/microsoft/MicrosoftAuth.ts | 52 -- .../implementations/auth/microsoft/index.ts | 17 - .../auth/oauth2/OAuth2.test.ts | 152 ----- .../implementations/auth/oauth2/OAuth2.ts | 179 ------ .../apis/implementations/auth/oauth2/index.ts | 18 - .../apis/implementations/auth/oauth2/types.ts | 28 - .../auth/okta/OktaAuth.test.ts | 61 -- .../implementations/auth/okta/OktaAuth.ts | 71 --- .../apis/implementations/auth/okta/index.ts | 17 - .../auth/onelogin/OneLoginAuth.ts | 82 --- .../implementations/auth/onelogin/index.ts | 17 - .../implementations/auth/saml/SamlAuth.ts | 98 ---- .../apis/implementations/auth/saml/index.ts | 16 - .../apis/implementations/auth/saml/types.ts | 22 - .../src/apis/implementations/auth/types.ts | 28 - .../src/apis/implementations/index.ts | 30 - packages/core-api/src/apis/index.ts | 19 - .../src/apis/system/ApiAggregator.test.ts | 46 -- .../core-api/src/apis/system/ApiAggregator.ts | 39 -- .../apis/system/ApiFactoryRegistry.test.ts | 91 --- .../src/apis/system/ApiFactoryRegistry.ts | 89 --- .../src/apis/system/ApiProvider.test.tsx | 212 ------- .../core-api/src/apis/system/ApiProvider.tsx | 131 ----- .../core-api/src/apis/system/ApiRef.test.ts | 51 -- packages/core-api/src/apis/system/ApiRef.ts | 57 -- .../src/apis/system/ApiRegistry.test.ts | 75 --- .../core-api/src/apis/system/ApiRegistry.ts | 69 --- .../src/apis/system/ApiResolver.test.ts | 266 --------- .../core-api/src/apis/system/ApiResolver.ts | 110 ---- packages/core-api/src/apis/system/helpers.ts | 49 -- packages/core-api/src/apis/system/index.ts | 23 - packages/core-api/src/apis/system/types.ts | 57 -- packages/core-api/src/app/App.test.tsx | 375 ------------- packages/core-api/src/app/App.tsx | 525 ------------------ packages/core-api/src/app/AppContext.test.tsx | 79 --- packages/core-api/src/app/AppContext.tsx | 67 --- packages/core-api/src/app/AppIdentity.ts | 85 --- .../core-api/src/app/AppThemeProvider.tsx | 97 ---- packages/core-api/src/app/index.ts | 18 - packages/core-api/src/app/types.ts | 256 --------- .../src/extensions/componentData.test.tsx | 118 ---- .../core-api/src/extensions/componentData.tsx | 84 --- .../src/extensions/extensions.test.tsx | 76 --- .../core-api/src/extensions/extensions.tsx | 141 ----- packages/core-api/src/extensions/index.ts | 22 - .../src/extensions/traversal.test.tsx | 104 ---- packages/core-api/src/extensions/traversal.ts | 129 ----- packages/core-api/src/icons/icons.tsx | 67 --- packages/core-api/src/icons/index.ts | 18 - packages/core-api/src/icons/types.ts | 34 -- packages/core-api/src/index.ts | 20 - .../DefaultAuthConnector.test.ts | 182 ------ .../lib/AuthConnector/DefaultAuthConnector.ts | 204 ------- .../lib/AuthConnector/DirectAuthConnector.ts | 77 --- .../AuthConnector/MockAuthConnector.test.ts | 35 -- .../lib/AuthConnector/MockAuthConnector.ts | 45 -- .../core-api/src/lib/AuthConnector/index.ts | 19 - .../core-api/src/lib/AuthConnector/types.ts | 30 - .../AuthSessionStore.test.ts | 137 ----- .../AuthSessionManager/AuthSessionStore.ts | 131 ----- .../RefreshingAuthSessionManager.test.ts | 168 ------ .../RefreshingAuthSessionManager.ts | 148 ----- .../AuthSessionManager/SessionStateTracker.ts | 40 -- .../StaticAuthSessionManager.test.ts | 97 ---- .../StaticAuthSessionManager.ts | 83 --- .../src/lib/AuthSessionManager/common.ts | 71 --- .../src/lib/AuthSessionManager/index.ts | 20 - .../src/lib/AuthSessionManager/types.ts | 57 -- .../core-api/src/lib/globalObject.test.ts | 90 --- packages/core-api/src/lib/globalObject.ts | 73 --- packages/core-api/src/lib/index.ts | 20 - packages/core-api/src/lib/loginPopup.test.ts | 218 -------- packages/core-api/src/lib/loginPopup.ts | 143 ----- packages/core-api/src/lib/subjects.test.ts | 178 ------ packages/core-api/src/lib/subjects.ts | 210 ------- .../core-api/src/lib/versionedValues.test.ts | 40 -- packages/core-api/src/lib/versionedValues.ts | 40 -- packages/core-api/src/plugin/Plugin.tsx | 99 ---- .../core-api/src/plugin/collectors.test.tsx | 106 ---- packages/core-api/src/plugin/collectors.ts | 32 -- packages/core-api/src/plugin/index.ts | 33 -- packages/core-api/src/plugin/types.ts | 121 ---- packages/core-api/src/private.ts | 17 - packages/core-api/src/public.ts | 23 - .../src/routing/ExternalRouteRef.test.ts | 119 ---- .../core-api/src/routing/ExternalRouteRef.ts | 37 -- .../core-api/src/routing/FlatRoutes.test.tsx | 111 ---- packages/core-api/src/routing/FlatRoutes.tsx | 87 --- .../core-api/src/routing/RouteRef.test.ts | 94 ---- packages/core-api/src/routing/RouteRef.ts | 44 -- .../src/routing/RouteResolver.test.ts | 293 ---------- .../core-api/src/routing/RouteResolver.ts | 223 -------- .../core-api/src/routing/SubRouteRef.test.ts | 134 ----- packages/core-api/src/routing/SubRouteRef.ts | 34 -- .../core-api/src/routing/collectors.test.tsx | 374 ------------- packages/core-api/src/routing/collectors.tsx | 172 ------ packages/core-api/src/routing/hooks.test.tsx | 405 -------------- packages/core-api/src/routing/hooks.tsx | 119 ---- packages/core-api/src/routing/index.ts | 30 - packages/core-api/src/routing/types.ts | 72 --- packages/core-api/src/routing/validation.ts | 56 -- packages/core-api/src/setupTests.ts | 18 - packages/core-api/src/types.ts | 73 --- packages/core/.eslintrc.js | 8 - packages/core/.npmrc | 1 - packages/core/CHANGELOG.md | 516 ----------------- packages/core/README.md | 12 - packages/core/config.d.ts | 113 ---- packages/core/package.json | 94 ---- .../core/src/api-wrappers/createApp.test.tsx | 117 ---- packages/core/src/api-wrappers/createApp.tsx | 167 ------ packages/core/src/api-wrappers/defaultApis.ts | 220 -------- packages/core/src/api-wrappers/index.ts | 17 - .../AlertDisplay/AlertDisplay.test.tsx | 107 ---- .../components/AlertDisplay/AlertDisplay.tsx | 76 --- .../core/src/components/AlertDisplay/index.ts | 17 - .../src/components/Avatar/Avatar.stories.tsx | 42 -- .../src/components/Avatar/Avatar.test.tsx | 27 - .../core/src/components/Avatar/Avatar.tsx | 59 -- packages/core/src/components/Avatar/index.ts | 16 - .../core/src/components/Avatar/util.test.ts | 37 -- packages/core/src/components/Avatar/utils.ts | 32 -- .../src/components/Button/Button.stories.tsx | 174 ------ .../src/components/Button/Button.test.tsx | 42 -- .../core/src/components/Button/Button.tsx | 32 -- packages/core/src/components/Button/index.ts | 17 - .../CheckboxTree/CheckboxTree.stories.tsx | 105 ---- .../CheckboxTree/CheckboxTree.test.tsx | 59 -- .../components/CheckboxTree/CheckboxTree.tsx | 358 ------------ .../src/components/CheckboxTree/index.tsx | 17 - .../core/src/components/Chip/Chip.stories.tsx | 43 -- .../CodeSnippet/CodeSnippet.stories.tsx | 94 ---- .../CodeSnippet/CodeSnippet.test.tsx | 74 --- .../components/CodeSnippet/CodeSnippet.tsx | 72 --- .../core/src/components/CodeSnippet/index.tsx | 17 - .../CopyTextButton/CopyTextButton.stories.tsx | 42 -- .../CopyTextButton/CopyTextButton.test.tsx | 85 --- .../CopyTextButton/CopyTextButton.tsx | 100 ---- .../src/components/CopyTextButton/index.tsx | 17 - .../DependencyGraph/DefaultLabel.tsx | 35 -- .../DependencyGraph/DefaultNode.tsx | 79 --- .../DependencyGraph.stories.tsx | 178 ------ .../DependencyGraph/DependencyGraph.test.tsx | 106 ---- .../DependencyGraph/DependencyGraph.tsx | 324 ----------- .../components/DependencyGraph/Edge.test.tsx | 100 ---- .../src/components/DependencyGraph/Edge.tsx | 122 ---- .../components/DependencyGraph/Node.test.tsx | 79 --- .../src/components/DependencyGraph/Node.tsx | 76 --- .../components/DependencyGraph/constants.ts | 21 - .../src/components/DependencyGraph/index.ts | 20 - .../src/components/DependencyGraph/types.ts | 88 --- .../src/components/Dialog/Dialog.stories.tsx | 127 ----- .../DismissableBanner.stories.tsx | 114 ---- .../DismissableBanner.test.tsx | 88 --- .../DismissableBanner/DismissableBanner.tsx | 135 ----- .../src/components/DismissableBanner/index.ts | 17 - .../src/components/Drawer/Drawer.stories.tsx | 171 ------ .../EmptyState/EmptyState.stories.tsx | 78 --- .../components/EmptyState/EmptyState.test.tsx | 39 -- .../src/components/EmptyState/EmptyState.tsx | 68 --- .../EmptyState/EmptyStateImage.test.tsx | 49 -- .../components/EmptyState/EmptyStateImage.tsx | 73 --- .../MissingAnnotationEmptyState.tsx | 86 --- .../EmptyState/assets/createComponent.svg | 1 - .../EmptyState/assets/missingAnnotation.svg | 1 - .../components/EmptyState/assets/noBuild.svg | 1 - .../EmptyState/assets/noInformation.svg | 1 - .../core/src/components/EmptyState/index.ts | 18 - .../FeatureCalloutCircular.test.tsx | 154 ----- .../FeatureCalloutCircular.tsx | 199 ------- .../src/components/FeatureDiscovery/index.ts | 17 - .../FeatureDiscovery/lib/usePortal.ts | 99 ---- .../FeatureDiscovery/lib/useShowCallout.ts | 58 -- .../HeaderIconLinkRow/HeaderIconLinkRow.tsx | 43 -- .../HeaderIconLinkRow/IconLinkVertical.tsx | 93 ---- .../src/components/HeaderIconLinkRow/index.ts | 19 - .../HorizontalScrollGrid.stories.tsx | 37 -- .../HorizontalScrollGrid.test.tsx | 87 --- .../HorizontalScrollGrid.tsx | 247 -------- .../components/HorizontalScrollGrid/index.tsx | 17 - .../Lifecycle/Lifecycle.stories.tsx | 43 -- .../components/Lifecycle/Lifecycle.test.tsx | 41 -- .../src/components/Lifecycle/Lifecycle.tsx | 56 -- .../core/src/components/Lifecycle/index.ts | 17 - .../core/src/components/Link/Link.stories.tsx | 92 --- .../core/src/components/Link/Link.test.tsx | 68 --- packages/core/src/components/Link/Link.tsx | 47 -- packages/core/src/components/Link/index.ts | 18 - .../MarkdownContent.stories.tsx | 120 ---- .../MarkdownContent/MarkdownContent.test.tsx | 65 --- .../MarkdownContent/MarkdownContent.tsx | 89 --- .../src/components/MarkdownContent/index.ts | 17 - .../LoginRequestListItem.tsx | 81 --- .../OAuthRequestDialog/OAuthRequestDialog.tsx | 86 --- .../components/OAuthRequestDialog/index.ts | 17 - .../OverflowTooltip.stories.tsx | 48 -- .../OverflowTooltip/OverflowTooltip.test.tsx | 30 - .../OverflowTooltip/OverflowTooltip.tsx | 57 -- .../src/components/OverflowTooltip/index.ts | 16 - .../components/Progress/Progress.stories.tsx | 25 - .../src/components/Progress/Progress.test.tsx | 34 -- .../core/src/components/Progress/Progress.tsx | 33 -- .../core/src/components/Progress/index.ts | 17 - .../components/ProgressBars/Gauge.stories.tsx | 55 -- .../components/ProgressBars/Gauge.test.tsx | 71 --- .../src/components/ProgressBars/Gauge.tsx | 106 ---- .../ProgressBars/GaugeCard.stories.tsx | 99 ---- .../ProgressBars/GaugeCard.test.tsx | 46 -- .../src/components/ProgressBars/GaugeCard.tsx | 56 -- .../ProgressBars/LinearGauge.stories.tsx | 43 -- .../ProgressBars/LinearGauge.test.tsx | 37 -- .../components/ProgressBars/LinearGauge.tsx | 53 -- .../core/src/components/ProgressBars/index.ts | 19 - .../ResponseErrorPanel/ResponseErrorPanel.tsx | 167 ------ .../components/ResponseErrorPanel/index.ts | 17 - .../src/components/Select/Select.stories.tsx | 57 -- .../src/components/Select/Select.test.tsx | 57 -- .../core/src/components/Select/Select.tsx | 231 -------- packages/core/src/components/Select/index.tsx | 17 - .../Select/static/ClosedDropdown.tsx | 45 -- .../Select/static/OpenedDropdown.tsx | 45 -- .../SimpleStepper/SimpleStepper.stories.tsx | 82 --- .../SimpleStepper/SimpleStepper.test.tsx | 148 ----- .../SimpleStepper/SimpleStepper.tsx | 98 ---- .../SimpleStepper/SimpleStepperFooter.tsx | 156 ------ .../SimpleStepper/SimpleStepperStep.tsx | 61 -- .../src/components/SimpleStepper/index.ts | 18 - .../src/components/SimpleStepper/types.ts | 41 -- .../src/components/Status/Status.stories.tsx | 90 --- .../src/components/Status/Status.test.tsx | 69 --- .../core/src/components/Status/Status.tsx | 130 ----- packages/core/src/components/Status/index.ts | 24 - .../StructuredMetadataTable/MetadataTable.tsx | 109 ---- .../StructuredMetadataTable/README.md | 62 --- .../StructuredMetadataTable.stories.tsx | 73 --- .../StructuredMetadataTable.test.tsx | 118 ---- .../StructuredMetadataTable.tsx | 163 ------ .../StructuredMetadataTable/index.tsx | 17 - .../SupportButton/SupportButton.test.tsx | 66 --- .../SupportButton/SupportButton.tsx | 139 ----- .../src/components/SupportButton/index.ts | 17 - .../TabbedLayout/RoutedTabs.test.tsx | 150 ----- .../components/TabbedLayout/RoutedTabs.tsx | 81 --- .../TabbedLayout/TabbedLayout.stories.tsx | 44 -- .../TabbedLayout/TabbedLayout.test.tsx | 94 ---- .../components/TabbedLayout/TabbedLayout.tsx | 91 --- .../core/src/components/TabbedLayout/index.ts | 17 - .../core/src/components/TabbedLayout/types.ts | 25 - .../core/src/components/Table/Filters.tsx | 157 ------ .../src/components/Table/SubvalueCell.tsx | 45 -- .../src/components/Table/Table.stories.tsx | 332 ----------- .../core/src/components/Table/Table.test.tsx | 68 --- packages/core/src/components/Table/Table.tsx | 508 ----------------- packages/core/src/components/Table/index.ts | 19 - .../core/src/components/Tabs/Tab.test.tsx | 26 - packages/core/src/components/Tabs/Tab.tsx | 62 --- packages/core/src/components/Tabs/TabBar.tsx | 52 -- packages/core/src/components/Tabs/TabIcon.tsx | 60 -- .../core/src/components/Tabs/TabPanel.tsx | 38 -- .../core/src/components/Tabs/Tabs.stories.tsx | 71 --- packages/core/src/components/Tabs/Tabs.tsx | 159 ------ packages/core/src/components/Tabs/index.ts | 17 - packages/core/src/components/Tabs/utils.ts | 28 - .../TrendLine/TrendLine.stories.tsx | 141 ----- .../components/TrendLine/TrendLine.test.tsx | 69 --- .../src/components/TrendLine/TrendLine.tsx | 48 -- .../core/src/components/TrendLine/index.ts | 17 - .../WarningPanel/WarningPanel.stories.tsx | 72 --- .../WarningPanel/WarningPanel.test.tsx | 67 --- .../components/WarningPanel/WarningPanel.tsx | 136 ----- .../core/src/components/WarningPanel/index.ts | 16 - packages/core/src/components/index.ts | 46 -- packages/core/src/hooks/index.ts | 23 - packages/core/src/hooks/useQueryParamState.ts | 95 ---- packages/core/src/hooks/useSupportConfig.ts | 74 --- packages/core/src/index.ts | 22 - .../src/layout/BottomLink/BottomLink.test.tsx | 30 - .../core/src/layout/BottomLink/BottomLink.tsx | 70 --- packages/core/src/layout/BottomLink/index.ts | 18 - .../Breadcrumbs/Breadcrumbs.stories.tsx | 138 ----- .../layout/Breadcrumbs/Breadcrumbs.test.tsx | 54 -- .../src/layout/Breadcrumbs/Breadcrumbs.tsx | 99 ---- packages/core/src/layout/Breadcrumbs/index.ts | 17 - packages/core/src/layout/Content/Content.tsx | 64 --- packages/core/src/layout/Content/index.ts | 17 - .../ContentHeader/ContentHeader.test.tsx | 48 -- .../layout/ContentHeader/ContentHeader.tsx | 116 ---- .../core/src/layout/ContentHeader/index.ts | 17 - .../ErrorBoundary/ErrorBoundary.test.tsx | 71 --- .../layout/ErrorBoundary/ErrorBoundary.tsx | 73 --- .../core/src/layout/ErrorBoundary/index.ts | 17 - .../src/layout/ErrorPage/ErrorPage.test.tsx | 33 -- .../core/src/layout/ErrorPage/ErrorPage.tsx | 85 --- .../core/src/layout/ErrorPage/MicDrop.tsx | 46 -- packages/core/src/layout/ErrorPage/index.ts | 17 - .../core/src/layout/ErrorPage/mic-drop.svg | 1 - .../core/src/layout/Header/Header.stories.tsx | 110 ---- .../core/src/layout/Header/Header.test.tsx | 67 --- packages/core/src/layout/Header/Header.tsx | 217 -------- packages/core/src/layout/Header/index.ts | 17 - .../HeaderActionMenu.test.tsx | 104 ---- .../HeaderActionMenu/HeaderActionMenu.tsx | 106 ---- .../HeaderActionMenu/VerticalMenuIcon.tsx | 25 - .../core/src/layout/HeaderActionMenu/index.ts | 17 - .../layout/HeaderLabel/HeaderLabel.test.tsx | 54 -- .../src/layout/HeaderLabel/HeaderLabel.tsx | 72 --- packages/core/src/layout/HeaderLabel/index.ts | 17 - .../src/layout/HeaderTabs/HeaderTabs.test.tsx | 81 --- .../core/src/layout/HeaderTabs/HeaderTabs.tsx | 97 ---- packages/core/src/layout/HeaderTabs/index.tsx | 18 - .../HomepageTimer/HomepageTimer.test.tsx | 53 -- .../layout/HomepageTimer/HomepageTimer.tsx | 102 ---- .../core/src/layout/HomepageTimer/index.ts | 17 - .../src/layout/InfoCard/InfoCard.stories.tsx | 69 --- .../src/layout/InfoCard/InfoCard.test.tsx | 39 -- .../core/src/layout/InfoCard/InfoCard.tsx | 211 ------- packages/core/src/layout/InfoCard/index.ts | 18 - .../src/layout/ItemCard/ItemCard.stories.tsx | 113 ---- .../src/layout/ItemCard/ItemCard.test.tsx | 61 -- .../core/src/layout/ItemCard/ItemCard.tsx | 104 ---- .../src/layout/ItemCard/ItemCardGrid.test.tsx | 50 -- .../core/src/layout/ItemCard/ItemCardGrid.tsx | 62 --- .../layout/ItemCard/ItemCardHeader.test.tsx | 60 -- .../src/layout/ItemCard/ItemCardHeader.tsx | 93 ---- packages/core/src/layout/ItemCard/index.ts | 21 - .../core/src/layout/Page/Page.stories.tsx | 245 -------- packages/core/src/layout/Page/Page.tsx | 48 -- packages/core/src/layout/Page/index.ts | 17 - packages/core/src/layout/Sidebar/Bar.tsx | 150 ----- packages/core/src/layout/Sidebar/Intro.tsx | 182 ------ .../core/src/layout/Sidebar/Items.test.tsx | 75 --- packages/core/src/layout/Sidebar/Items.tsx | 297 ---------- packages/core/src/layout/Sidebar/Page.tsx | 74 --- .../src/layout/Sidebar/Sidebar.stories.tsx | 56 -- packages/core/src/layout/Sidebar/config.ts | 49 -- packages/core/src/layout/Sidebar/index.ts | 33 -- .../src/layout/Sidebar/localStorage.test.ts | 25 - .../core/src/layout/Sidebar/localStorage.ts | 40 -- .../core/src/layout/SignInPage/SignInPage.tsx | 195 ------- .../src/layout/SignInPage/auth0Provider.tsx | 88 --- .../src/layout/SignInPage/commonProvider.tsx | 96 ---- .../src/layout/SignInPage/customProvider.tsx | 126 ----- .../src/layout/SignInPage/guestProvider.tsx | 61 -- packages/core/src/layout/SignInPage/index.ts | 18 - .../core/src/layout/SignInPage/providers.tsx | 181 ------ .../core/src/layout/SignInPage/styles.tsx | 42 -- packages/core/src/layout/SignInPage/types.ts | 49 -- .../layout/TabbedCard/TabbedCard.stories.tsx | 113 ---- .../src/layout/TabbedCard/TabbedCard.test.tsx | 99 ---- .../core/src/layout/TabbedCard/TabbedCard.tsx | 132 ----- packages/core/src/layout/TabbedCard/index.ts | 17 - packages/core/src/layout/index.ts | 31 -- packages/core/src/setupTests.ts | 17 - 406 files changed, 32821 deletions(-) delete mode 100644 packages/core-api/.eslintrc.js delete mode 100644 packages/core-api/CHANGELOG.md delete mode 100644 packages/core-api/README.md delete mode 100644 packages/core-api/package.json delete mode 100644 packages/core-api/src/apis/definitions/AlertApi.ts delete mode 100644 packages/core-api/src/apis/definitions/AppThemeApi.ts delete mode 100644 packages/core-api/src/apis/definitions/ConfigApi.ts delete mode 100644 packages/core-api/src/apis/definitions/DiscoveryApi.ts delete mode 100644 packages/core-api/src/apis/definitions/ErrorApi.ts delete mode 100644 packages/core-api/src/apis/definitions/FeatureFlagsApi.ts delete mode 100644 packages/core-api/src/apis/definitions/IdentityApi.ts delete mode 100644 packages/core-api/src/apis/definitions/OAuthRequestApi.ts delete mode 100644 packages/core-api/src/apis/definitions/StorageApi.ts delete mode 100644 packages/core-api/src/apis/definitions/auth.ts delete mode 100644 packages/core-api/src/apis/definitions/index.ts delete mode 100644 packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts delete mode 100644 packages/core-api/src/apis/implementations/AlertApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts delete mode 100644 packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts delete mode 100644 packages/core-api/src/apis/implementations/AppThemeApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/ConfigApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts delete mode 100644 packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts delete mode 100644 packages/core-api/src/apis/implementations/DiscoveryApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts delete mode 100644 packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts delete mode 100644 packages/core-api/src/apis/implementations/ErrorApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx delete mode 100644 packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx delete mode 100644 packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts delete mode 100644 packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts delete mode 100644 packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts delete mode 100644 packages/core-api/src/apis/implementations/StorageApi/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/auth0/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/github/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/github/types.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/gitlab/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/google/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/microsoft/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oauth2/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/oauth2/types.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/okta/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/onelogin/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/saml/index.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/saml/types.ts delete mode 100644 packages/core-api/src/apis/implementations/auth/types.ts delete mode 100644 packages/core-api/src/apis/implementations/index.ts delete mode 100644 packages/core-api/src/apis/index.ts delete mode 100644 packages/core-api/src/apis/system/ApiAggregator.test.ts delete mode 100644 packages/core-api/src/apis/system/ApiAggregator.ts delete mode 100644 packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts delete mode 100644 packages/core-api/src/apis/system/ApiFactoryRegistry.ts delete mode 100644 packages/core-api/src/apis/system/ApiProvider.test.tsx delete mode 100644 packages/core-api/src/apis/system/ApiProvider.tsx delete mode 100644 packages/core-api/src/apis/system/ApiRef.test.ts delete mode 100644 packages/core-api/src/apis/system/ApiRef.ts delete mode 100644 packages/core-api/src/apis/system/ApiRegistry.test.ts delete mode 100644 packages/core-api/src/apis/system/ApiRegistry.ts delete mode 100644 packages/core-api/src/apis/system/ApiResolver.test.ts delete mode 100644 packages/core-api/src/apis/system/ApiResolver.ts delete mode 100644 packages/core-api/src/apis/system/helpers.ts delete mode 100644 packages/core-api/src/apis/system/index.ts delete mode 100644 packages/core-api/src/apis/system/types.ts delete mode 100644 packages/core-api/src/app/App.test.tsx delete mode 100644 packages/core-api/src/app/App.tsx delete mode 100644 packages/core-api/src/app/AppContext.test.tsx delete mode 100644 packages/core-api/src/app/AppContext.tsx delete mode 100644 packages/core-api/src/app/AppIdentity.ts delete mode 100644 packages/core-api/src/app/AppThemeProvider.tsx delete mode 100644 packages/core-api/src/app/index.ts delete mode 100644 packages/core-api/src/app/types.ts delete mode 100644 packages/core-api/src/extensions/componentData.test.tsx delete mode 100644 packages/core-api/src/extensions/componentData.tsx delete mode 100644 packages/core-api/src/extensions/extensions.test.tsx delete mode 100644 packages/core-api/src/extensions/extensions.tsx delete mode 100644 packages/core-api/src/extensions/index.ts delete mode 100644 packages/core-api/src/extensions/traversal.test.tsx delete mode 100644 packages/core-api/src/extensions/traversal.ts delete mode 100644 packages/core-api/src/icons/icons.tsx delete mode 100644 packages/core-api/src/icons/index.ts delete mode 100644 packages/core-api/src/icons/types.ts delete mode 100644 packages/core-api/src/index.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/index.ts delete mode 100644 packages/core-api/src/lib/AuthConnector/types.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/common.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/index.ts delete mode 100644 packages/core-api/src/lib/AuthSessionManager/types.ts delete mode 100644 packages/core-api/src/lib/globalObject.test.ts delete mode 100644 packages/core-api/src/lib/globalObject.ts delete mode 100644 packages/core-api/src/lib/index.ts delete mode 100644 packages/core-api/src/lib/loginPopup.test.ts delete mode 100644 packages/core-api/src/lib/loginPopup.ts delete mode 100644 packages/core-api/src/lib/subjects.test.ts delete mode 100644 packages/core-api/src/lib/subjects.ts delete mode 100644 packages/core-api/src/lib/versionedValues.test.ts delete mode 100644 packages/core-api/src/lib/versionedValues.ts delete mode 100644 packages/core-api/src/plugin/Plugin.tsx delete mode 100644 packages/core-api/src/plugin/collectors.test.tsx delete mode 100644 packages/core-api/src/plugin/collectors.ts delete mode 100644 packages/core-api/src/plugin/index.ts delete mode 100644 packages/core-api/src/plugin/types.ts delete mode 100644 packages/core-api/src/private.ts delete mode 100644 packages/core-api/src/public.ts delete mode 100644 packages/core-api/src/routing/ExternalRouteRef.test.ts delete mode 100644 packages/core-api/src/routing/ExternalRouteRef.ts delete mode 100644 packages/core-api/src/routing/FlatRoutes.test.tsx delete mode 100644 packages/core-api/src/routing/FlatRoutes.tsx delete mode 100644 packages/core-api/src/routing/RouteRef.test.ts delete mode 100644 packages/core-api/src/routing/RouteRef.ts delete mode 100644 packages/core-api/src/routing/RouteResolver.test.ts delete mode 100644 packages/core-api/src/routing/RouteResolver.ts delete mode 100644 packages/core-api/src/routing/SubRouteRef.test.ts delete mode 100644 packages/core-api/src/routing/SubRouteRef.ts delete mode 100644 packages/core-api/src/routing/collectors.test.tsx delete mode 100644 packages/core-api/src/routing/collectors.tsx delete mode 100644 packages/core-api/src/routing/hooks.test.tsx delete mode 100644 packages/core-api/src/routing/hooks.tsx delete mode 100644 packages/core-api/src/routing/index.ts delete mode 100644 packages/core-api/src/routing/types.ts delete mode 100644 packages/core-api/src/routing/validation.ts delete mode 100644 packages/core-api/src/setupTests.ts delete mode 100644 packages/core-api/src/types.ts delete mode 100644 packages/core/.eslintrc.js delete mode 100644 packages/core/.npmrc delete mode 100644 packages/core/CHANGELOG.md delete mode 100644 packages/core/README.md delete mode 100644 packages/core/config.d.ts delete mode 100644 packages/core/package.json delete mode 100644 packages/core/src/api-wrappers/createApp.test.tsx delete mode 100644 packages/core/src/api-wrappers/createApp.tsx delete mode 100644 packages/core/src/api-wrappers/defaultApis.ts delete mode 100644 packages/core/src/api-wrappers/index.ts delete mode 100644 packages/core/src/components/AlertDisplay/AlertDisplay.test.tsx delete mode 100644 packages/core/src/components/AlertDisplay/AlertDisplay.tsx delete mode 100644 packages/core/src/components/AlertDisplay/index.ts delete mode 100644 packages/core/src/components/Avatar/Avatar.stories.tsx delete mode 100644 packages/core/src/components/Avatar/Avatar.test.tsx delete mode 100644 packages/core/src/components/Avatar/Avatar.tsx delete mode 100644 packages/core/src/components/Avatar/index.ts delete mode 100644 packages/core/src/components/Avatar/util.test.ts delete mode 100644 packages/core/src/components/Avatar/utils.ts delete mode 100644 packages/core/src/components/Button/Button.stories.tsx delete mode 100644 packages/core/src/components/Button/Button.test.tsx delete mode 100644 packages/core/src/components/Button/Button.tsx delete mode 100644 packages/core/src/components/Button/index.ts delete mode 100644 packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx delete mode 100644 packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx delete mode 100644 packages/core/src/components/CheckboxTree/CheckboxTree.tsx delete mode 100644 packages/core/src/components/CheckboxTree/index.tsx delete mode 100644 packages/core/src/components/Chip/Chip.stories.tsx delete mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.stories.tsx delete mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.test.tsx delete mode 100644 packages/core/src/components/CodeSnippet/CodeSnippet.tsx delete mode 100644 packages/core/src/components/CodeSnippet/index.tsx delete mode 100644 packages/core/src/components/CopyTextButton/CopyTextButton.stories.tsx delete mode 100644 packages/core/src/components/CopyTextButton/CopyTextButton.test.tsx delete mode 100644 packages/core/src/components/CopyTextButton/CopyTextButton.tsx delete mode 100644 packages/core/src/components/CopyTextButton/index.tsx delete mode 100644 packages/core/src/components/DependencyGraph/DefaultLabel.tsx delete mode 100644 packages/core/src/components/DependencyGraph/DefaultNode.tsx delete mode 100644 packages/core/src/components/DependencyGraph/DependencyGraph.stories.tsx delete mode 100644 packages/core/src/components/DependencyGraph/DependencyGraph.test.tsx delete mode 100644 packages/core/src/components/DependencyGraph/DependencyGraph.tsx delete mode 100644 packages/core/src/components/DependencyGraph/Edge.test.tsx delete mode 100644 packages/core/src/components/DependencyGraph/Edge.tsx delete mode 100644 packages/core/src/components/DependencyGraph/Node.test.tsx delete mode 100644 packages/core/src/components/DependencyGraph/Node.tsx delete mode 100644 packages/core/src/components/DependencyGraph/constants.ts delete mode 100644 packages/core/src/components/DependencyGraph/index.ts delete mode 100644 packages/core/src/components/DependencyGraph/types.ts delete mode 100644 packages/core/src/components/Dialog/Dialog.stories.tsx delete mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.stories.tsx delete mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.test.tsx delete mode 100644 packages/core/src/components/DismissableBanner/DismissableBanner.tsx delete mode 100644 packages/core/src/components/DismissableBanner/index.ts delete mode 100644 packages/core/src/components/Drawer/Drawer.stories.tsx delete mode 100644 packages/core/src/components/EmptyState/EmptyState.stories.tsx delete mode 100644 packages/core/src/components/EmptyState/EmptyState.test.tsx delete mode 100644 packages/core/src/components/EmptyState/EmptyState.tsx delete mode 100644 packages/core/src/components/EmptyState/EmptyStateImage.test.tsx delete mode 100644 packages/core/src/components/EmptyState/EmptyStateImage.tsx delete mode 100644 packages/core/src/components/EmptyState/MissingAnnotationEmptyState.tsx delete mode 100644 packages/core/src/components/EmptyState/assets/createComponent.svg delete mode 100644 packages/core/src/components/EmptyState/assets/missingAnnotation.svg delete mode 100644 packages/core/src/components/EmptyState/assets/noBuild.svg delete mode 100644 packages/core/src/components/EmptyState/assets/noInformation.svg delete mode 100644 packages/core/src/components/EmptyState/index.ts delete mode 100644 packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.test.tsx delete mode 100644 packages/core/src/components/FeatureDiscovery/FeatureCalloutCircular.tsx delete mode 100644 packages/core/src/components/FeatureDiscovery/index.ts delete mode 100644 packages/core/src/components/FeatureDiscovery/lib/usePortal.ts delete mode 100644 packages/core/src/components/FeatureDiscovery/lib/useShowCallout.ts delete mode 100644 packages/core/src/components/HeaderIconLinkRow/HeaderIconLinkRow.tsx delete mode 100644 packages/core/src/components/HeaderIconLinkRow/IconLinkVertical.tsx delete mode 100644 packages/core/src/components/HeaderIconLinkRow/index.ts delete mode 100644 packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.stories.tsx delete mode 100644 packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.test.tsx delete mode 100644 packages/core/src/components/HorizontalScrollGrid/HorizontalScrollGrid.tsx delete mode 100644 packages/core/src/components/HorizontalScrollGrid/index.tsx delete mode 100644 packages/core/src/components/Lifecycle/Lifecycle.stories.tsx delete mode 100644 packages/core/src/components/Lifecycle/Lifecycle.test.tsx delete mode 100644 packages/core/src/components/Lifecycle/Lifecycle.tsx delete mode 100644 packages/core/src/components/Lifecycle/index.ts delete mode 100644 packages/core/src/components/Link/Link.stories.tsx delete mode 100644 packages/core/src/components/Link/Link.test.tsx delete mode 100644 packages/core/src/components/Link/Link.tsx delete mode 100644 packages/core/src/components/Link/index.ts delete mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.stories.tsx delete mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.test.tsx delete mode 100644 packages/core/src/components/MarkdownContent/MarkdownContent.tsx delete mode 100644 packages/core/src/components/MarkdownContent/index.ts delete mode 100644 packages/core/src/components/OAuthRequestDialog/LoginRequestListItem.tsx delete mode 100644 packages/core/src/components/OAuthRequestDialog/OAuthRequestDialog.tsx delete mode 100644 packages/core/src/components/OAuthRequestDialog/index.ts delete mode 100644 packages/core/src/components/OverflowTooltip/OverflowTooltip.stories.tsx delete mode 100644 packages/core/src/components/OverflowTooltip/OverflowTooltip.test.tsx delete mode 100644 packages/core/src/components/OverflowTooltip/OverflowTooltip.tsx delete mode 100644 packages/core/src/components/OverflowTooltip/index.ts delete mode 100644 packages/core/src/components/Progress/Progress.stories.tsx delete mode 100644 packages/core/src/components/Progress/Progress.test.tsx delete mode 100644 packages/core/src/components/Progress/Progress.tsx delete mode 100644 packages/core/src/components/Progress/index.ts delete mode 100644 packages/core/src/components/ProgressBars/Gauge.stories.tsx delete mode 100644 packages/core/src/components/ProgressBars/Gauge.test.tsx delete mode 100644 packages/core/src/components/ProgressBars/Gauge.tsx delete mode 100644 packages/core/src/components/ProgressBars/GaugeCard.stories.tsx delete mode 100644 packages/core/src/components/ProgressBars/GaugeCard.test.tsx delete mode 100644 packages/core/src/components/ProgressBars/GaugeCard.tsx delete mode 100644 packages/core/src/components/ProgressBars/LinearGauge.stories.tsx delete mode 100644 packages/core/src/components/ProgressBars/LinearGauge.test.tsx delete mode 100644 packages/core/src/components/ProgressBars/LinearGauge.tsx delete mode 100644 packages/core/src/components/ProgressBars/index.ts delete mode 100644 packages/core/src/components/ResponseErrorPanel/ResponseErrorPanel.tsx delete mode 100644 packages/core/src/components/ResponseErrorPanel/index.ts delete mode 100644 packages/core/src/components/Select/Select.stories.tsx delete mode 100644 packages/core/src/components/Select/Select.test.tsx delete mode 100644 packages/core/src/components/Select/Select.tsx delete mode 100644 packages/core/src/components/Select/index.tsx delete mode 100644 packages/core/src/components/Select/static/ClosedDropdown.tsx delete mode 100644 packages/core/src/components/Select/static/OpenedDropdown.tsx delete mode 100644 packages/core/src/components/SimpleStepper/SimpleStepper.stories.tsx delete mode 100644 packages/core/src/components/SimpleStepper/SimpleStepper.test.tsx delete mode 100644 packages/core/src/components/SimpleStepper/SimpleStepper.tsx delete mode 100644 packages/core/src/components/SimpleStepper/SimpleStepperFooter.tsx delete mode 100644 packages/core/src/components/SimpleStepper/SimpleStepperStep.tsx delete mode 100644 packages/core/src/components/SimpleStepper/index.ts delete mode 100644 packages/core/src/components/SimpleStepper/types.ts delete mode 100644 packages/core/src/components/Status/Status.stories.tsx delete mode 100644 packages/core/src/components/Status/Status.test.tsx delete mode 100644 packages/core/src/components/Status/Status.tsx delete mode 100644 packages/core/src/components/Status/index.ts delete mode 100644 packages/core/src/components/StructuredMetadataTable/MetadataTable.tsx delete mode 100644 packages/core/src/components/StructuredMetadataTable/README.md delete mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.stories.tsx delete mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.test.tsx delete mode 100644 packages/core/src/components/StructuredMetadataTable/StructuredMetadataTable.tsx delete mode 100644 packages/core/src/components/StructuredMetadataTable/index.tsx delete mode 100644 packages/core/src/components/SupportButton/SupportButton.test.tsx delete mode 100644 packages/core/src/components/SupportButton/SupportButton.tsx delete mode 100644 packages/core/src/components/SupportButton/index.ts delete mode 100644 packages/core/src/components/TabbedLayout/RoutedTabs.test.tsx delete mode 100644 packages/core/src/components/TabbedLayout/RoutedTabs.tsx delete mode 100644 packages/core/src/components/TabbedLayout/TabbedLayout.stories.tsx delete mode 100644 packages/core/src/components/TabbedLayout/TabbedLayout.test.tsx delete mode 100644 packages/core/src/components/TabbedLayout/TabbedLayout.tsx delete mode 100644 packages/core/src/components/TabbedLayout/index.ts delete mode 100644 packages/core/src/components/TabbedLayout/types.ts delete mode 100644 packages/core/src/components/Table/Filters.tsx delete mode 100644 packages/core/src/components/Table/SubvalueCell.tsx delete mode 100644 packages/core/src/components/Table/Table.stories.tsx delete mode 100644 packages/core/src/components/Table/Table.test.tsx delete mode 100644 packages/core/src/components/Table/Table.tsx delete mode 100644 packages/core/src/components/Table/index.ts delete mode 100644 packages/core/src/components/Tabs/Tab.test.tsx delete mode 100644 packages/core/src/components/Tabs/Tab.tsx delete mode 100644 packages/core/src/components/Tabs/TabBar.tsx delete mode 100644 packages/core/src/components/Tabs/TabIcon.tsx delete mode 100644 packages/core/src/components/Tabs/TabPanel.tsx delete mode 100644 packages/core/src/components/Tabs/Tabs.stories.tsx delete mode 100644 packages/core/src/components/Tabs/Tabs.tsx delete mode 100644 packages/core/src/components/Tabs/index.ts delete mode 100644 packages/core/src/components/Tabs/utils.ts delete mode 100644 packages/core/src/components/TrendLine/TrendLine.stories.tsx delete mode 100644 packages/core/src/components/TrendLine/TrendLine.test.tsx delete mode 100644 packages/core/src/components/TrendLine/TrendLine.tsx delete mode 100644 packages/core/src/components/TrendLine/index.ts delete mode 100644 packages/core/src/components/WarningPanel/WarningPanel.stories.tsx delete mode 100644 packages/core/src/components/WarningPanel/WarningPanel.test.tsx delete mode 100644 packages/core/src/components/WarningPanel/WarningPanel.tsx delete mode 100644 packages/core/src/components/WarningPanel/index.ts delete mode 100644 packages/core/src/components/index.ts delete mode 100644 packages/core/src/hooks/index.ts delete mode 100644 packages/core/src/hooks/useQueryParamState.ts delete mode 100644 packages/core/src/hooks/useSupportConfig.ts delete mode 100644 packages/core/src/index.ts delete mode 100644 packages/core/src/layout/BottomLink/BottomLink.test.tsx delete mode 100644 packages/core/src/layout/BottomLink/BottomLink.tsx delete mode 100644 packages/core/src/layout/BottomLink/index.ts delete mode 100644 packages/core/src/layout/Breadcrumbs/Breadcrumbs.stories.tsx delete mode 100644 packages/core/src/layout/Breadcrumbs/Breadcrumbs.test.tsx delete mode 100644 packages/core/src/layout/Breadcrumbs/Breadcrumbs.tsx delete mode 100644 packages/core/src/layout/Breadcrumbs/index.ts delete mode 100644 packages/core/src/layout/Content/Content.tsx delete mode 100644 packages/core/src/layout/Content/index.ts delete mode 100644 packages/core/src/layout/ContentHeader/ContentHeader.test.tsx delete mode 100644 packages/core/src/layout/ContentHeader/ContentHeader.tsx delete mode 100644 packages/core/src/layout/ContentHeader/index.ts delete mode 100644 packages/core/src/layout/ErrorBoundary/ErrorBoundary.test.tsx delete mode 100644 packages/core/src/layout/ErrorBoundary/ErrorBoundary.tsx delete mode 100644 packages/core/src/layout/ErrorBoundary/index.ts delete mode 100644 packages/core/src/layout/ErrorPage/ErrorPage.test.tsx delete mode 100644 packages/core/src/layout/ErrorPage/ErrorPage.tsx delete mode 100644 packages/core/src/layout/ErrorPage/MicDrop.tsx delete mode 100644 packages/core/src/layout/ErrorPage/index.ts delete mode 100644 packages/core/src/layout/ErrorPage/mic-drop.svg delete mode 100644 packages/core/src/layout/Header/Header.stories.tsx delete mode 100644 packages/core/src/layout/Header/Header.test.tsx delete mode 100644 packages/core/src/layout/Header/Header.tsx delete mode 100644 packages/core/src/layout/Header/index.ts delete mode 100644 packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.test.tsx delete mode 100644 packages/core/src/layout/HeaderActionMenu/HeaderActionMenu.tsx delete mode 100644 packages/core/src/layout/HeaderActionMenu/VerticalMenuIcon.tsx delete mode 100644 packages/core/src/layout/HeaderActionMenu/index.ts delete mode 100644 packages/core/src/layout/HeaderLabel/HeaderLabel.test.tsx delete mode 100644 packages/core/src/layout/HeaderLabel/HeaderLabel.tsx delete mode 100644 packages/core/src/layout/HeaderLabel/index.ts delete mode 100644 packages/core/src/layout/HeaderTabs/HeaderTabs.test.tsx delete mode 100644 packages/core/src/layout/HeaderTabs/HeaderTabs.tsx delete mode 100644 packages/core/src/layout/HeaderTabs/index.tsx delete mode 100644 packages/core/src/layout/HomepageTimer/HomepageTimer.test.tsx delete mode 100644 packages/core/src/layout/HomepageTimer/HomepageTimer.tsx delete mode 100644 packages/core/src/layout/HomepageTimer/index.ts delete mode 100644 packages/core/src/layout/InfoCard/InfoCard.stories.tsx delete mode 100644 packages/core/src/layout/InfoCard/InfoCard.test.tsx delete mode 100644 packages/core/src/layout/InfoCard/InfoCard.tsx delete mode 100644 packages/core/src/layout/InfoCard/index.ts delete mode 100644 packages/core/src/layout/ItemCard/ItemCard.stories.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCard.test.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCard.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCardGrid.test.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCardGrid.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCardHeader.test.tsx delete mode 100644 packages/core/src/layout/ItemCard/ItemCardHeader.tsx delete mode 100644 packages/core/src/layout/ItemCard/index.ts delete mode 100644 packages/core/src/layout/Page/Page.stories.tsx delete mode 100644 packages/core/src/layout/Page/Page.tsx delete mode 100644 packages/core/src/layout/Page/index.ts delete mode 100644 packages/core/src/layout/Sidebar/Bar.tsx delete mode 100644 packages/core/src/layout/Sidebar/Intro.tsx delete mode 100644 packages/core/src/layout/Sidebar/Items.test.tsx delete mode 100644 packages/core/src/layout/Sidebar/Items.tsx delete mode 100644 packages/core/src/layout/Sidebar/Page.tsx delete mode 100644 packages/core/src/layout/Sidebar/Sidebar.stories.tsx delete mode 100644 packages/core/src/layout/Sidebar/config.ts delete mode 100644 packages/core/src/layout/Sidebar/index.ts delete mode 100644 packages/core/src/layout/Sidebar/localStorage.test.ts delete mode 100644 packages/core/src/layout/Sidebar/localStorage.ts delete mode 100644 packages/core/src/layout/SignInPage/SignInPage.tsx delete mode 100644 packages/core/src/layout/SignInPage/auth0Provider.tsx delete mode 100644 packages/core/src/layout/SignInPage/commonProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/customProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/guestProvider.tsx delete mode 100644 packages/core/src/layout/SignInPage/index.ts delete mode 100644 packages/core/src/layout/SignInPage/providers.tsx delete mode 100644 packages/core/src/layout/SignInPage/styles.tsx delete mode 100644 packages/core/src/layout/SignInPage/types.ts delete mode 100644 packages/core/src/layout/TabbedCard/TabbedCard.stories.tsx delete mode 100644 packages/core/src/layout/TabbedCard/TabbedCard.test.tsx delete mode 100644 packages/core/src/layout/TabbedCard/TabbedCard.tsx delete mode 100644 packages/core/src/layout/TabbedCard/index.ts delete mode 100644 packages/core/src/layout/index.ts delete mode 100644 packages/core/src/setupTests.ts diff --git a/packages/core-api/.eslintrc.js b/packages/core-api/.eslintrc.js deleted file mode 100644 index d592a653c8..0000000000 --- a/packages/core-api/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], - rules: { - // TODO: add prop types to JS and remove - 'react/prop-types': 0, - 'jest/expect-expect': 0, - }, -}; diff --git a/packages/core-api/CHANGELOG.md b/packages/core-api/CHANGELOG.md deleted file mode 100644 index 016dff8a10..0000000000 --- a/packages/core-api/CHANGELOG.md +++ /dev/null @@ -1,390 +0,0 @@ -# @backstage/core-api - -## 0.2.23 - -### Patch Changes - -- a1c30d7ea: Add deprecation warning to package README. -- Updated dependencies - - @backstage/core-plugin-api@0.1.3 - -## 0.2.22 - -### Patch Changes - -- 9bca2a252: Improve forwards compatibility with `@backstage/core-app-api` and `@backstage/core-plugin-api` by re-using route reference types and factory methods from `@backstage/core-plugin-api`. -- Updated dependencies [75b8537ce] -- Updated dependencies [da8cba44f] - - @backstage/core-plugin-api@0.1.2 - -## 0.2.21 - -### Patch Changes - -- 0160678b1: Made the `RouteRef*` types compatible with the ones exported from `@backstage/core-plugin-api`. -- Updated dependencies [031ccd45f] -- Updated dependencies [e7c5e4b30] - - @backstage/core-plugin-api@0.1.1 - - @backstage/theme@0.2.8 - -## 0.2.20 - -### Patch Changes - -- d597a50c6: Add a global type definition for `Symbol.observable`, fix type checking in projects that didn't already have it defined. - -## 0.2.19 - -### Patch Changes - -- 61c3f927c: Updated the `Observable` type to provide interoperability with `Symbol.observable`, making it compatible with at least `zen-observable` and `RxJS 7`. - - In cases where this change breaks tests that mocked the `Observable` type, the following addition to the mock should fix the breakage: - - ```ts - [Symbol.observable]() { - return this; - }, - ``` - -- 65e6c4541: Remove circular dependencies - -## 0.2.18 - -### Patch Changes - -- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 -- 675a569a9: chore: bump `react-use` dependency in all packages - -## 0.2.17 - -### Patch Changes - -- ab07d77f6: Add support for discovering plugins through the app element tree, removing the need to register them explicitly. -- 50ce875a0: Fixed a potentially confusing error being thrown about misuse of routable extensions where the error was actually something different. -- Updated dependencies [931b21a12] - - @backstage/theme@0.2.6 - -## 0.2.16 - -### Patch Changes - -- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading - errors visible to the user. -- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. -- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. - -## 0.2.15 - -### Patch Changes - -- 76deafd31: Changed the signature of `createRoutableExtension` to include null -- 01ccef4c7: Introduce `useRouteRefParams` to `core-api` to retrieve typed route parameters. -- Updated dependencies [4618774ff] - - @backstage/theme@0.2.5 - -## 0.2.14 - -### Patch Changes - -- a51dc0006: Export `SubRouteRef` type, and allow `SubRouteRef`s to be assigned to `plugin.routes`. -- e7f9b9435: Allow elements to be used multiple times in the app element tree. -- 34ff49b0f: Allow extension components to also return `null` in addition to a `JSX.Element`. -- d88dd219e: Internal refactor to allow for future package splits. As part of this `ApiRef`s are now identified by their ID rather than their reference. -- c8b54c370: Added new Docs Icon to Core Icons -- Updated dependencies [0434853a5] - - @backstage/config@0.1.4 - -## 0.2.13 - -### Patch Changes - -- 13524b80b: Fully deprecate `title` option of `RouteRef`s and introduce `id` instead. -- e74b07578: Fixed a bug where FlatRoutes didn't handle React Fragments properly. -- 6fb4258a8: Add `SubRouteRef`s, which can be used to create a route ref with a fixed path relative to an absolute `RouteRef`. They are useful if you for example have a page that is mounted at a sub route of a routable extension component, and you want other plugins to be able to route to that page. - - For example: - - ```tsx - // routes.ts - const rootRouteRef = createRouteRef({ id: 'root' }); - const detailsRouteRef = createSubRouteRef({ - id: 'root-sub', - parent: rootRouteRef, - path: '/details', - }); - - // plugin.ts - export const myPlugin = createPlugin({ - routes: { - root: rootRouteRef, - details: detailsRouteRef, - } - }) - - export const MyPage = plugin.provide(createRoutableExtension({ - component: () => import('./components/MyPage').then(m => m.MyPage), - mountPoint: rootRouteRef, - })) - - // components/MyPage.tsx - const MyPage = () => ( - - {/* myPlugin.routes.root will take the user to this page */} - }> - - {/* myPlugin.routes.details will take the user to this page */} - }> - - ) - ``` - -- 395885905: Wait for `configApi` to be ready before using `featureFlagsApi` -- Updated dependencies [2089de76b] - - @backstage/theme@0.2.4 - -## 0.2.12 - -### Patch Changes - -- 40c0fdbaa: Added support for optional external route references. By setting `optional: true` when creating an `ExternalRouteRef` it is no longer a requirement to bind the route in the app. If the app isn't bound `useRouteRef` will return `undefined`. -- 2a271d89e: Internal refactor of how component data is access to avoid polluting components and make it possible to bridge across versions. - -## 0.2.11 - -### Patch Changes - -- 3a58084b6: The `FlatRoutes` components now renders the not found page of the app if no routes are matched. -- 1407b34c6: More informative error message for missing ApiContext. -- b6c4f485d: Fix error when querying Backstage Identity with SAML authentication -- 3a58084b6: Created separate `AppContext` type to be returned from `useApp` rather than the `BackstageApp` itself. The `AppContext` type includes but deprecates `getPlugins`, `getProvider`, `getRouter`, and `getRoutes`. In addition, the `AppContext` adds a new `getComponents` method which providers access to the app components. -- Updated dependencies [a1f5e6545] - - @backstage/config@0.1.3 - -## 0.2.10 - -### Patch Changes - -- f10950bd2: Minor refactoring of BackstageApp.getSystemIcons to support custom registered - icons. Custom Icons can be added using: - - ```tsx - import AlarmIcon from '@material-ui/icons/Alarm'; - import MyPersonIcon from './MyPerson'; - - const app = createApp({ - icons: { - user: MyPersonIcon // override system icon - alert: AlarmIcon, // Custom icon - }, - }); - ``` - -- fd3f2a8c0: Export `createExternalRouteRef`, as well as give it an `id` for easier debugging, and fix parameter requirements when used with `useRouteRef`. - -## 0.2.9 - -### Patch Changes - -- ab0892358: Remove test dependencies from production package list - -## 0.2.8 - -### Patch Changes - -- a08c32ced: Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes. -- 86c3c652a: Deprecate `RouteRef` path parameter and member, and remove deprecated `routeRef.createSubRouteRef`. -- 27f2af935: Delay auth loginPopup close to avoid race condition with callers of authFlowHelpers. - -## 0.2.7 - -### Patch Changes - -- d681db2b5: Fix for GitHub and SAML auth not properly updating session state when already logged in. -- 1dc445e89: Introduce new plugin extension API -- Updated dependencies [1dc445e89] - - @backstage/test-utils@0.1.6 - -## 0.2.6 - -### Patch Changes - -- 7dd2ef7d1: Use auth provider ID to create unique session storage keys for GitHub and SAML Auth. - -## 0.2.5 - -### Patch Changes - -- b6557c098: Update ApiFactory type to correctly infer API type and disallow mismatched implementations. - - This fixes for example the following code: - - ```ts - interface MyApi { - myMethod(): void - } - - const myApiRef = createApiRef({...}); - - createApiFactory({ - api: myApiRef, - deps: {}, - // This should've caused an error, since the empty object does not fully implement MyApi - factory: () => ({}), - }) - ``` - -- d8d5a17da: Deprecated the `ConcreteRoute`, `MutableRouteRef`, `AbsoluteRouteRef` types and added a new `RouteRef` type as replacement. - - Deprecated and disabled the `createSubRoute` method of `AbsoluteRouteRef`. - - Add an as of yet unused `params` option to `createRouteRef`. - -- Updated dependencies [e3bd9fc2f] -- Updated dependencies [e1f4e24ef] -- Updated dependencies [1665ae8bb] -- Updated dependencies [e3bd9fc2f] - - @backstage/config@0.1.2 - - @backstage/test-utils@0.1.5 - - @backstage/theme@0.2.2 - -## 0.2.4 - -### Patch Changes - -- b4488ddb0: Added a type alias for PositionError = GeolocationPositionError - - @backstage/test-utils@0.1.4 - -## 0.2.3 - -### Patch Changes - -- 700a212b4: bug fix: issue 3223 - detect mismatching origin and indicate it in the message at auth failure - -## 0.2.2 - -### Patch Changes - -- 9b9e86f8a: export oidc provider - -## 0.2.1 - -### Patch Changes - -- c5bab94ab: Updated the AuthApi `.create` methods to configure the default scope of the corresponding Auth Api. As a result the - default scope is configurable when overwriting the Core Api in the app. - - ``` - GithubAuth.create({ - discoveryApi, - oauthRequestApi, - defaultScopes: ['read:user', 'repo'], - }), - ``` - - Replaced redundant CreateOptions of each Auth Api with the OAuthApiCreateOptions type. - - ``` - export type OAuthApiCreateOptions = AuthApiCreateOptions & { - oauthRequestApi: OAuthRequestApi; - defaultScopes?: string[]; - }; - - export type AuthApiCreateOptions = { - discoveryApi: DiscoveryApi; - environment?: string; - provider?: AuthProvider & { id: string }; - }; - ``` - -- Updated dependencies [4577e377b] - - @backstage/theme@0.2.1 - -## 0.2.0 - -### Minor Changes - -- 819a70229: Add SAML login to backstage - - ![](https://user-images.githubusercontent.com/872486/92251660-bb9e3400-eeff-11ea-86fe-1f2a0262cd31.png) - - ![](https://user-images.githubusercontent.com/872486/93851658-1a76f200-fce3-11ea-990b-26ca1a327a15.png) - -- b79017fd3: Updated the `GithubAuth.create` method to configure the default scope of the GitHub Auth Api. As a result the - default scope is configurable when overwriting the Core Api in the app. - - ``` - GithubAuth.create({ - discoveryApi, - oauthRequestApi, - defaultScopes: ['read:user', 'repo'], - }), - ``` - -- cbab5bbf8: Refactored the FeatureFlagsApi to make it easier to re-implement. Existing usage of particularly getUserFlags can be replaced with isActive() or save(). - -### Patch Changes - -- cbbd271c4: Add initial RouteRefRegistry - - Starting out some work to bring routing back and working as part of the work towards finalizing #1536 - - This is some of the groundwork of an experiment we're working on to enable routing via RouteRefs, while letting the app itself look something like this: - - ```jsx - const App = () => ( - - - - {' '} - // catalogRouteRef - - - - - - - // statusRouteRef - - - - - - - - - - // sentryRouteRef - - - - - - - - - - - - - - - - ); - ``` - - As part of inverting the composition of the app, route refs and routing in general was somewhat broken, intentionally. Right now it's not really possible to easily route to different parts of the app from a plugin, or even different parts of the plugin that are not within the same router. - - The core part of the experiment is to construct a map of ApiRef[] -> path overrides. Each key in the map is the list of route refs to traversed to reach a leaf in the routing tree, and the value is the path override at that point. For example, the above tree would add entries like [techDocsRouteRef] -> '/docs', and [entityRouteRef, apiDocsRouteRef] -> '/api'. By mapping out the entire app in this structure, the idea is that we can navigate to any point in the app using RouteRefs. - - The RouteRefRegistry is an implementation of such a map, and the idea is to add it in master to make it a bit easier to experiment and iterate. This is not an exposed API at this point. - - We've explored a couple of alternatives for how to enable routing, but it's boiled down to either a solution centred around the route map mentioned above, or treating all routes as static and globally unique, with no room for flexibility, customization or conflicts between different plugins. We're starting out pursuing this options 😁. We also expect that a the app-wide routing table will make things like dynamic loading a lot cleaner, as there would be a much more clear handoff between the main chunk and dynamic chunks. - -- 26e69ab1a: Remove cost insights example client from demo app and export from plugin - Create cost insights dev plugin using example client - Make PluginConfig and dependent types public -- Updated dependencies [ae5983387] -- Updated dependencies [0d4459c08] - - @backstage/theme@0.2.0 - - @backstage/test-utils@0.1.2 diff --git a/packages/core-api/README.md b/packages/core-api/README.md deleted file mode 100644 index 5a6430baa7..0000000000 --- a/packages/core-api/README.md +++ /dev/null @@ -1,12 +0,0 @@ -# @backstage/core-api - -**NOTE: This package is deprecated** - -See the [migration documentation](https://backstage.io/docs/tutorials/migrating-away-from-core) for details on how to move to using the newer packages, [`@backstage/core-app-api`](https://www.npmjs.com/package/@backstage/core-app-api), [`@backstage/core-components`](https://www.npmjs.com/package/@backstage/core-components), and [`@backstage/core-plugin-api`](https://www.npmjs.com/package/@backstage/core-plugin-api) - -This package used to provide the core API used by Backstage plugins and apps. - -## Documentation - -- [Backstage Readme](https://github.com/backstage/backstage/blob/master/README.md) -- [Backstage Documentation](https://github.com/backstage/backstage/blob/master/docs/README.md) diff --git a/packages/core-api/package.json b/packages/core-api/package.json deleted file mode 100644 index c6a6b43401..0000000000 --- a/packages/core-api/package.json +++ /dev/null @@ -1,62 +0,0 @@ -{ - "name": "@backstage/core-api", - "description": "Internal Core API used by Backstage plugins and apps", - "version": "0.2.23", - "private": false, - "publishConfig": { - "access": "public", - "main": "dist/index.esm.js", - "types": "dist/index.d.ts" - }, - "homepage": "https://backstage.io", - "repository": { - "type": "git", - "url": "https://github.com/backstage/backstage", - "directory": "packages/core-api" - }, - "keywords": [ - "backstage" - ], - "license": "Apache-2.0", - "main": "src/index.ts", - "types": "src/index.ts", - "scripts": { - "build": "backstage-cli build --outputs types,esm", - "lint": "backstage-cli lint", - "test": "backstage-cli test", - "prepack": "backstage-cli prepack", - "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean" - }, - "dependencies": { - "@backstage/config": "^0.1.4", - "@backstage/core-plugin-api": "^0.1.3", - "@backstage/theme": "^0.2.8", - "@material-ui/core": "^4.11.0", - "@material-ui/icons": "^4.9.1", - "@types/react": "^16.9", - "@types/prop-types": "^15.7.3", - "prop-types": "^15.7.2", - "react": "^16.12.0", - "react-router-dom": "6.0.0-beta.0", - "react-use": "^17.2.4", - "zen-observable": "^0.8.15" - }, - "devDependencies": { - "@backstage/cli": "^0.7.2", - "@backstage/test-utils": "^0.1.14", - "@backstage/test-utils-core": "^0.1.1", - "@testing-library/jest-dom": "^5.10.1", - "@testing-library/react": "^11.2.5", - "@testing-library/react-hooks": "^3.3.0", - "@testing-library/user-event": "^13.1.8", - "@types/jest": "^26.0.7", - "@types/node": "^14.14.32", - "@types/zen-observable": "^0.8.0", - "cross-fetch": "^3.0.6", - "msw": "^0.29.0" - }, - "files": [ - "dist" - ] -} diff --git a/packages/core-api/src/apis/definitions/AlertApi.ts b/packages/core-api/src/apis/definitions/AlertApi.ts deleted file mode 100644 index 22dd97ee38..0000000000 --- a/packages/core-api/src/apis/definitions/AlertApi.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 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 { createApiRef, ApiRef } from '../system'; -import { Observable } from '../../types'; - -export type AlertMessage = { - message: string; - // Severity will default to success since that is what material ui defaults the value to. - severity?: 'success' | 'info' | 'warning' | 'error'; -}; - -/** - * The alert API is used to report alerts to the app, and display them to the user. - */ - -export type AlertApi = { - /** - * Post an alert for handling by the application. - */ - post(alert: AlertMessage): void; - - /** - * Observe alerts posted by other parts of the application. - */ - alert$(): Observable; -}; - -export const alertApiRef: ApiRef = createApiRef({ - id: 'core.alert', - description: 'Used to report alerts and forward them to the app', -}); diff --git a/packages/core-api/src/apis/definitions/AppThemeApi.ts b/packages/core-api/src/apis/definitions/AppThemeApi.ts deleted file mode 100644 index 75a2d6a4ec..0000000000 --- a/packages/core-api/src/apis/definitions/AppThemeApi.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { BackstageTheme } from '@backstage/theme'; -import { Observable } from '../../types'; -import { SvgIconProps } from '@material-ui/core'; - -/** - * Describes a theme provided by the app. - */ -export type AppTheme = { - /** - * ID used to remember theme selections. - */ - id: string; - - /** - * Title of the theme - */ - title: string; - - /** - * Theme variant - */ - variant: 'light' | 'dark'; - - /** - * The specialized MaterialUI theme instance. - */ - theme: BackstageTheme; - - /** - * An Icon for the theme mode setting. - */ - icon?: React.ReactElement; -}; - -/** - * The AppThemeApi gives access to the current app theme, and allows switching - * to other options that have been registered as a part of the App. - */ -export type AppThemeApi = { - /** - * Get a list of available themes. - */ - getInstalledThemes(): AppTheme[]; - - /** - * Observe the currently selected theme. A value of undefined means no specific theme has been selected. - */ - activeThemeId$(): Observable; - - /** - * Get the current theme ID. Returns undefined if no specific theme is selected. - */ - getActiveThemeId(): string | undefined; - - /** - * Set a specific theme to use in the app, overriding the default theme selection. - * - * Clear the selection by passing in undefined. - */ - setActiveThemeId(themeId?: string): void; -}; - -export const appThemeApiRef: ApiRef = createApiRef({ - id: 'core.apptheme', - description: 'API Used to configure the app theme, and enumerate options', -}); diff --git a/packages/core-api/src/apis/definitions/ConfigApi.ts b/packages/core-api/src/apis/definitions/ConfigApi.ts deleted file mode 100644 index c060d878bf..0000000000 --- a/packages/core-api/src/apis/definitions/ConfigApi.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { Config } from '@backstage/config'; - -/** - * The Config API is used to provide a mechanism to access the - * runtime configuration of the system. - */ -export type ConfigApi = Config; - -export const configApiRef: ApiRef = createApiRef({ - id: 'core.config', - description: 'Used to access runtime configuration', -}); diff --git a/packages/core-api/src/apis/definitions/DiscoveryApi.ts b/packages/core-api/src/apis/definitions/DiscoveryApi.ts deleted file mode 100644 index 9ae5c8c4f9..0000000000 --- a/packages/core-api/src/apis/definitions/DiscoveryApi.ts +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; - -/** - * The discovery API is used to provide a mechanism for plugins to - * discover the endpoint to use to talk to their backend counterpart. - * - * The purpose of the discovery API is to allow for many different deployment - * setups and routing methods through a central configuration, instead - * of letting each individual plugin manage that configuration. - * - * Implementations of the discovery API can be a simple as a URL pattern - * using the pluginId, but could also have overrides for individual plugins, - * or query a separate discovery service. - */ -export type DiscoveryApi = { - /** - * Returns the HTTP base backend URL for a given plugin, without a trailing slash. - * - * This method must always be called just before making a request, as opposed to - * fetching the URL when constructing an API client. That is to ensure that more - * flexible routing patterns can be supported. - * - * For example, asking for the URL for `auth` may return something - * like `https://backstage.example.com/api/auth` - */ - getBaseUrl(pluginId: string): Promise; -}; - -export const discoveryApiRef: ApiRef = createApiRef({ - id: 'core.discovery', - description: 'Provides service discovery of backend plugins', -}); diff --git a/packages/core-api/src/apis/definitions/ErrorApi.ts b/packages/core-api/src/apis/definitions/ErrorApi.ts deleted file mode 100644 index 86ca407718..0000000000 --- a/packages/core-api/src/apis/definitions/ErrorApi.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; - -/** - * Mirrors the JavaScript Error class, for the purpose of - * providing documentation and optional fields. - */ -type Error = { - name: string; - message: string; - stack?: string; -}; - -/** - * Provides additional information about an error that was posted to the application. - */ -export type ErrorContext = { - // If set to true, this error should not be displayed to the user. Defaults to false. - hidden?: boolean; -}; - -/** - * The error API is used to report errors to the app, and display them to the user. - * - * Plugins can use this API as a method of displaying errors to the user, but also - * to report errors for collection by error reporting services. - * - * If an error can be displayed inline, e.g. as feedback in a form, that should be - * preferred over relying on this API to display the error. The main use of this API - * for displaying errors should be for asynchronous errors, such as a failing background process. - * - * Even if an error is displayed inline, it should still be reported through this API - * if it would be useful to collect or log it for debugging purposes, but with - * the hidden flag set. For example, an error arising from form field validation - * should probably not be reported, while a failed REST call would be useful to report. - */ -export type ErrorApi = { - /** - * Post an error for handling by the application. - */ - post(error: Error, context?: ErrorContext): void; - - /** - * Observe errors posted by other parts of the application. - */ - error$(): Observable<{ error: Error; context?: ErrorContext }>; -}; - -export const errorApiRef: ApiRef = createApiRef({ - id: 'core.error', - description: 'Used to report errors and forward them to the app', -}); diff --git a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts b/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts deleted file mode 100644 index 56fcd7cacf..0000000000 --- a/packages/core-api/src/apis/definitions/FeatureFlagsApi.ts +++ /dev/null @@ -1,86 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; - -/** - * The feature flags API is used to toggle functionality to users across plugins and Backstage. - * - * Plugins can use this API to register feature flags that they have available - * for users to enable/disable, and this API will centralize the current user's - * state of which feature flags they would like to enable. - * - * This is ideal for Backstage plugins, as well as your own App, to trial incomplete - * or unstable upcoming features. Although there will be a common interface for users - * to enable and disable feature flags, this API acts as another way to enable/disable. - */ - -export type FeatureFlag = { - name: string; - pluginId: string; -}; - -export enum FeatureFlagState { - None = 0, - Active = 1, -} - -/** - * Options to use when saving feature flags. - */ -export type FeatureFlagsSaveOptions = { - /** - * The new feature flag states to save. - */ - states: Record; - - /** - * Whether the saves states should be merged into the existing ones, or replace them. - * - * Defaults to false. - */ - merge?: boolean; -}; - -export type UserFlags = {}; - -export interface FeatureFlagsApi { - /** - * Registers a new feature flag. Once a feature flag has been registered it - * can be toggled by users, and read back to enable or disable features. - */ - registerFlag(flag: FeatureFlag): void; - - /** - * Get a list of all registered flags. - */ - getRegisteredFlags(): FeatureFlag[]; - - /** - * Whether the feature flag with the given name is currently activated for the user. - */ - isActive(name: string): boolean; - - /** - * Save the user's choice of feature flag states. - */ - save(options: FeatureFlagsSaveOptions): void; -} - -export const featureFlagsApiRef: ApiRef = createApiRef({ - id: 'core.featureflags', - description: 'Used to toggle functionality in features across Backstage', -}); diff --git a/packages/core-api/src/apis/definitions/IdentityApi.ts b/packages/core-api/src/apis/definitions/IdentityApi.ts deleted file mode 100644 index 222cf12547..0000000000 --- a/packages/core-api/src/apis/definitions/IdentityApi.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { ProfileInfo } from './auth'; - -/** - * The Identity API used to identify and get information about the signed in user. - */ -export type IdentityApi = { - /** - * The ID of the signed in user. This ID is not meant to be presented to the user, but used - * as an opaque string to pass on to backends or use in frontend logic. - * - * TODO: The intention of the user ID is to be able to tie the user to an identity - * that is known by the catalog and/or identity backend. It should for example - * be possible to fetch all owned components using this ID. - */ - getUserId(): string; - - // TODO: getProfile(): Promise - We want this to be async when added, but needs more work. - /** - * The profile of the signed in user. - */ - getProfile(): ProfileInfo; - - /** - * An OpenID Connect ID Token which proves the identity of the signed in user. - * - * The ID token will be undefined if the signed in user does not have a verified - * identity, such as a demo user or mocked user for e2e tests. - */ - getIdToken(): Promise; - - /** - * Sign out the current user - */ - signOut(): Promise; -}; - -export const identityApiRef: ApiRef = createApiRef({ - id: 'core.identity', - description: 'Provides access to the identity of the signed in user', -}); diff --git a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts b/packages/core-api/src/apis/definitions/OAuthRequestApi.ts deleted file mode 100644 index 4e9b2cdbc8..0000000000 --- a/packages/core-api/src/apis/definitions/OAuthRequestApi.ts +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright 2020 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 { IconComponent } from '../../icons/types'; -import { Observable } from '../../types'; -import { ApiRef, createApiRef } from '../system'; - -/** - * Information about the auth provider that we're requesting a login towards. - * - * This should be shown to the user so that they can be informed about what login is being requested - * before a popup is shown. - */ -export type AuthProvider = { - /** - * Title for the auth provider, for example "GitHub" - */ - title: string; - - /** - * Icon for the auth provider. - */ - icon: IconComponent; -}; - -/** - * Describes how to handle auth requests. Both how to show them to the user, and what to do when - * the user accesses the auth request. - */ -export type AuthRequesterOptions = { - /** - * Information about the auth provider, which will be forwarded to auth requests. - */ - provider: AuthProvider; - - /** - * Implementation of the auth flow, which will be called synchronously when - * trigger() is called on an auth requests. - */ - onAuthRequest(scopes: Set): Promise; -}; - -/** - * Function used to trigger new auth requests for a set of scopes. - * - * The returned promise will resolve to the same value returned by the onAuthRequest in the - * AuthRequesterOptions. Or rejected, if the request is rejected. - * - * This function can be called multiple times before the promise resolves. All calls - * will be merged into one request, and the scopes forwarded to the onAuthRequest will be the - * union of all requested scopes. - */ -export type AuthRequester = ( - scopes: Set, -) => Promise; - -/** - * An pending auth request for a single auth provider. The request will remain in this pending - * state until either reject() or trigger() is called. - * - * Any new requests for the same provider are merged into the existing pending request, meaning - * there will only ever be a single pending request for a given provider. - */ -export type PendingAuthRequest = { - /** - * Information about the auth provider, as given in the AuthRequesterOptions - */ - provider: AuthProvider; - - /** - * Rejects the request, causing all pending AuthRequester calls to fail with "RejectedError". - */ - reject: () => void; - - /** - * Trigger the auth request to continue the auth flow, by for example showing a popup. - * - * Synchronously calls onAuthRequest with all scope currently in the request. - */ - trigger(): Promise; -}; - -/** - * Provides helpers for implemented OAuth login flows within Backstage. - */ -export type OAuthRequestApi = { - /** - * A utility for showing login popups or similar things, and merging together multiple requests for - * different scopes into one request that includes all scopes. - * - * The passed in options provide information about the login provider, and how to handle auth requests. - * - * The returned AuthRequester function is used to request login with new scopes. These requests - * are merged together and forwarded to the auth handler, as soon as a consumer of auth requests - * triggers an auth flow. - * - * See AuthRequesterOptions, AuthRequester, and handleAuthRequests for more info. - */ - createAuthRequester( - options: AuthRequesterOptions, - ): AuthRequester; - - /** - * Observers pending auth requests. The returned observable will emit all - * current active auth request, at most one for each created auth requester. - * - * Each request has its own info about the login provider, forwarded from the auth requester options. - * - * Depending on user interaction, the request should either be rejected, or used to trigger the auth handler. - * If the request is rejected, all pending AuthRequester calls will fail with a "RejectedError". - * If a auth is triggered, and the auth handler resolves successfully, then all currently pending - * AuthRequester calls will resolve to the value returned by the onAuthRequest call. - */ - authRequest$(): Observable; -}; - -export const oauthRequestApiRef: ApiRef = createApiRef({ - id: 'core.oauthrequest', - description: 'An API for implementing unified OAuth flows in Backstage', -}); diff --git a/packages/core-api/src/apis/definitions/StorageApi.ts b/packages/core-api/src/apis/definitions/StorageApi.ts deleted file mode 100644 index bc243a65f3..0000000000 --- a/packages/core-api/src/apis/definitions/StorageApi.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; -import { ErrorApi } from './ErrorApi'; - -export type StorageValueChange = { - key: string; - newValue?: T; -}; - -export type CreateStorageApiOptions = { - errorApi: ErrorApi; - namespace?: string; -}; - -export interface StorageApi { - /** - * Create a bucket to store data in. - * @param {String} name Namespace for the storage to be stored under, - * will inherit previous namespaces too - */ - forBucket(name: string): StorageApi; - - /** - * Get the current value for persistent data, use observe$ to be notified of updates. - * - * @param {String} key Unique key associated with the data. - * @return {Object} data The data that should is stored. - */ - get(key: string): T | undefined; - - /** - * Remove persistent data. - * - * @param {String} key Unique key associated with the data. - */ - remove(key: string): Promise; - - /** - * Save persistent data, and emit messages to anyone that is using observe$ for this key - * - * @param {String} key Unique key associated with the data. - */ - set(key: string, data: any): Promise; - - /** - * Observe changes on a particular key in the bucket - * @param {String} key Unique key associated with the data - */ - observe$(key: string): Observable>; -} - -export const storageApiRef: ApiRef = createApiRef({ - id: 'core.storage', - description: 'Provides the ability to store data which is unique to the user', -}); diff --git a/packages/core-api/src/apis/definitions/auth.ts b/packages/core-api/src/apis/definitions/auth.ts deleted file mode 100644 index f71aa5df9c..0000000000 --- a/packages/core-api/src/apis/definitions/auth.ts +++ /dev/null @@ -1,347 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, createApiRef } from '../system'; -import { Observable } from '../../types'; - -/** - * This file contains declarations for common interfaces of auth-related APIs. - * The declarations should be used to signal which type of authentication and - * authorization methods each separate auth provider supports. - * - * For example, a Google OAuth provider that supports OAuth 2 and OpenID Connect, - * would be declared as follows: - * - * const googleAuthApiRef = createApiRef({ ... }) - */ - -/** - * An array of scopes, or a scope string formatted according to the - * auth provider, which is typically a space separated list. - * - * See the documentation for each auth provider for the list of scopes - * supported by each provider. - */ -export type OAuthScope = string | string[]; - -export type AuthRequestOptions = { - /** - * If this is set to true, the user will not be prompted to log in, - * and an empty response will be returned if there is no existing session. - * - * This can be used to perform a check whether the user is logged in, or if you don't - * want to force a user to be logged in, but provide functionality if they already are. - * - * @default false - */ - optional?: boolean; - - /** - * If this is set to true, the request will bypass the regular oauth login modal - * and open the login popup directly. - * - * The method must be called synchronously from a user action for this to work in all browsers. - * - * @default false - */ - instantPopup?: boolean; -}; - -/** - * This API provides access to OAuth 2 credentials. It lets you request access tokens, - * which can be used to act on behalf of the user when talking to APIs. - */ -export type OAuthApi = { - /** - * Requests an OAuth 2 Access Token, optionally with a set of scopes. The access token allows - * you to make requests on behalf of the user, and the copes may grant you broader access, depending - * on the auth provider. - * - * Each auth provider has separate handling of scope, so you need to look at the documentation - * for each one to know what scope you need to request. - * - * This method is cheap and should be called each time an access token is used. Do not for example - * store the access token in React component state, as that could cause the token to expire. Instead - * fetch a new access token for each request. - * - * Be sure to include all required scopes when requesting an access token. When testing your implementation - * it is best to log out the Backstage session and then visit your plugin page directly, as - * you might already have some required scopes in your existing session. Not requesting the correct - * scopes can lead to 403 or other authorization errors, which can be tricky to debug. - * - * If the user has not yet granted access to the provider and the set of requested scopes, the user - * will be prompted to log in. The returned promise will not resolve until the user has - * successfully logged in. The returned promise can be rejected, but only if the user rejects the login request. - */ - getAccessToken( - scope?: OAuthScope, - options?: AuthRequestOptions, - ): Promise; -}; - -/** - * This API provides access to OpenID Connect credentials. It lets you request ID tokens, - * which can be passed to backend services to prove the user's identity. - */ -export type OpenIdConnectApi = { - /** - * Requests an OpenID Connect ID Token. - * - * This method is cheap and should be called each time an ID token is used. Do not for example - * store the id token in React component state, as that could cause the token to expire. Instead - * fetch a new id token for each request. - * - * If the user has not yet logged in to Google inside Backstage, the user will be prompted - * to log in. The returned promise will not resolve until the user has successfully logged in. - * The returned promise can be rejected, but only if the user rejects the login request. - */ - getIdToken(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to profile information of the user from an auth provider. - */ -export type ProfileInfoApi = { - /** - * Get profile information for the user as supplied by this auth provider. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details. - */ - getProfile(options?: AuthRequestOptions): Promise; -}; - -/** - * This API provides access to the user's identity within Backstage. - * - * An auth provider that implements this interface can be used to sign-in to backstage. It is - * not intended to be used directly from a plugin, but instead serves as a connection between - * this authentication method and the app's @IdentityApi - */ -export type BackstageIdentityApi = { - /** - * Get the user's identity within Backstage. This should normally not be called directly, - * use the @IdentityApi instead. - * - * If the optional flag is not set, a session is guaranteed to be returned, while if - * the optional flag is set, the session may be undefined. See @AuthRequestOptions for more details. - */ - getBackstageIdentity( - options?: AuthRequestOptions, - ): Promise; -}; - -export type BackstageIdentity = { - /** - * The backstage user ID. - */ - id: string; - - /** - * An ID token that can be used to authenticate the user within Backstage. - */ - idToken: string; -}; - -/** - * Profile information of the user. - */ -export type ProfileInfo = { - /** - * Email ID. - */ - email?: string; - - /** - * Display name that can be presented to the user. - */ - displayName?: string; - - /** - * URL to an avatar image of the user. - */ - picture?: string; -}; - -/** - * Session state values passed to subscribers of the SessionApi. - */ -export enum SessionState { - SignedIn = 'SignedIn', - SignedOut = 'SignedOut', -} - -/** - * The SessionApi provides basic controls for any auth provider that is tied to a persistent session. - */ -export type SessionApi = { - /** - * Sign in with a minimum set of permissions. - */ - signIn(): Promise; - - /** - * Sign out from the current session. This will reload the page. - */ - signOut(): Promise; - - /** - * Observe the current state of the auth session. Emits the current state on subscription. - */ - sessionState$(): Observable; -}; - -/** - * Provides authentication towards Google APIs and identities. - * - * See https://developers.google.com/identity/protocols/googlescopes for a full list of supported scopes. - * - * Note that the ID token payload is only guaranteed to contain the user's numerical Google ID, - * email and expiration information. Do not rely on any other fields, as they might not be present. - */ -export const googleAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.google', - description: 'Provides authentication towards Google APIs and identities', -}); - -/** - * Provides authentication towards GitHub APIs. - * - * See https://developer.github.com/apps/building-oauth-apps/understanding-scopes-for-oauth-apps/ - * for a full list of supported scopes. - */ -export const githubAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.github', - description: 'Provides authentication towards GitHub APIs', -}); - -/** - * Provides authentication towards Okta APIs. - * - * See https://developer.okta.com/docs/guides/implement-oauth-for-okta/scopes/ - * for a full list of supported scopes. - */ -export const oktaAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.okta', - description: 'Provides authentication towards Okta APIs', -}); - -/** - * Provides authentication towards GitLab APIs. - * - * See https://docs.gitlab.com/ee/user/profile/personal_access_tokens.html#limiting-scopes-of-a-personal-access-token - * for a full list of supported scopes. - */ -export const gitlabAuthApiRef: ApiRef< - OAuthApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.gitlab', - description: 'Provides authentication towards GitLab APIs', -}); - -/** - * Provides authentication towards Auth0 APIs. - * - * See https://auth0.com/docs/scopes/current/oidc-scopes - * for a full list of supported scopes. - */ -export const auth0AuthApiRef: ApiRef< - OpenIdConnectApi & ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.auth0', - description: 'Provides authentication towards Auth0 APIs', -}); - -/** - * Provides authentication towards Microsoft APIs and identities. - * - * For more info and a full list of supported scopes, see: - * - https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-permissions-and-consent - * - https://docs.microsoft.com/en-us/graph/permissions-reference - */ -export const microsoftAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.microsoft', - description: 'Provides authentication towards Microsoft APIs and identities', -}); - -/** - * Provides authentication for custom identity providers. - */ -export const oauth2ApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.oauth2', - description: 'Example of how to use oauth2 custom provider', -}); - -/** - * Provides authentication for custom OpenID Connect identity providers. - */ -export const oidcAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.oidc', - description: 'Example of how to use oidc custom provider', -}); - -/** - * Provides authentication for saml based identity providers - */ -export const samlAuthApiRef: ApiRef< - ProfileInfoApi & BackstageIdentityApi & SessionApi -> = createApiRef({ - id: 'core.auth.saml', - description: 'Example of how to use SAML custom provider', -}); - -export const oneloginAuthApiRef: ApiRef< - OAuthApi & - OpenIdConnectApi & - ProfileInfoApi & - BackstageIdentityApi & - SessionApi -> = createApiRef({ - id: 'core.auth.onelogin', - description: 'Provides authentication towards OneLogin APIs and identities', -}); diff --git a/packages/core-api/src/apis/definitions/index.ts b/packages/core-api/src/apis/definitions/index.ts deleted file mode 100644 index d4350ddbf6..0000000000 --- a/packages/core-api/src/apis/definitions/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 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. - */ - -// This folder contains definitions for all core APIs. -// -// Plugins should rely on these APIs for functionality as much as possible. -// -// If you think some API definition is missing, please open an Issue or send a PR! - -export * from './auth'; - -export * from './AlertApi'; -export * from './AppThemeApi'; -export * from './ConfigApi'; -export * from './DiscoveryApi'; -export * from './ErrorApi'; -export * from './FeatureFlagsApi'; -export * from './IdentityApi'; -export * from './OAuthRequestApi'; -export * from './StorageApi'; diff --git a/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts b/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts deleted file mode 100644 index 9f7adc260d..0000000000 --- a/packages/core-api/src/apis/implementations/AlertApi/AlertApiForwarder.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 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 { PublishSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; -import { AlertApi, AlertMessage } from '../../definitions'; - -/** - * Base implementation for the AlertApi that simply forwards alerts to consumers. - */ -export class AlertApiForwarder implements AlertApi { - private readonly subject = new PublishSubject(); - - post(alert: AlertMessage) { - this.subject.next(alert); - } - - alert$(): Observable { - return this.subject; - } -} diff --git a/packages/core-api/src/apis/implementations/AlertApi/index.ts b/packages/core-api/src/apis/implementations/AlertApi/index.ts deleted file mode 100644 index d572845809..0000000000 --- a/packages/core-api/src/apis/implementations/AlertApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { AlertApiForwarder } from './AlertApiForwarder'; diff --git a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts deleted file mode 100644 index 363e829ac2..0000000000 --- a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.test.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020 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 { AppTheme } from '../../definitions'; -import { AppThemeSelector } from './AppThemeSelector'; - -describe('AppThemeSelector', () => { - it('should should select new themes', async () => { - const selector = new AppThemeSelector([]); - - expect(selector.getInstalledThemes()).toEqual([]); - - const subFn = jest.fn(); - selector.activeThemeId$().subscribe(subFn); - expect(selector.getActiveThemeId()).toBe(undefined); - await 'wait a tick'; - expect(subFn).toHaveBeenLastCalledWith(undefined); - - selector.setActiveThemeId('x'); - expect(subFn).toHaveBeenLastCalledWith('x'); - expect(selector.getActiveThemeId()).toBe('x'); - - selector.setActiveThemeId(undefined); - expect(subFn).toHaveBeenLastCalledWith(undefined); - expect(selector.getActiveThemeId()).toBe(undefined); - }); - - it('should return a new array of themes', () => { - const themes = new Array(); - const selector = new AppThemeSelector(themes); - - expect(selector.getInstalledThemes()).toEqual(themes); - expect(selector.getInstalledThemes()).not.toBe(themes); - }); - - it('should store theme in local storage', async () => { - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe( - undefined, - ); - localStorage.setItem('theme', 'x'); - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe('x'); - localStorage.removeItem('theme'); - expect(AppThemeSelector.createWithStorage([]).getActiveThemeId()).toBe( - undefined, - ); - - const addListenerSpy = jest.spyOn(window, 'addEventListener'); - const selector = AppThemeSelector.createWithStorage([]); - - expect(addListenerSpy).toHaveBeenCalledTimes(1); - expect(addListenerSpy).toHaveBeenCalledWith( - 'storage', - expect.any(Function), - ); - - selector.setActiveThemeId('y'); - await 'wait a tick'; - expect(localStorage.getItem('theme')).toBe('y'); - - selector.setActiveThemeId(undefined); - await 'wait a tick'; - expect(localStorage.getItem('theme')).toBe(null); - - localStorage.setItem('theme', 'z'); - expect(selector.getActiveThemeId()).toBe(undefined); - - const listener = addListenerSpy.mock.calls[0][1] as EventListener; - listener({ key: 'theme' } as StorageEvent); - - expect(selector.getActiveThemeId()).toBe('z'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts b/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts deleted file mode 100644 index 5856199733..0000000000 --- a/packages/core-api/src/apis/implementations/AppThemeApi/AppThemeSelector.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020 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 { AppThemeApi, AppTheme } from '../../definitions'; -import { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -const STORAGE_KEY = 'theme'; - -export class AppThemeSelector implements AppThemeApi { - static createWithStorage(themes: AppTheme[]) { - const selector = new AppThemeSelector(themes); - - if (!window.localStorage) { - return selector; - } - - const initialThemeId = - window.localStorage.getItem(STORAGE_KEY) ?? undefined; - - selector.setActiveThemeId(initialThemeId); - - selector.activeThemeId$().subscribe(themeId => { - if (themeId) { - window.localStorage.setItem(STORAGE_KEY, themeId); - } else { - window.localStorage.removeItem(STORAGE_KEY); - } - }); - - window.addEventListener('storage', event => { - if (event.key === STORAGE_KEY) { - const themeId = localStorage.getItem(STORAGE_KEY) ?? undefined; - selector.setActiveThemeId(themeId); - } - }); - - return selector; - } - - private activeThemeId: string | undefined; - private readonly subject = new BehaviorSubject(undefined); - - constructor(private readonly themes: AppTheme[]) {} - - getInstalledThemes(): AppTheme[] { - return this.themes.slice(); - } - - activeThemeId$(): Observable { - return this.subject; - } - - getActiveThemeId(): string | undefined { - return this.activeThemeId; - } - - setActiveThemeId(themeId?: string): void { - this.activeThemeId = themeId; - this.subject.next(themeId); - } -} diff --git a/packages/core-api/src/apis/implementations/AppThemeApi/index.ts b/packages/core-api/src/apis/implementations/AppThemeApi/index.ts deleted file mode 100644 index b0a314303b..0000000000 --- a/packages/core-api/src/apis/implementations/AppThemeApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 './AppThemeSelector'; diff --git a/packages/core-api/src/apis/implementations/ConfigApi/index.ts b/packages/core-api/src/apis/implementations/ConfigApi/index.ts deleted file mode 100644 index 708c1d4573..0000000000 --- a/packages/core-api/src/apis/implementations/ConfigApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { ConfigReader } from '@backstage/config'; diff --git a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts b/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts deleted file mode 100644 index 9cd666e8a3..0000000000 --- a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.test.ts +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 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 { UrlPatternDiscovery } from './UrlPatternDiscovery'; - -describe('UrlPatternDiscovery', () => { - it('should not require interpolation', async () => { - const discoveryApi = UrlPatternDiscovery.compile('http://example.com'); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'http://example.com', - ); - }); - - it('should use a plain pattern', async () => { - const discoveryApi = UrlPatternDiscovery.compile( - 'http://localhost:7000/{{ pluginId }}', - ); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'http://localhost:7000/my-plugin', - ); - }); - - it('should allow for multiple interpolation points', async () => { - const discoveryApi = UrlPatternDiscovery.compile( - 'https://{{pluginId }}.example.com/api/{{ pluginId}}', - ); - await expect(discoveryApi.getBaseUrl('my-plugin')).resolves.toBe( - 'https://my-plugin.example.com/api/my-plugin', - ); - }); - - it('should validate that the pattern is a valid URL', () => { - expect(() => { - UrlPatternDiscovery.compile('example.com'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: example.com'); - - expect(() => { - UrlPatternDiscovery.compile('http://'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: http://'); - - expect(() => { - UrlPatternDiscovery.compile('abc123'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: abc123'); - - expect(() => { - UrlPatternDiscovery.compile('http://example.com:{{pluginId}}'); - }).toThrow( - 'Invalid discovery URL pattern, Invalid URL: http://example.com:pluginId', - ); - - expect(() => { - UrlPatternDiscovery.compile('/{{pluginId}}'); - }).toThrow('Invalid discovery URL pattern, Invalid URL: /pluginId'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}?forbidden'); - }).toThrow('Invalid discovery URL pattern, URL must not have a query'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}#forbidden'); - }).toThrow('Invalid discovery URL pattern, URL must not have a hash'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/{{pluginId}}/'); - }).toThrow('Invalid discovery URL pattern, URL must not end with a slash'); - - expect(() => { - UrlPatternDiscovery.compile('http://localhost/'); - }).toThrow('Invalid discovery URL pattern, URL must not end with a slash'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts b/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts deleted file mode 100644 index 10d3d90c63..0000000000 --- a/packages/core-api/src/apis/implementations/DiscoveryApi/UrlPatternDiscovery.ts +++ /dev/null @@ -1,58 +0,0 @@ -/* - * Copyright 2020 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 { DiscoveryApi } from '../../definitions/DiscoveryApi'; - -/** - * UrlPatternDiscovery is a lightweight DiscoveryApi implementation. - * It uses a single template string to construct URLs for each plugin. - */ -export class UrlPatternDiscovery implements DiscoveryApi { - /** - * Creates a new UrlPatternDiscovery given a template. The the only - * interpolation done for the template is to replace instances of `{{pluginId}}` - * with the ID of the plugin being requested. - * - * Example pattern: `http://localhost:7000/api/{{ pluginId }}` - */ - static compile(pattern: string): UrlPatternDiscovery { - const parts = pattern.split(/\{\{\s*pluginId\s*\}\}/); - - try { - const urlStr = parts.join('pluginId'); - const url = new URL(urlStr); - if (url.hash) { - throw new Error('URL must not have a hash'); - } - if (url.search) { - throw new Error('URL must not have a query'); - } - if (urlStr.endsWith('/')) { - throw new Error('URL must not end with a slash'); - } - } catch (error) { - throw new Error(`Invalid discovery URL pattern, ${error.message}`); - } - - return new UrlPatternDiscovery(parts); - } - - private constructor(private readonly parts: string[]) {} - - async getBaseUrl(pluginId: string): Promise { - return this.parts.join(pluginId); - } -} diff --git a/packages/core-api/src/apis/implementations/DiscoveryApi/index.ts b/packages/core-api/src/apis/implementations/DiscoveryApi/index.ts deleted file mode 100644 index 184346401f..0000000000 --- a/packages/core-api/src/apis/implementations/DiscoveryApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { UrlPatternDiscovery } from './UrlPatternDiscovery'; diff --git a/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts deleted file mode 100644 index f537205917..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/ErrorAlerter.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 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 { AlertApi, ErrorApi, ErrorContext } from '../../definitions'; - -/** - * Decorates an ErrorApi by also forwarding error messages - * to the alertApi with an 'error' severity. - */ -export class ErrorAlerter implements ErrorApi { - constructor( - private readonly alertApi: AlertApi, - private readonly errorApi: ErrorApi, - ) {} - - post(error: Error, context?: ErrorContext) { - if (!context?.hidden) { - this.alertApi.post({ message: error.message, severity: 'error' }); - } - - return this.errorApi.post(error, context); - } - - error$() { - return this.errorApi.error$(); - } -} diff --git a/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts b/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts deleted file mode 100644 index 226c4f5488..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/ErrorApiForwarder.ts +++ /dev/null @@ -1,36 +0,0 @@ -/* - * Copyright 2020 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 { PublishSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; -import { ErrorApi, ErrorContext } from '../../definitions'; - -/** - * Base implementation for the ErrorApi that simply forwards errors to consumers. - */ -export class ErrorApiForwarder implements ErrorApi { - private readonly subject = new PublishSubject<{ - error: Error; - context?: ErrorContext; - }>(); - - post(error: Error, context?: ErrorContext) { - this.subject.next({ error, context }); - } - - error$(): Observable<{ error: Error; context?: ErrorContext }> { - return this.subject; - } -} diff --git a/packages/core-api/src/apis/implementations/ErrorApi/index.ts b/packages/core-api/src/apis/implementations/ErrorApi/index.ts deleted file mode 100644 index 49e12b4646..0000000000 --- a/packages/core-api/src/apis/implementations/ErrorApi/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 { ErrorAlerter } from './ErrorAlerter'; -export { ErrorApiForwarder } from './ErrorApiForwarder'; diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx b/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx deleted file mode 100644 index 64ab907d5c..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.test.tsx +++ /dev/null @@ -1,222 +0,0 @@ -/* - * Copyright 2020 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 { LocalStorageFeatureFlags } from './LocalStorageFeatureFlags'; -import { FeatureFlagState, FeatureFlagsApi } from '../../definitions'; - -describe('FeatureFlags', () => { - beforeEach(() => { - window.localStorage.clear(); - }); - - describe('getFlags', () => { - let featureFlags: FeatureFlagsApi; - - beforeEach(() => { - featureFlags = new LocalStorageFeatureFlags(); - }); - - it('returns no flags', () => { - expect(featureFlags.getRegisteredFlags()).toEqual([]); - }); - - it('loads flags from local storage', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 1, - 'feature-flag-three': 0, - 'feature-flag-four': 2, - 'feature-flag-five': 'not-valid', - }), - ); - - expect(featureFlags.isActive('feature-flag-one')).toBe(true); - expect(featureFlags.isActive('feature-flag-two')).toBe(true); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - expect(featureFlags.isActive('feature-flag-four')).toBe(false); - expect(featureFlags.isActive('feature-flag-five')).toBe(false); - }); - - it('sets the correct values', () => { - featureFlags.save({ - states: { - 'feature-flag-zero': FeatureFlagState.Active, - }, - }); - - expect(featureFlags.isActive('feature-flag-zero')).toBe(true); - expect(window.localStorage.getItem('featureFlags')).toEqual( - '{"feature-flag-zero":1}', - ); - }); - - it('deletes the correct values', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 0, - 'feature-flag-tree': 1, - 'feature-flag-four': 0, - }), - ); - - featureFlags.save({ - states: { - 'feature-flag-one': FeatureFlagState.None, - 'feature-flag-two': FeatureFlagState.Active, - }, - }); - - expect(window.localStorage.getItem('featureFlags')).toEqual( - '{"feature-flag-two":1}', - ); - }); - - it('clears all values', () => { - window.localStorage.setItem( - 'featureFlags', - JSON.stringify({ - 'feature-flag-one': 1, - 'feature-flag-two': 1, - 'feature-flag-three': 0, - }), - ); - - expect(featureFlags.isActive('feature-flag-one')).toBe(true); - expect(featureFlags.isActive('feature-flag-two')).toBe(true); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - - featureFlags.save({ states: {} }); - - expect(featureFlags.isActive('feature-flag-one')).toBe(false); - expect(featureFlags.isActive('feature-flag-two')).toBe(false); - expect(featureFlags.isActive('feature-flag-three')).toBe(false); - - expect(window.localStorage.getItem('featureFlags')).toEqual('{}'); - }); - }); - - describe('getRegisteredFlags', () => { - let featureFlags: FeatureFlagsApi; - - beforeEach(() => { - featureFlags = new LocalStorageFeatureFlags(); - featureFlags.registerFlag({ - name: 'registered-flag-1', - pluginId: 'plugin-one', - }); - featureFlags.registerFlag({ - name: 'registered-flag-2', - pluginId: 'plugin-one', - }); - featureFlags.registerFlag({ - name: 'registered-flag-3', - pluginId: 'plugin-two', - }); - }); - - it('should return an empty list', () => { - featureFlags = new LocalStorageFeatureFlags(); - expect(featureFlags.getRegisteredFlags()).toEqual([]); - }); - - it('should return an valid list', () => { - expect(featureFlags.getRegisteredFlags()).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - }); - - it('should provide a copy of the list of flags', () => { - const flags = featureFlags.getRegisteredFlags(); - expect(flags).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - flags.splice(2, 1); - expect(flags).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - ]); - expect(featureFlags.getRegisteredFlags()).toEqual([ - { name: 'registered-flag-1', pluginId: 'plugin-one' }, - { name: 'registered-flag-2', pluginId: 'plugin-one' }, - { name: 'registered-flag-3', pluginId: 'plugin-two' }, - ]); - }); - - it('should get the correct values', () => { - const getByName = (name: string) => - featureFlags.getRegisteredFlags().find(flag => flag.name === name); - - expect(getByName('registered-flag-0')).toBeUndefined(); - expect(getByName('registered-flag-1')).toEqual({ - name: 'registered-flag-1', - pluginId: 'plugin-one', - }); - expect(getByName('registered-flag-2')).toEqual({ - name: 'registered-flag-2', - pluginId: 'plugin-one', - }); - expect(getByName('registered-flag-3')).toEqual({ - name: 'registered-flag-3', - pluginId: 'plugin-two', - }); - }); - - it('throws an error if length is less than three characters', () => { - expect(() => - featureFlags.registerFlag({ - name: 'ab', - pluginId: 'plugin-three', - }), - ).toThrow(/minimum length of three characters/i); - }); - - it('throws an error if length is greater than 150 characters', () => { - expect(() => - featureFlags.registerFlag({ - name: - 'loremipsumdolorsitametconsecteturadipiscingelitnuncvitaeportaexaullamcorperturpismaurisutmattisnequemorbisediaculisauguevivamuspulvinarcursuseratblandithendreritquisqueuttinciduntmagnavestibulumblanditaugueat', - pluginId: 'plugin-three', - }), - ).toThrow(/not exceed 150 characters/i); - }); - - it('throws an error if name does not start with a lowercase letter', () => { - expect(() => - featureFlags.registerFlag({ - name: '123456789', - pluginId: 'plugin-three', - }), - ).toThrow(/start with a lowercase letter/i); - }); - - it('throws an error if name contains characters other than lowercase letters, numbers and hyphens', () => { - expect(() => - featureFlags.registerFlag({ - name: 'Invalid_Feature_Flag', - pluginId: 'plugin-three', - }), - ).toThrow(/only contain lowercase letters, numbers and hyphens/i); - }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx b/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx deleted file mode 100644 index 3d47e60de8..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/LocalStorageFeatureFlags.tsx +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright 2020 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 { - FeatureFlagState, - FeatureFlagsApi, - FeatureFlag, - FeatureFlagsSaveOptions, -} from '../../definitions'; - -export function validateFlagName(name: string): void { - if (name.length < 3) { - throw new Error( - `The '${name}' feature flag must have a minimum length of three characters.`, - ); - } - - if (name.length > 150) { - throw new Error( - `The '${name}' feature flag must not exceed 150 characters.`, - ); - } - - if (!name.match(/^[a-z]+[a-z0-9-]+$/)) { - throw new Error( - `The '${name}' feature flag must start with a lowercase letter and only contain lowercase letters, numbers and hyphens. ` + - 'Examples: feature-flag-one, alpha, release-2020', - ); - } -} - -/** - * Create the FeatureFlags implementation based on the API. - */ -export class LocalStorageFeatureFlags implements FeatureFlagsApi { - private registeredFeatureFlags: FeatureFlag[] = []; - private flags?: Map; - - registerFlag(flag: FeatureFlag) { - validateFlagName(flag.name); - this.registeredFeatureFlags.push(flag); - } - - getRegisteredFlags(): FeatureFlag[] { - return this.registeredFeatureFlags.slice(); - } - - isActive(name: string): boolean { - if (!this.flags) { - this.flags = this.load(); - } - return this.flags.get(name) === FeatureFlagState.Active; - } - - save(options: FeatureFlagsSaveOptions): void { - if (!this.flags) { - this.flags = this.load(); - } - if (!options.merge) { - this.flags.clear(); - } - for (const [name, state] of Object.entries(options.states)) { - this.flags.set(name, state); - } - - const enabled = Array.from(this.flags.entries()).filter( - ([, state]) => state === FeatureFlagState.Active, - ); - window.localStorage.setItem( - 'featureFlags', - JSON.stringify(Object.fromEntries(enabled)), - ); - } - - private load(): Map { - try { - const jsonStr = window.localStorage.getItem('featureFlags'); - if (!jsonStr) { - return new Map(); - } - const json = JSON.parse(jsonStr) as unknown; - if (typeof json !== 'object' || json === null || Array.isArray(json)) { - return new Map(); - } - - const entries = Object.entries(json).filter(([name, value]) => { - validateFlagName(name); - return value === FeatureFlagState.Active; - }); - - return new Map(entries); - } catch { - return new Map(); - } - } -} diff --git a/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts b/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts deleted file mode 100644 index fc806c9ae1..0000000000 --- a/packages/core-api/src/apis/implementations/FeatureFlagsApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { LocalStorageFeatureFlags } from './LocalStorageFeatureFlags'; diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts deleted file mode 100644 index d0f137f0be..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.test.ts +++ /dev/null @@ -1,101 +0,0 @@ -/* - * Copyright 2020 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 MockOAuthApi from './MockOAuthApi'; -import PowerIcon from '@material-ui/icons/Power'; - -describe('MockOAuthApi', () => { - it('should trigger all requests', async () => { - const authResult = { is: 'done' }; - const mock = new MockOAuthApi(); - - const authHandler1 = jest.fn().mockImplementation(() => authResult); - const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler1, - }); - - const authHandler2 = jest.fn().mockResolvedValue('other'); - const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler2, - }); - - const promises = [ - requester1(new Set(['a'])), - requester1(new Set(['b'])), - requester2(new Set(['a', 'b'])), - requester2(new Set(['b', 'c'])), - requester2(new Set(['c', 'a'])), - ]; - - await expect( - Promise.race([Promise.all(promises), 'waiting']), - ).resolves.toBe('waiting'); - - await mock.triggerAll(); - - await expect(Promise.all(promises)).resolves.toEqual([ - authResult, - authResult, - 'other', - 'other', - 'other', - ]); - - expect(authHandler1).toHaveBeenCalledTimes(1); - expect(authHandler1).toHaveBeenCalledWith(new Set(['a', 'b'])); - expect(authHandler2).toHaveBeenCalledTimes(1); - expect(authHandler2).toHaveBeenCalledWith(new Set(['a', 'b', 'c'])); - }); - - it('should reject all requests', async () => { - const mock = new MockOAuthApi(); - - const authHandler1 = jest.fn(); - const requester1 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler1, - }); - - const authHandler2 = jest.fn(); - const requester2 = mock.createAuthRequester({ - provider: { icon: PowerIcon, title: 'Test' }, - onAuthRequest: authHandler2, - }); - - const promises = [ - requester1(new Set(['a'])), - requester1(new Set(['b'])), - requester2(new Set(['a', 'b'])), - requester2(new Set(['b', 'c'])), - requester2(new Set(['c', 'a'])), - ]; - - await expect( - Promise.race([Promise.all(promises), 'waiting']), - ).resolves.toBe('waiting'); - - await mock.rejectAll(); - - for (const promise of promises) { - await expect(promise).rejects.toMatchObject({ name: 'RejectedError' }); - } - - expect(authHandler1).not.toHaveBeenCalled(); - expect(authHandler2).not.toHaveBeenCalled(); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts deleted file mode 100644 index 7c37d702d6..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/MockOAuthApi.ts +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright 2020 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 { OAuthRequestApi, AuthRequesterOptions } from '../../definitions'; -import { OAuthRequestManager } from './OAuthRequestManager'; - -export default class MockOAuthApi implements OAuthRequestApi { - private readonly real = new OAuthRequestManager(); - - createAuthRequester(options: AuthRequesterOptions) { - return this.real.createAuthRequester(options); - } - - authRequest$() { - return this.real.authRequest$(); - } - - async triggerAll() { - await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - - return new Promise(resolve => { - const subscription = this.authRequest$().subscribe(requests => { - subscription.unsubscribe(); - Promise.all(requests.map(request => request.trigger())).then(() => - resolve(), - ); - }); - }); - } - - async rejectAll() { - await Promise.resolve(); // Wait a tick to allow new requests to get forwarded - - return new Promise(resolve => { - const subscription = this.authRequest$().subscribe(requests => { - subscription.unsubscribe(); - requests.map(request => request.reject()); - resolve(); - }); - }); - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts deleted file mode 100644 index 20b0cbba67..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.test.ts +++ /dev/null @@ -1,96 +0,0 @@ -/* - * Copyright 2020 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 { waitFor } from '@testing-library/react'; -import { OAuthPendingRequests } from './OAuthPendingRequests'; - -describe('OAuthPendingRequests', () => { - it('notifies new observers about current state', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - - const input = new Set(['a', 'b']); - target.pending().subscribe({ next, error }); - target.request(input); - - await waitFor(() => expect(next).toBeCalledTimes(2)); - expect(next.mock.calls[0][0].scopes).toBeUndefined(); - expect(next.mock.calls[1][0].scopes.toString()).toBe(input.toString()); - expect(error.mock.calls.length).toBe(0); - }); - - it('resolves requests and notifies observers', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - const request2 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - target.resolve(new Set(['a']), 'session1'); - target.resolve(new Set(['a']), 'session2'); - - await expect(request1).resolves.toBe('session1'); - await expect(request2).resolves.toBe('session1'); - expect(next).toBeCalledTimes(3); // once on subscription, twice on resolve - expect(error).toBeCalledTimes(0); - }); - - it('can resolve through the observable', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(pendingRequest => pendingRequest.resolve('done')); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - - await expect(request1).resolves.toBe('done'); - expect(next).toBeCalledTimes(2); // once with data on subscription, once empty after resolution - expect(error).toBeCalledTimes(0); - }); - - it('rejects requests and notifies observers only once', async () => { - const target = new OAuthPendingRequests(); - const next = jest.fn(); - const error = jest.fn(); - const rejection = new Error('eek'); - - const request1 = target.request(new Set(['a'])); - const request2 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - target.reject(rejection); - target.resolve(new Set(['a']), 'session'); - - await expect(request1).rejects.toBe(rejection); - await expect(request2).rejects.toBe(rejection); - expect(next).toBeCalledTimes(3); // once on subscription, once or reject, once on resolve - expect(error).toBeCalledTimes(0); - }); - - it('can reject through the observable', async () => { - const target = new OAuthPendingRequests(); - const rejection = new Error('nope'); - const next = jest.fn(pendingRequest => pendingRequest.reject(rejection)); - const error = jest.fn(); - - const request1 = target.request(new Set(['a'])); - target.pending().subscribe({ next, error }); - - await expect(request1).rejects.toBe(rejection); - expect(next).toBeCalledTimes(2); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts deleted file mode 100644 index 9e65ca007f..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthPendingRequests.ts +++ /dev/null @@ -1,126 +0,0 @@ -/* - * Copyright 2020 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 { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -type RequestQueueEntry = { - scopes: Set; - resolve: (value: ResultType | PromiseLike) => void; - reject: (reason: Error) => void; -}; - -export type PendingRequest = { - scopes: Set | undefined; - resolve: (value: ResultType) => void; - reject: (reason: Error) => void; -}; - -export function hasScopes( - searched: Set, - searchFor: Set, -): boolean { - for (const scope of searchFor) { - if (!searched.has(scope)) { - return false; - } - } - return true; -} - -export function joinScopes( - scopes: Set, - ...moreScopess: Set[] -): Set { - const result = new Set(scopes); - - for (const moreScopes of moreScopess) { - for (const scope of moreScopes) { - result.add(scope); - } - } - - return result; -} - -/** - * The OAuthPendingRequests class is a utility for managing and observing - * a stream of requests for oauth scopes for a single provider, and resolving - * them correctly once requests are fulfilled. - */ -export class OAuthPendingRequests { - private requests: RequestQueueEntry[] = []; - private subject = new BehaviorSubject>( - this.getCurrentPending(), - ); - - request(scopes: Set): Promise { - return new Promise((resolve, reject) => { - this.requests.push({ scopes, resolve, reject }); - - this.subject.next(this.getCurrentPending()); - }); - } - - resolve(scopes: Set, result: ResultType): void { - this.requests = this.requests.filter(request => { - if (hasScopes(scopes, request.scopes)) { - request.resolve(result); - return false; - } - return true; - }); - - this.subject.next(this.getCurrentPending()); - } - - reject(error: Error) { - this.requests.forEach(request => request.reject(error)); - this.requests = []; - - this.subject.next(this.getCurrentPending()); - } - - pending(): Observable> { - return this.subject; - } - - private getCurrentPending(): PendingRequest { - const currentScopes = - this.requests.length === 0 - ? undefined - : this.requests - .slice(1) - .reduce( - (acc, current) => joinScopes(acc, current.scopes), - this.requests[0].scopes, - ); - - return { - scopes: currentScopes, - resolve: (value: ResultType) => { - if (currentScopes) { - this.resolve(currentScopes, value); - } - }, - reject: (reason: Error) => { - if (currentScopes) { - this.reject(reason); - } - }, - }; - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts deleted file mode 100644 index d8faf90229..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.test.ts +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 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 ProviderIcon from '@material-ui/icons/AcUnit'; -import { OAuthRequestManager } from './OAuthRequestManager'; - -describe('OAuthRequestManager', () => { - it('should forward a requests', async () => { - const manager = new OAuthRequestManager(); - - const reqSpy = jest.fn(); - manager.authRequest$().subscribe(reqSpy); - - const requester = manager.createAuthRequester({ - provider: { - title: 'My Provider', - icon: ProviderIcon, - }, - onAuthRequest: async () => 'hello', - }); - - expect(reqSpy).toHaveBeenCalledTimes(0); - await 'a tick'; - expect(reqSpy).toHaveBeenCalledTimes(2); - expect(reqSpy).toHaveBeenLastCalledWith([]); - - const req = requester(new Set(['my-scope'])); - - expect(reqSpy).toHaveBeenCalledTimes(3); - expect(reqSpy).toHaveBeenLastCalledWith([ - expect.objectContaining({ - reject: expect.any(Function), - trigger: expect.any(Function), - }), - ]); - - await expect(Promise.race([req, Promise.resolve('not yet')])).resolves.toBe( - 'not yet', - ); - - const [request] = reqSpy.mock.calls[2][0]; - request.trigger(); - - await expect(req).resolves.toBe('hello'); - }); -}); diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts deleted file mode 100644 index c8cea322e8..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/OAuthRequestManager.ts +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright 2020 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 { - OAuthRequestApi, - PendingAuthRequest, - AuthRequester, - AuthRequesterOptions, -} from '../../definitions'; -import { OAuthPendingRequests, PendingRequest } from './OAuthPendingRequests'; -import { BehaviorSubject } from '../../../lib/subjects'; -import { Observable } from '../../../types'; - -/** - * The OAuthRequestManager is an implementation of the OAuthRequestApi. - * - * The purpose of this class and the API is to read a stream of incoming requests - * of OAuth access tokens from different providers with varying scope, and funnel - * them all together into a single request for each OAuth provider. - */ -export class OAuthRequestManager implements OAuthRequestApi { - private readonly subject = new BehaviorSubject([]); - private currentRequests: PendingAuthRequest[] = []; - private handlerCount = 0; - - createAuthRequester(options: AuthRequesterOptions): AuthRequester { - const handler = new OAuthPendingRequests(); - - const index = this.handlerCount; - this.handlerCount++; - - handler.pending().subscribe({ - next: scopeRequest => { - const newRequests = this.currentRequests.slice(); - const request = this.makeAuthRequest(scopeRequest, options); - if (!request) { - delete newRequests[index]; - } else { - newRequests[index] = request; - } - this.currentRequests = newRequests; - // Convert from sparse array to array of present items only - this.subject.next(newRequests.filter(Boolean)); - }, - }); - - return scopes => { - return handler.request(scopes); - }; - } - - // Converts the pending request and popup options into a popup request that we can forward to subscribers. - private makeAuthRequest( - request: PendingRequest, - options: AuthRequesterOptions, - ): PendingAuthRequest | undefined { - const { scopes } = request; - if (!scopes) { - return undefined; - } - - return { - provider: options.provider, - trigger: async () => { - const result = await options.onAuthRequest(scopes); - request.resolve(result); - }, - reject: () => { - const error = new Error('Login failed, rejected by user'); - error.name = 'RejectedError'; - request.reject(error); - }, - }; - } - - authRequest$(): Observable { - return this.subject; - } -} diff --git a/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts b/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts deleted file mode 100644 index 72e42872fd..0000000000 --- a/packages/core-api/src/apis/implementations/OAuthRequestApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { OAuthRequestManager } from './OAuthRequestManager'; diff --git a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts deleted file mode 100644 index 8551eed4a6..0000000000 --- a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.test.ts +++ /dev/null @@ -1,173 +0,0 @@ -/* - * Copyright 2020 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 { WebStorage } from './WebStorage'; -import { CreateStorageApiOptions, StorageApi } from '../../definitions'; - -describe('WebStorage Storage API', () => { - const mockErrorApi = { post: jest.fn(), error$: jest.fn() }; - const createWebStorage = ( - args?: Partial, - ): StorageApi => { - return WebStorage.create({ - errorApi: mockErrorApi, - ...args, - }); - }; - it('should return undefined for values which are unset', async () => { - const storage = createWebStorage(); - - expect(storage.get('myfakekey')).toBeUndefined(); - }); - - it('should allow the setting and getting of the simple data structures', async () => { - const storage = createWebStorage(); - - await storage.set('myfakekey', 'helloimastring'); - await storage.set('mysecondfakekey', 1234); - await storage.set('mythirdfakekey', true); - expect(storage.get('myfakekey')).toBe('helloimastring'); - expect(storage.get('mysecondfakekey')).toBe(1234); - expect(storage.get('mythirdfakekey')).toBe(true); - }); - - it('should allow setting of complex datastructures', async () => { - const storage = createWebStorage(); - - const mockData = { - something: 'here', - is: [{ super: { complex: [{ but: 'something', why: true }] } }], - }; - - await storage.set('myfakekey', mockData); - - expect(storage.get('myfakekey')).toEqual(mockData); - }); - - it('should subscribe to key changes when setting a new value', async () => { - const storage = createWebStorage(); - - const wrongKeyNextHandler = jest.fn(); - const selectedKeyNextHandler = jest.fn(); - const mockData = { hello: 'im a great new value' }; - - await new Promise(resolve => { - storage.observe$('correctKey').subscribe({ - next: (...args) => { - selectedKeyNextHandler(...args); - resolve(); - }, - }); - - storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); - - storage.set('correctKey', mockData); - }); - - expect(wrongKeyNextHandler).not.toHaveBeenCalled(); - expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); - expect(selectedKeyNextHandler).toHaveBeenCalledWith({ - key: 'correctKey', - newValue: mockData, - }); - }); - - it('should subscribe to key changes when deleting a value', async () => { - const storage = createWebStorage(); - - const wrongKeyNextHandler = jest.fn(); - const selectedKeyNextHandler = jest.fn(); - const mockData = { hello: 'im a great new value' }; - - storage.set('correctKey', mockData); - - await new Promise(resolve => { - storage.observe$('correctKey').subscribe({ - next: (...args) => { - selectedKeyNextHandler(...args); - resolve(); - }, - }); - - storage.observe$('wrongKey').subscribe({ next: wrongKeyNextHandler }); - - storage.remove('correctKey'); - }); - - expect(wrongKeyNextHandler).not.toHaveBeenCalled(); - expect(selectedKeyNextHandler).toHaveBeenCalledTimes(1); - expect(selectedKeyNextHandler).toHaveBeenCalledWith({ - key: 'correctKey', - newValue: undefined, - }); - }); - - it('should be able to create different buckets for different uses', async () => { - const rootStorage = createWebStorage(); - - const firstStorage = rootStorage.forBucket('userSettings'); - const secondStorage = rootStorage.forBucket('profileSettings'); - const keyName = 'blobby'; - - await firstStorage.set(keyName, 'boop'); - await secondStorage.set(keyName, 'deerp'); - - expect(firstStorage.get(keyName)).not.toBe(secondStorage.get(keyName)); - expect(firstStorage.get(keyName)).toBe('boop'); - expect(secondStorage.get(keyName)).toBe('deerp'); - }); - - it('should not clash with other namesapces when creating buckets', async () => { - const rootStorage = createWebStorage(); - - // when getting key test2 it will translate to /profile/something/deep/test2 - const firstStorage = rootStorage - .forBucket('profile') - .forBucket('something') - .forBucket('deep'); - // when getting key deep/test2 it will translate to /profile/something/deep/test2 - const secondStorage = rootStorage.forBucket('profile/something'); - - await firstStorage.set('test2', { error: true }); - - expect(secondStorage.get('deep/test2')).toBe(undefined); - }); - - it('should call the error api when the json can not be parsed in local storage', async () => { - const rootStorage = createWebStorage({ - namespace: '/Test/Mock/Thing', - }); - - localStorage.setItem('/Test/Mock/Thing/key', '{smd: asdouindA}'); - - const value = rootStorage.get('key'); - - expect(value).toBe(undefined); - expect(mockErrorApi.post).toHaveBeenCalledWith(expect.any(Error)); - expect(mockErrorApi.post).toHaveBeenCalledWith( - expect.objectContaining({ - message: 'Error when parsing JSON config from storage for: key', - }), - ); - }); - - it('should return a singleton for the same namespace and same bucket', async () => { - const rootStorage = createWebStorage({ - namespace: '/Test/Mock/Thing/Thing ', - }); - - expect(rootStorage.forBucket('test')).toBe(rootStorage.forBucket('test')); - }); -}); diff --git a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts b/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts deleted file mode 100644 index 1b4f4a88bb..0000000000 --- a/packages/core-api/src/apis/implementations/StorageApi/WebStorage.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020 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 { - StorageApi, - StorageValueChange, - ErrorApi, - CreateStorageApiOptions, -} from '../../definitions'; -import { Observable } from '../../../types'; -import ObservableImpl from 'zen-observable'; - -const buckets = new Map(); - -export class WebStorage implements StorageApi { - constructor( - private readonly namespace: string, - private readonly errorApi: ErrorApi, - ) {} - - static create(options: CreateStorageApiOptions): WebStorage { - return new WebStorage(options.namespace ?? '', options.errorApi); - } - - get(key: string): T | undefined { - try { - const storage = JSON.parse(localStorage.getItem(this.getKeyName(key))!); - return storage ?? undefined; - } catch (e) { - this.errorApi.post( - new Error(`Error when parsing JSON config from storage for: ${key}`), - ); - } - - return undefined; - } - - forBucket(name: string): WebStorage { - const bucketPath = `${this.namespace}/${name}`; - if (!buckets.has(bucketPath)) { - buckets.set(bucketPath, new WebStorage(bucketPath, this.errorApi)); - } - return buckets.get(bucketPath)!; - } - - async set(key: string, data: T): Promise { - localStorage.setItem(this.getKeyName(key), JSON.stringify(data, null, 2)); - this.notifyChanges({ key, newValue: data }); - } - - async remove(key: string): Promise { - localStorage.removeItem(this.getKeyName(key)); - this.notifyChanges({ key, newValue: undefined }); - } - - observe$(key: string): Observable> { - return this.observable.filter(({ key: messageKey }) => messageKey === key); - } - - private getKeyName(key: string) { - return `${this.namespace}/${encodeURIComponent(key)}`; - } - - private notifyChanges(message: StorageValueChange) { - for (const subscription of this.subscribers) { - subscription.next(message); - } - } - - private subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - private readonly observable = new ObservableImpl( - subscriber => { - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }, - ); -} diff --git a/packages/core-api/src/apis/implementations/StorageApi/index.ts b/packages/core-api/src/apis/implementations/StorageApi/index.ts deleted file mode 100644 index 2f941381fd..0000000000 --- a/packages/core-api/src/apis/implementations/StorageApi/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { WebStorage } from './WebStorage'; diff --git a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts b/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts deleted file mode 100644 index 2dac460fbe..0000000000 --- a/packages/core-api/src/apis/implementations/auth/auth0/Auth0Auth.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 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 Auth0Icon from '@material-ui/icons/AcUnit'; -import { auth0AuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'auth0', - title: 'Auth0', - icon: Auth0Icon, -}; - -class Auth0Auth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['openid', `email`, `profile`], - }: OAuthApiCreateOptions): typeof auth0AuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} - -export default Auth0Auth; diff --git a/packages/core-api/src/apis/implementations/auth/auth0/index.ts b/packages/core-api/src/apis/implementations/auth/auth0/index.ts deleted file mode 100644 index 9daed7d13e..0000000000 --- a/packages/core-api/src/apis/implementations/auth/auth0/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as Auth0Auth } from './Auth0Auth'; diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts deleted file mode 100644 index 04bfff028d..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.test.ts +++ /dev/null @@ -1,29 +0,0 @@ -/* - * Copyright 2020 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 GithubAuth from './GithubAuth'; - -describe('GithubAuth', () => { - it('should get access token', async () => { - const getSession = jest - .fn() - .mockResolvedValue({ providerInfo: { accessToken: 'access-token' } }); - const githubAuth = new GithubAuth({ getSession } as any); - - expect(await githubAuth.getAccessToken()).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts b/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts deleted file mode 100644 index 60ee6a2629..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/GithubAuth.ts +++ /dev/null @@ -1,140 +0,0 @@ -/* - * Copyright 2020 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 GithubIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { GithubSession } from './types'; -import { - OAuthApi, - SessionApi, - SessionState, - ProfileInfo, - BackstageIdentity, - AuthRequestOptions, -} from '../../../definitions/auth'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { - AuthSessionStore, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; -import { Observable } from '../../../../types'; -import { OAuthApiCreateOptions } from '../types'; - -export type GithubAuthResponse = { - providerInfo: { - accessToken: string; - scope: string; - expiresInSeconds: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'github', - title: 'GitHub', - icon: GithubIcon, -}; - -class GithubAuth implements OAuthApi, SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['read:user'], - }: OAuthApiCreateOptions) { - const connector = new DefaultAuthConnector({ - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: GithubAuthResponse): GithubSession { - return { - ...res, - providerInfo: { - accessToken: res.providerInfo.accessToken, - scopes: GithubAuth.normalizeScope(res.providerInfo.scope), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, - }); - - const sessionManager = new StaticAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }); - - const authSessionStore = new AuthSessionStore({ - manager: sessionManager, - storageKey: `${provider.id}Session`, - sessionScopes: (session: GithubSession) => session.providerInfo.scopes, - }); - - return new GithubAuth(authSessionStore); - } - - constructor(private readonly sessionManager: SessionManager) {} - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken(scope?: string, options?: AuthRequestOptions) { - const session = await this.sessionManager.getSession({ - ...options, - scopes: GithubAuth.normalizeScope(scope), - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - static normalizeScope(scope?: string): Set { - if (!scope) { - return new Set(); - } - - const scopeList = Array.isArray(scope) - ? scope - : scope.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeList); - } -} -export default GithubAuth; diff --git a/packages/core-api/src/apis/implementations/auth/github/index.ts b/packages/core-api/src/apis/implementations/auth/github/index.ts deleted file mode 100644 index ee4334f6fc..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 './types'; -export { default as GithubAuth } from './GithubAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/github/types.ts b/packages/core-api/src/apis/implementations/auth/github/types.ts deleted file mode 100644 index 90573d2429..0000000000 --- a/packages/core-api/src/apis/implementations/auth/github/types.ts +++ /dev/null @@ -1,27 +0,0 @@ -/* - * Copyright 2020 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 { BackstageIdentity, ProfileInfo } from '../../../definitions'; - -export type GithubSession = { - providerInfo: { - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts b/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts deleted file mode 100644 index 3346af9b7c..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.test.ts +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2020 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 MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; -import GitlabAuth from './GitlabAuth'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('GitlabAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - [ - 'read_user api write_repository', - ['read_user', 'api', 'write_repository'], - ], - ['read_repository sudo', ['read_repository', 'sudo']], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const gitlabAuth = GitlabAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - gitlabAuth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts b/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts deleted file mode 100644 index 37a3c89dd6..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/GitlabAuth.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 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 GitlabIcon from '@material-ui/icons/AcUnit'; -import { gitlabAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'gitlab', - title: 'GitLab', - icon: GitlabIcon, -}; - -class GitlabAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['read_user'], - }: OAuthApiCreateOptions): typeof gitlabAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} -export default GitlabAuth; diff --git a/packages/core-api/src/apis/implementations/auth/gitlab/index.ts b/packages/core-api/src/apis/implementations/auth/gitlab/index.ts deleted file mode 100644 index 935a11dd54..0000000000 --- a/packages/core-api/src/apis/implementations/auth/gitlab/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as GitlabAuth } from './GitlabAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts deleted file mode 100644 index f8a1c3a1f5..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.test.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020 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 GoogleAuth from './GoogleAuth'; -import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; - -const PREFIX = 'https://www.googleapis.com/auth/'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('GoogleAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - ['email', [`${PREFIX}userinfo.email`]], - ['profile', [`${PREFIX}userinfo.profile`]], - ['openid', ['openid']], - ['userinfo.email', [`${PREFIX}userinfo.email`]], - [ - 'userinfo.profile email', - [`${PREFIX}userinfo.profile`, `${PREFIX}userinfo.email`], - ], - [ - `profile ${PREFIX}userinfo.email`, - [`${PREFIX}userinfo.profile`, `${PREFIX}userinfo.email`], - ], - [`${PREFIX}userinfo.profile`, [`${PREFIX}userinfo.profile`]], - ['a', [`${PREFIX}a`]], - ['a b\tc', [`${PREFIX}a`, `${PREFIX}b`, `${PREFIX}c`]], - [`${PREFIX}a b`, [`${PREFIX}a`, `${PREFIX}b`]], - [`${PREFIX}a`, [`${PREFIX}a`]], - - // Some incorrect scopes that we don't try to fix - [`${PREFIX}email`, [`${PREFIX}email`]], - [`${PREFIX}profile`, [`${PREFIX}profile`]], - [`${PREFIX}openid`, [`${PREFIX}openid`]], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const googleAuth = GoogleAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - googleAuth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts b/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts deleted file mode 100644 index 988ef9c2e0..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/GoogleAuth.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* - * Copyright 2020 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 GoogleIcon from '@material-ui/icons/AcUnit'; -import { googleAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'google', - title: 'Google', - icon: GoogleIcon, -}; - -const SCOPE_PREFIX = 'https://www.googleapis.com/auth/'; - -class GoogleAuth { - static create({ - discoveryApi, - oauthRequestApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - defaultScopes = [ - 'openid', - `${SCOPE_PREFIX}userinfo.email`, - `${SCOPE_PREFIX}userinfo.profile`, - ], - }: OAuthApiCreateOptions): typeof googleAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - scopeTransform(scopes: string[]) { - return scopes.map(scope => { - if (scope === 'openid') { - return scope; - } - - if (scope === 'profile' || scope === 'email') { - return `${SCOPE_PREFIX}userinfo.${scope}`; - } - - if (scope.startsWith(SCOPE_PREFIX)) { - return scope; - } - - return `${SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} -export default GoogleAuth; diff --git a/packages/core-api/src/apis/implementations/auth/google/index.ts b/packages/core-api/src/apis/implementations/auth/google/index.ts deleted file mode 100644 index 96a8699eab..0000000000 --- a/packages/core-api/src/apis/implementations/auth/google/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as GoogleAuth } from './GoogleAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/index.ts b/packages/core-api/src/apis/implementations/auth/index.ts deleted file mode 100644 index 9889a33878..0000000000 --- a/packages/core-api/src/apis/implementations/auth/index.ts +++ /dev/null @@ -1,25 +0,0 @@ -/* - * Copyright 2020 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 './github'; -export * from './gitlab'; -export * from './google'; -export * from './oauth2'; -export * from './okta'; -export * from './saml'; -export * from './auth0'; -export * from './microsoft'; -export * from './onelogin'; diff --git a/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts b/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts deleted file mode 100644 index 5f0e018add..0000000000 --- a/packages/core-api/src/apis/implementations/auth/microsoft/MicrosoftAuth.ts +++ /dev/null @@ -1,52 +0,0 @@ -/* - * Copyright 2020 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 MicrosoftIcon from '@material-ui/icons/AcUnit'; -import { microsoftAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'microsoft', - title: 'Microsoft', - icon: MicrosoftIcon, -}; - -class MicrosoftAuth { - static create({ - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - discoveryApi, - defaultScopes = [ - 'openid', - 'offline_access', - 'profile', - 'email', - 'User.Read', - ], - }: OAuthApiCreateOptions): typeof microsoftAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - }); - } -} - -export default MicrosoftAuth; diff --git a/packages/core-api/src/apis/implementations/auth/microsoft/index.ts b/packages/core-api/src/apis/implementations/auth/microsoft/index.ts deleted file mode 100644 index 44bca3d37c..0000000000 --- a/packages/core-api/src/apis/implementations/auth/microsoft/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as MicrosoftAuth } from './MicrosoftAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts deleted file mode 100644 index 4f030d1c64..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.test.ts +++ /dev/null @@ -1,152 +0,0 @@ -/* - * Copyright 2020 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 OAuth2 from './OAuth2'; - -const theFuture = new Date(Date.now() + 3600000); -const thePast = new Date(Date.now() - 10); - -const PREFIX = 'https://www.googleapis.com/auth/'; - -const scopeTransform = (x: string[]) => x; - -describe('OAuth2', () => { - it('should get refreshed access token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getAccessToken('my-scope my-scope2')).toBe( - 'access-token', - ); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-scope', 'my-scope2']), - ); - }); - - it('should transform scopes', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { accessToken: 'access-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform: scopes => scopes.map(scope => `my-prefix/${scope}`), - }); - - expect(await oauth2.getAccessToken('my-scope')).toBe('access-token'); - expect(getSession).toBeCalledTimes(1); - expect(getSession.mock.calls[0][0].scopes).toEqual( - new Set(['my-prefix/my-scope']), - ); - }); - - it('should get refreshed id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getIdToken()).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should get optional id token', async () => { - const getSession = jest.fn().mockResolvedValue({ - providerInfo: { idToken: 'id-token', expiresAt: theFuture }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - expect(await oauth2.getIdToken({ optional: true })).toBe('id-token'); - expect(getSession).toBeCalledTimes(1); - }); - - it('should share popup closed errors', async () => { - const error = new Error('NOPE'); - error.name = 'RejectedError'; - const getSession = jest - .fn() - .mockResolvedValueOnce({ - providerInfo: { - accessToken: 'access-token', - expiresAt: theFuture, - scopes: new Set([`${PREFIX}not-enough`]), - }, - }) - .mockRejectedValue(error); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Make sure we have a session before we do the double request, so that we get past the !this.currentSession check - await expect(oauth2.getAccessToken()).resolves.toBe('access-token'); - - const promise1 = oauth2.getAccessToken('more'); - const promise2 = oauth2.getAccessToken('more'); - await expect(promise1).rejects.toBe(error); - await expect(promise2).rejects.toBe(error); - expect(getSession).toBeCalledTimes(3); - }); - - it('should wait for all session refreshes', async () => { - const initialSession = { - providerInfo: { - idToken: 'token1', - expiresAt: theFuture, - scopes: new Set(), - }, - }; - const getSession = jest - .fn() - .mockResolvedValueOnce(initialSession) - .mockResolvedValue({ - providerInfo: { - idToken: 'token2', - expiresAt: theFuture, - scopes: new Set(), - }, - }); - const oauth2 = new OAuth2({ - sessionManager: { getSession } as any, - scopeTransform, - }); - - // Grab the expired session first - await expect(oauth2.getIdToken()).resolves.toBe('token1'); - expect(getSession).toBeCalledTimes(1); - - initialSession.providerInfo.expiresAt = thePast; - - const promise1 = oauth2.getIdToken(); - const promise2 = oauth2.getIdToken(); - const promise3 = oauth2.getIdToken(); - await expect(promise1).resolves.toBe('token2'); - await expect(promise2).resolves.toBe('token2'); - await expect(promise3).resolves.toBe('token2'); - expect(getSession).toBeCalledTimes(4); // De-duping of session requests happens in client - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts b/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts deleted file mode 100644 index 10d9792799..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/OAuth2.ts +++ /dev/null @@ -1,179 +0,0 @@ -/* - * Copyright 2020 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 OAuth2Icon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from '../../../../lib/AuthConnector'; -import { RefreshingAuthSessionManager } from '../../../../lib/AuthSessionManager'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { Observable } from '../../../../types'; -import { - AuthRequestOptions, - BackstageIdentity, - OAuthApi, - OpenIdConnectApi, - ProfileInfo, - ProfileInfoApi, - SessionState, - SessionApi, - BackstageIdentityApi, -} from '../../../definitions/auth'; -import { OAuth2Session } from './types'; -import { OAuthApiCreateOptions } from '../types'; - -type Options = { - sessionManager: SessionManager; - scopeTransform: (scopes: string[]) => string[]; -}; - -type CreateOptions = OAuthApiCreateOptions & { - scopeTransform?: (scopes: string[]) => string[]; -}; - -export type OAuth2Response = { - providerInfo: { - accessToken: string; - idToken: string; - scope: string; - expiresInSeconds: number; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'oauth2', - title: 'Your Identity Provider', - icon: OAuth2Icon, -}; - -class OAuth2 - implements - OAuthApi, - OpenIdConnectApi, - ProfileInfoApi, - BackstageIdentityApi, - SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = [], - scopeTransform = x => x, - }: CreateOptions) { - const connector = new DefaultAuthConnector({ - discoveryApi, - environment, - provider, - oauthRequestApi: oauthRequestApi, - sessionTransform(res: OAuth2Response): OAuth2Session { - return { - ...res, - providerInfo: { - idToken: res.providerInfo.idToken, - accessToken: res.providerInfo.accessToken, - scopes: OAuth2.normalizeScopes( - scopeTransform, - res.providerInfo.scope, - ), - expiresAt: new Date( - Date.now() + res.providerInfo.expiresInSeconds * 1000, - ), - }, - }; - }, - }); - - const sessionManager = new RefreshingAuthSessionManager({ - connector, - defaultScopes: new Set(defaultScopes), - sessionScopes: (session: OAuth2Session) => session.providerInfo.scopes, - sessionShouldRefresh: (session: OAuth2Session) => { - const expiresInSec = - (session.providerInfo.expiresAt.getTime() - Date.now()) / 1000; - return expiresInSec < 60 * 5; - }, - }); - - return new OAuth2({ sessionManager, scopeTransform }); - } - - private readonly sessionManager: SessionManager; - private readonly scopeTransform: (scopes: string[]) => string[]; - - constructor(options: Options) { - this.sessionManager = options.sessionManager; - this.scopeTransform = options.scopeTransform; - } - - async signIn() { - await this.getAccessToken(); - } - - async signOut() { - await this.sessionManager.removeSession(); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - async getAccessToken( - scope?: string | string[], - options?: AuthRequestOptions, - ) { - const normalizedScopes = OAuth2.normalizeScopes(this.scopeTransform, scope); - const session = await this.sessionManager.getSession({ - ...options, - scopes: normalizedScopes, - }); - return session?.providerInfo.accessToken ?? ''; - } - - async getIdToken(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.providerInfo.idToken ?? ''; - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } - - private static normalizeScopes( - scopeTransform: (scopes: string[]) => string[], - scopes?: string | string[], - ): Set { - if (!scopes) { - return new Set(); - } - - const scopeList = Array.isArray(scopes) - ? scopes - : scopes.split(/[\s|,]/).filter(Boolean); - - return new Set(scopeTransform(scopeList)); - } -} - -export default OAuth2; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/index.ts b/packages/core-api/src/apis/implementations/auth/oauth2/index.ts deleted file mode 100644 index 793be515b7..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 { default as OAuth2 } from './OAuth2'; -export * from './types'; diff --git a/packages/core-api/src/apis/implementations/auth/oauth2/types.ts b/packages/core-api/src/apis/implementations/auth/oauth2/types.ts deleted file mode 100644 index ca34de9f78..0000000000 --- a/packages/core-api/src/apis/implementations/auth/oauth2/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; - -export type OAuth2Session = { - providerInfo: { - idToken: string; - accessToken: string; - scopes: Set; - expiresAt: Date; - }; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts b/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts deleted file mode 100644 index 6489b326ff..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.test.ts +++ /dev/null @@ -1,61 +0,0 @@ -/* - * Copyright 2020 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 OktaAuth from './OktaAuth'; -import MockOAuthApi from '../../OAuthRequestApi/MockOAuthApi'; -import { UrlPatternDiscovery } from '../../DiscoveryApi'; - -const PREFIX = 'okta.'; - -const getSession = jest.fn(); - -jest.mock('../../../../lib/AuthSessionManager', () => ({ - ...(jest.requireActual('../../../../lib/AuthSessionManager') as any), - RefreshingAuthSessionManager: class { - getSession = getSession; - }, -})); - -describe('OktaAuth', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it.each([ - ['openid', ['openid']], - ['profile email', ['profile', 'email']], - [`${PREFIX}groups.manage`, [`${PREFIX}groups.manage`]], - ['groups.read', [`${PREFIX}groups.read`]], - [ - `${PREFIX}groups.manage groups.read, openid`, - [`${PREFIX}groups.manage`, `${PREFIX}groups.read`, 'openid'], - ], - [`email\t ${PREFIX}groups.read`, ['email', `${PREFIX}groups.read`]], - - // Some incorrect scopes that we don't try to fix - [`${PREFIX}email`, [`${PREFIX}email`]], - [`${PREFIX}profile`, [`${PREFIX}profile`]], - [`${PREFIX}openid`, [`${PREFIX}openid`]], - ])(`should normalize scopes correctly - %p`, (scope, scopes) => { - const auth = OktaAuth.create({ - oauthRequestApi: new MockOAuthApi(), - discoveryApi: UrlPatternDiscovery.compile('http://example.com'), - }); - - auth.getAccessToken(scope); - expect(getSession).toHaveBeenCalledWith({ scopes: new Set(scopes) }); - }); -}); diff --git a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts b/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts deleted file mode 100644 index 3f29a56deb..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/OktaAuth.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 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 OktaIcon from '@material-ui/icons/AcUnit'; -import { oktaAuthApiRef } from '../../../definitions/auth'; -import { OAuth2 } from '../oauth2'; -import { OAuthApiCreateOptions } from '../types'; - -const DEFAULT_PROVIDER = { - id: 'okta', - title: 'Okta', - icon: OktaIcon, -}; - -const OKTA_OIDC_SCOPES: Set = new Set([ - 'openid', - 'profile', - 'email', - 'phone', - 'address', - 'groups', - 'offline_access', -]); - -const OKTA_SCOPE_PREFIX: string = 'okta.'; - -class OktaAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - defaultScopes = ['openid', 'email', 'profile', 'offline_access'], - }: OAuthApiCreateOptions): typeof oktaAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes, - scopeTransform(scopes) { - return scopes.map(scope => { - if (OKTA_OIDC_SCOPES.has(scope)) { - return scope; - } - - if (scope.startsWith(OKTA_SCOPE_PREFIX)) { - return scope; - } - - return `${OKTA_SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} - -export default OktaAuth; diff --git a/packages/core-api/src/apis/implementations/auth/okta/index.ts b/packages/core-api/src/apis/implementations/auth/okta/index.ts deleted file mode 100644 index c20ce4e16b..0000000000 --- a/packages/core-api/src/apis/implementations/auth/okta/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as OktaAuth } from './OktaAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts b/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts deleted file mode 100644 index 6d34e63669..0000000000 --- a/packages/core-api/src/apis/implementations/auth/onelogin/OneLoginAuth.ts +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright 2020 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 OneLoginIcon from '@material-ui/icons/AcUnit'; -import { oneloginAuthApiRef } from '../../../definitions/auth'; -import { - OAuthRequestApi, - AuthProvider, - DiscoveryApi, -} from '../../../definitions'; -import { OAuth2 } from '../oauth2'; - -type CreateOptions = { - discoveryApi: DiscoveryApi; - oauthRequestApi: OAuthRequestApi; - - environment?: string; - provider?: AuthProvider & { id: string }; -}; - -const DEFAULT_PROVIDER = { - id: 'onelogin', - title: 'onelogin', - icon: OneLoginIcon, -}; - -const OIDC_SCOPES: Set = new Set([ - 'openid', - 'profile', - 'email', - 'phone', - 'address', - 'groups', - 'offline_access', -]); - -const SCOPE_PREFIX: string = 'onelogin.'; - -class OneLoginAuth { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - oauthRequestApi, - }: CreateOptions): typeof oneloginAuthApiRef.T { - return OAuth2.create({ - discoveryApi, - oauthRequestApi, - provider, - environment, - defaultScopes: ['openid', 'email', 'profile', 'offline_access'], - scopeTransform(scopes) { - return scopes.map(scope => { - if (OIDC_SCOPES.has(scope)) { - return scope; - } - - if (scope.startsWith(SCOPE_PREFIX)) { - return scope; - } - - return `${SCOPE_PREFIX}${scope}`; - }); - }, - }); - } -} - -export default OneLoginAuth; diff --git a/packages/core-api/src/apis/implementations/auth/onelogin/index.ts b/packages/core-api/src/apis/implementations/auth/onelogin/index.ts deleted file mode 100644 index e1826f17dd..0000000000 --- a/packages/core-api/src/apis/implementations/auth/onelogin/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { default as OneLoginAuth } from './OneLoginAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts b/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts deleted file mode 100644 index b8f41cfc18..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/SamlAuth.ts +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright 2020 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 SamlIcon from '@material-ui/icons/AcUnit'; -import { DirectAuthConnector } from '../../../../lib/AuthConnector'; -import { SessionManager } from '../../../../lib/AuthSessionManager/types'; -import { Observable } from '../../../../types'; -import { - ProfileInfo, - BackstageIdentity, - SessionState, - AuthRequestOptions, - ProfileInfoApi, - BackstageIdentityApi, - SessionApi, -} from '../../../definitions/auth'; -import { SamlSession } from './types'; -import { - AuthSessionStore, - StaticAuthSessionManager, -} from '../../../../lib/AuthSessionManager'; -import { AuthApiCreateOptions } from '../types'; - -export type SamlAuthResponse = { - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; - -const DEFAULT_PROVIDER = { - id: 'saml', - title: 'SAML', - icon: SamlIcon, -}; - -class SamlAuth implements ProfileInfoApi, BackstageIdentityApi, SessionApi { - static create({ - discoveryApi, - environment = 'development', - provider = DEFAULT_PROVIDER, - }: AuthApiCreateOptions) { - const connector = new DirectAuthConnector({ - discoveryApi, - environment, - provider, - }); - - const sessionManager = new StaticAuthSessionManager({ - connector, - }); - - const authSessionStore = new AuthSessionStore({ - manager: sessionManager, - storageKey: `${provider.id}Session`, - }); - - return new SamlAuth(authSessionStore); - } - - sessionState$(): Observable { - return this.sessionManager.sessionState$(); - } - - constructor(private readonly sessionManager: SessionManager) {} - - async signIn() { - await this.getBackstageIdentity({}); - } - async signOut() { - await this.sessionManager.removeSession(); - } - - async getBackstageIdentity( - options: AuthRequestOptions = {}, - ): Promise { - const session = await this.sessionManager.getSession(options); - return session?.backstageIdentity; - } - - async getProfile(options: AuthRequestOptions = {}) { - const session = await this.sessionManager.getSession(options); - return session?.profile; - } -} - -export default SamlAuth; diff --git a/packages/core-api/src/apis/implementations/auth/saml/index.ts b/packages/core-api/src/apis/implementations/auth/saml/index.ts deleted file mode 100644 index 930e6cb115..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/index.ts +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright 2020 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 { default as SamlAuth } from './SamlAuth'; diff --git a/packages/core-api/src/apis/implementations/auth/saml/types.ts b/packages/core-api/src/apis/implementations/auth/saml/types.ts deleted file mode 100644 index 2827c6aa24..0000000000 --- a/packages/core-api/src/apis/implementations/auth/saml/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020 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 { ProfileInfo, BackstageIdentity } from '../../../definitions'; - -export type SamlSession = { - userId: string; - profile: ProfileInfo; - backstageIdentity: BackstageIdentity; -}; diff --git a/packages/core-api/src/apis/implementations/auth/types.ts b/packages/core-api/src/apis/implementations/auth/types.ts deleted file mode 100644 index 14202f4e29..0000000000 --- a/packages/core-api/src/apis/implementations/auth/types.ts +++ /dev/null @@ -1,28 +0,0 @@ -/* - * Copyright 2020 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 { AuthProvider, DiscoveryApi, OAuthRequestApi } from '../../definitions'; - -export type OAuthApiCreateOptions = AuthApiCreateOptions & { - oauthRequestApi: OAuthRequestApi; - defaultScopes?: string[]; -}; - -export type AuthApiCreateOptions = { - discoveryApi: DiscoveryApi; - environment?: string; - provider?: AuthProvider & { id: string }; -}; diff --git a/packages/core-api/src/apis/implementations/index.ts b/packages/core-api/src/apis/implementations/index.ts deleted file mode 100644 index 31f01f2bff..0000000000 --- a/packages/core-api/src/apis/implementations/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 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. - */ - -// This folder contains implementations for all core APIs. -// -// Plugins should rely on these APIs for functionality as much as possible. - -export * from './auth'; - -export * from './AlertApi'; -export * from './AppThemeApi'; -export * from './ConfigApi'; -export * from './DiscoveryApi'; -export * from './ErrorApi'; -export * from './FeatureFlagsApi'; -export * from './OAuthRequestApi'; -export * from './StorageApi'; diff --git a/packages/core-api/src/apis/index.ts b/packages/core-api/src/apis/index.ts deleted file mode 100644 index 05b920c88a..0000000000 --- a/packages/core-api/src/apis/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 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 './system'; -export * from './definitions'; -export * from './implementations'; diff --git a/packages/core-api/src/apis/system/ApiAggregator.test.ts b/packages/core-api/src/apis/system/ApiAggregator.test.ts deleted file mode 100644 index 5a7cd12683..0000000000 --- a/packages/core-api/src/apis/system/ApiAggregator.test.ts +++ /dev/null @@ -1,46 +0,0 @@ -/* - * Copyright 2020 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 { ApiAggregator } from './ApiAggregator'; -import { createApiRef } from './ApiRef'; -import { ApiRegistry } from './ApiRegistry'; - -describe('ApiAggregator', () => { - const apiARef = createApiRef({ id: 'a', description: '' }); - const apiBRef = createApiRef({ id: 'b', description: '' }); - - it('should forward implementations', () => { - const agg = new ApiAggregator( - ApiRegistry.from([ - [apiARef, 5], - [apiBRef, 10], - ]), - ); - expect(agg.get(apiARef)).toBe(5); - expect(agg.get(apiBRef)).toBe(10); - }); - - it('should return the first implementation', () => { - const agg = new ApiAggregator( - ApiRegistry.from([ - [apiARef, 1], - [apiARef, 2], - ]), - ); - expect(agg.get(apiARef)).toBe(2); - expect(agg.get(apiBRef)).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiAggregator.ts b/packages/core-api/src/apis/system/ApiAggregator.ts deleted file mode 100644 index 28b2827d54..0000000000 --- a/packages/core-api/src/apis/system/ApiAggregator.ts +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, ApiHolder } from './types'; - -/** - * An ApiHolder that queries multiple other holders from for - * an Api implementation, returning the first one encountered.. - */ -export class ApiAggregator implements ApiHolder { - private readonly holders: ApiHolder[]; - - constructor(...holders: ApiHolder[]) { - this.holders = holders; - } - - get(apiRef: ApiRef): T | undefined { - for (const holder of this.holders) { - const api = holder.get(apiRef); - if (api) { - return api; - } - } - return undefined; - } -} diff --git a/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts deleted file mode 100644 index c20d0805b1..0000000000 --- a/packages/core-api/src/apis/system/ApiFactoryRegistry.test.ts +++ /dev/null @@ -1,91 +0,0 @@ -/* - * Copyright 2020 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 { ApiFactoryRegistry } from './ApiFactoryRegistry'; -import { createApiRef } from './ApiRef'; - -const aRef = createApiRef({ id: 'a', description: '' }); -const aFactory1 = { api: aRef, deps: {}, factory: () => 1 }; -const aFactory2 = { api: aRef, deps: {}, factory: () => 2 }; -const bRef = createApiRef({ id: 'b', description: '' }); -const bFactory = { api: bRef, deps: {}, factory: () => 'x' }; -const cRef = createApiRef({ id: 'c', description: '' }); -const cFactory = { api: cRef, deps: {}, factory: () => 'y' }; - -describe('ApiFactoryRegistry', () => { - it('should be empty when created', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.getAllApis()).toEqual(new Set()); - }); - - it('should register a factory', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.getAllApis()).toEqual(new Set([aRef])); - }); - - it('should prioritize factories based on scope', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('default', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('app', aFactory2)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory2); - expect(registry.register('default', aFactory1)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory2); - expect(registry.register('static', aFactory1)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('static', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.register('app', aFactory2)).toBe(false); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.getAllApis()).toEqual(new Set([aRef])); - }); - - it('should register multiple factories without conflict', () => { - const registry = new ApiFactoryRegistry(); - expect(registry.register('static', aFactory1)).toBe(true); - expect(registry.register('default', bFactory)).toBe(true); - expect(registry.register('app', cFactory)).toBe(true); - expect(registry.get(aRef)).toBe(aFactory1); - expect(registry.get(bRef)).toBe(bFactory); - expect(registry.get(cRef)).toBe(cFactory); - expect(registry.getAllApis()).toEqual(new Set([aRef, bRef, cRef])); - }); - - it('should identify ApiRefs by id but still return the correct factory ref when listing all apis', () => { - const ref1 = createApiRef({ id: 'a', description: 'ref1' }); - const ref2 = createApiRef({ id: 'a', description: 'ref2' }); - - const factory1 = { api: ref1, deps: {}, factory: () => 3 }; - const factory2 = { api: ref2, deps: {}, factory: () => 3 }; - - const registry = new ApiFactoryRegistry(); - expect(registry.register('default', factory1)).toBe(true); - expect(registry.register('default', factory2)).toBe(false); - expect(registry.get(ref1)).toEqual(factory1); - expect(registry.get(ref2)).toEqual(factory1); - expect(registry.getAllApis()).toEqual(new Set([ref1])); - - expect(registry.register('app', factory2)).toBe(true); - expect(registry.get(ref1)).toEqual(factory2); - expect(registry.get(ref2)).toEqual(factory2); - expect(registry.getAllApis()).toEqual(new Set([ref2])); - expect(registry.getAllApis()).not.toEqual(new Set([ref1])); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts b/packages/core-api/src/apis/system/ApiFactoryRegistry.ts deleted file mode 100644 index 0c37078d42..0000000000 --- a/packages/core-api/src/apis/system/ApiFactoryRegistry.ts +++ /dev/null @@ -1,89 +0,0 @@ -/* - * Copyright 2020 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 { - ApiRef, - ApiFactoryHolder, - ApiFactory, - AnyApiRef, - AnyApiFactory, -} from './types'; - -type ApiFactoryScope = - | 'default' // Default factories registered by core and plugins - | 'app' // Factories registered in the app, overriding default ones - | 'static'; // APIs that can't be overridden, e.g. config - -enum ScopePriority { - default = 10, - app = 50, - static = 100, -} - -type FactoryTuple = { - priority: number; - factory: AnyApiFactory; -}; - -/** - * ApiFactoryRegistry is an ApiFactoryHolder implementation that enables - * registration of API Factories with different scope. - * - * Each scope has an assigned priority, where factories registered with - * higher priority scopes override ones with lower priority. - */ -export class ApiFactoryRegistry implements ApiFactoryHolder { - private readonly factories = new Map(); - - /** - * Register a new API factory. Returns true if the factory was added - * to the registry. - * - * A factory will not be added to the registry if there is already - * an existing factory with the same or higher priority. - */ - register( - scope: ApiFactoryScope, - factory: ApiFactory, - ) { - const priority = ScopePriority[scope]; - const existing = this.factories.get(factory.api.id); - if (existing && existing.priority >= priority) { - return false; - } - - this.factories.set(factory.api.id, { priority, factory }); - return true; - } - - get( - api: ApiRef, - ): ApiFactory | undefined { - const tuple = this.factories.get(api.id); - if (!tuple) { - return undefined; - } - return tuple.factory as ApiFactory; - } - - getAllApis(): Set { - const refs = new Set(); - for (const { factory } of this.factories.values()) { - refs.add(factory.api); - } - return refs; - } -} diff --git a/packages/core-api/src/apis/system/ApiProvider.test.tsx b/packages/core-api/src/apis/system/ApiProvider.test.tsx deleted file mode 100644 index a212f253ce..0000000000 --- a/packages/core-api/src/apis/system/ApiProvider.test.tsx +++ /dev/null @@ -1,212 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { Context, useContext } from 'react'; -import { ApiProvider, useApi, withApis } from './ApiProvider'; -import { createApiRef } from './ApiRef'; -import { ApiRegistry } from './ApiRegistry'; -import { render } from '@testing-library/react'; -import { withLogCollector } from '@backstage/test-utils-core'; -import { getGlobalSingleton } from '../../lib/globalObject'; -import { ApiHolder, ApiRef } from './types'; -import { VersionedValue } from '../../lib/versionedValues'; - -describe('ApiProvider', () => { - type Api = () => string; - const apiRef = createApiRef({ id: 'x', description: '' }); - const registry = ApiRegistry.from([[apiRef, () => 'hello']]); - - const MyHookConsumer = () => { - const api = useApi(apiRef); - return

    hook message: {api()}

    ; - }; - - const MyHocConsumer = withApis({ getMessage: apiRef })(({ getMessage }) => { - return

    hoc message: {getMessage()}

    ; - }); - - it('should provide apis', () => { - const renderedHook = render( - - - , - ); - renderedHook.getByText('hook message: hello'); - - const renderedHoc = render( - - - , - ); - renderedHoc.getByText('hoc message: hello'); - }); - - it('should provide nested access to apis', () => { - const aRef = createApiRef({ id: 'a', description: '' }); - const bRef = createApiRef({ id: 'b', description: '' }); - - const MyComponent = () => { - const a = useApi(aRef); - const b = useApi(bRef); - return ( -
    - a={a} b={b} -
    - ); - }; - - const renderedHook = render( - - - - - , - ); - renderedHook.getByText('a=z b=y'); - }); - - it('should ignore deps in prototype', () => { - // 100% coverage + happy typescript = hasOwnProperty + this atrocity - const xRef = createApiRef({ id: 'x', description: '' }); - - const proto = { x: xRef }; - const props = { getMessage: { enumerable: true, value: apiRef } }; - const obj = Object.create(proto, props) as { - getMessage: typeof apiRef; - x: typeof xRef; - }; - - const MyWeirdHocConsumer = withApis(obj)(({ getMessage }) => { - return

    hoc message: {getMessage()}

    ; - }); - - const renderedHoc = render( - - - , - ); - renderedHoc.getByText('hoc message: hello'); - }); - - it('should error if no provider is available', () => { - expect( - withLogCollector(['error'], () => { - expect(() => { - render(); - }).toThrow(/^No ApiProvider available in react context. /); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context. /, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - - expect( - withLogCollector(['error'], () => { - expect(() => { - render(); - }).toThrow(/^No ApiProvider available in react context. /); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No ApiProvider available in react context. /, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - }); - - it('should error if api is not available', () => { - expect( - withLogCollector(['error'], () => { - expect(() => { - render( - - - , - ); - }).toThrow('No implementation available for apiRef{x}'); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No implementation available for apiRef{x}\]/, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - - expect( - withLogCollector(['error'], () => { - expect(() => { - render( - - - , - ); - }).toThrow('No implementation available for apiRef{x}'); - }).error, - ).toEqual([ - expect.stringMatching( - /^Error: Uncaught \[Error: No implementation available for apiRef{x}\]/, - ), - expect.stringMatching( - /^The above error occurred in the component/, - ), - ]); - }); -}); - -describe('v1 consumer', () => { - const ApiContext = getGlobalSingleton< - Context> - >('api-context'); - - function useMockApiV1(apiRef: ApiRef): T { - const impl = useContext(ApiContext)?.atVersion(1)?.get(apiRef); - if (!impl) { - throw new Error('no impl'); - } - return impl; - } - - type Api = () => string; - const apiRef = createApiRef({ id: 'x', description: '' }); - const registry = ApiRegistry.with(apiRef, () => 'hello'); - - const MyHookConsumerV1 = () => { - const api = useMockApiV1(apiRef); - return

    hook message: {api()}

    ; - }; - - it('should provide apis', () => { - const renderedHook = render( - - - , - ); - renderedHook.getByText('hook message: hello'); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiProvider.tsx b/packages/core-api/src/apis/system/ApiProvider.tsx deleted file mode 100644 index 40e3cddb72..0000000000 --- a/packages/core-api/src/apis/system/ApiProvider.tsx +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { - createContext, - useContext, - ReactNode, - PropsWithChildren, - Context, - useMemo, -} from 'react'; -import PropTypes from 'prop-types'; -import { ApiRef, ApiHolder, TypesToApiRefs } from './types'; -import { ApiAggregator } from './ApiAggregator'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../../lib/globalObject'; -import { - VersionedValue, - createVersionedValueMap, -} from '../../lib/versionedValues'; - -const missingHolderMessage = - 'No ApiProvider available in react context. ' + - 'A common cause of this error is that multiple versions of @backstage/core-api are installed. ' + - `You can check if that is the case using 'yarn backstage-cli versions:check', and can in many cases ` + - `fix the issue either with the --fix flag or using 'yarn backstage-cli versions:bump'`; - -type ApiProviderProps = { - apis: ApiHolder; - children: ReactNode; -}; - -type ApiContextType = VersionedValue<{ 1: ApiHolder }> | undefined; -const ApiContext = getOrCreateGlobalSingleton('api-context', () => - createContext(undefined), -); - -export const ApiProvider = ({ - apis, - children, -}: PropsWithChildren) => { - const parentHolder = useContext(ApiContext)?.atVersion(1); - const versionedValue = useMemo( - () => - createVersionedValueMap({ - 1: parentHolder ? new ApiAggregator(apis, parentHolder) : apis, - }), - [parentHolder, apis], - ); - - return ; -}; - -ApiProvider.propTypes = { - apis: PropTypes.shape({ get: PropTypes.func.isRequired }).isRequired, - children: PropTypes.node, -}; - -export function useApiHolder(): ApiHolder { - const versionedHolder = useContext( - getGlobalSingleton>('api-context'), - ); - - if (!versionedHolder) { - throw new Error(missingHolderMessage); - } - - const apiHolder = versionedHolder.atVersion(1); - if (!apiHolder) { - throw new Error('ApiContext v1 not available'); - } - - return apiHolder; -} - -export function useApi(apiRef: ApiRef): T { - const apiHolder = useApiHolder(); - - const api = apiHolder.get(apiRef); - if (!api) { - throw new Error(`No implementation available for ${apiRef}`); - } - return api; -} - -export function withApis(apis: TypesToApiRefs) { - return function withApisWrapper

    ( - WrappedComponent: React.ComponentType

    , - ) { - const Hoc = (props: PropsWithChildren>) => { - const apiHolder = useApiHolder(); - - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = apiHolder.get(ref); - if (!api) { - throw new Error(`No implementation available for ${ref}`); - } - impls[key] = api; - } - } - - return ; - }; - const displayName = - WrappedComponent.displayName || WrappedComponent.name || 'Component'; - - Hoc.displayName = `withApis(${displayName})`; - - return Hoc; - }; -} diff --git a/packages/core-api/src/apis/system/ApiRef.test.ts b/packages/core-api/src/apis/system/ApiRef.test.ts deleted file mode 100644 index 7cd778634c..0000000000 --- a/packages/core-api/src/apis/system/ApiRef.test.ts +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright 2020 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 { createApiRef } from './ApiRef'; - -describe('ApiRef', () => { - it('should be created', () => { - const ref = createApiRef({ id: 'abc', description: '123' }); - expect(ref.id).toBe('abc'); - expect(ref.description).toBe('123'); - expect(String(ref)).toBe('apiRef{abc}'); - expect(() => ref.T).toThrow('tried to read ApiRef.T of apiRef{abc}'); - }); - - it('should reject invalid ids', () => { - for (const id of ['a', 'abc', 'ab-c', 'a.b.c', 'a-b.c', 'abc.a-b-c.abc3']) { - expect(createApiRef({ id, description: '123' }).id).toBe(id); - } - - for (const id of [ - '123', - 'ab-3', - 'ab_c', - '.', - '2ac', - 'ab.3a', - '.abc', - 'abc.', - 'ab..s', - '', - '_', - ]) { - expect(() => createApiRef({ id, description: '123' }).id).toThrow( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${id}'`, - ); - } - }); -}); diff --git a/packages/core-api/src/apis/system/ApiRef.ts b/packages/core-api/src/apis/system/ApiRef.ts deleted file mode 100644 index 5a774474d8..0000000000 --- a/packages/core-api/src/apis/system/ApiRef.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef } from './types'; - -export type ApiRefConfig = { - id: string; - description?: string; -}; - -class ApiRefImpl implements ApiRef { - constructor(private readonly config: ApiRefConfig) { - const valid = config.id - .split('.') - .flatMap(part => part.split('-')) - .every(part => part.match(/^[a-z][a-z0-9]*$/)); - if (!valid) { - throw new Error( - `API id must only contain period separated lowercase alphanum tokens with dashes, got '${config.id}'`, - ); - } - } - - get id(): string { - return this.config.id; - } - - get description() { - return this.config.description; - } - - // Utility for getting type of an api, using `typeof apiRef.T` - get T(): T { - throw new Error(`tried to read ApiRef.T of ${this}`); - } - - toString() { - return `apiRef{${this.config.id}}`; - } -} - -export function createApiRef(config: ApiRefConfig): ApiRef { - return new ApiRefImpl(config); -} diff --git a/packages/core-api/src/apis/system/ApiRegistry.test.ts b/packages/core-api/src/apis/system/ApiRegistry.test.ts deleted file mode 100644 index 66fcb70262..0000000000 --- a/packages/core-api/src/apis/system/ApiRegistry.test.ts +++ /dev/null @@ -1,75 +0,0 @@ -/* - * Copyright 2020 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 { ApiRegistry } from './ApiRegistry'; -import { createApiRef } from './ApiRef'; - -describe('ApiRegistry', () => { - const x1Ref = createApiRef({ id: 'x1', description: '' }); - const x1DuplicateRef = createApiRef({ id: 'x1', description: '' }); - const x2Ref = createApiRef({ id: 'x2', description: '' }); - - it('should be created', () => { - const registry = ApiRegistry.from([]); - expect(registry.get(x1Ref)).toBe(undefined); - }); - - it('should be created with APIs', () => { - const registry = ApiRegistry.from([ - [x1Ref, 3], - [x2Ref, 'y'], - ]); - expect(registry.get(x1Ref)).toBe(3); - expect(registry.get(x1DuplicateRef)).toBe(3); - expect(registry.get(x2Ref)).toBe('y'); - }); - - it('should be built', () => { - const registry = ApiRegistry.builder().build(); - expect(registry.get(x1Ref)).toBe(undefined); - expect(registry.get(x1DuplicateRef)).toBe(undefined); - }); - - it('should be built with APIs', () => { - const builder = ApiRegistry.builder(); - builder.add(x1Ref, 3); - builder.add(x2Ref, 'y'); - - const registry = builder.build(); - expect(registry.get(x1Ref)).toBe(3); - expect(registry.get(x1DuplicateRef)).toBe(3); - expect(registry.get(x2Ref)).toBe('y'); - }); - - it('should be created with API', () => { - const reg1 = ApiRegistry.with(x1Ref, 3); - const reg2 = reg1.with(x2Ref, 'y'); - const reg3 = reg2.with(x2Ref, 'z'); - const reg4 = reg3.with(x1Ref, 2); - const reg5 = reg3.with(x1DuplicateRef, 4); - - expect(reg1.get(x1Ref)).toBe(3); - expect(reg1.get(x2Ref)).toBe(undefined); - expect(reg2.get(x1Ref)).toBe(3); - expect(reg2.get(x2Ref)).toBe('y'); - expect(reg3.get(x1Ref)).toBe(3); - expect(reg3.get(x2Ref)).toBe('z'); - expect(reg4.get(x1Ref)).toBe(2); - expect(reg4.get(x2Ref)).toBe('z'); - expect(reg5.get(x1Ref)).toBe(4); - expect(reg5.get(x2Ref)).toBe('z'); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiRegistry.ts b/packages/core-api/src/apis/system/ApiRegistry.ts deleted file mode 100644 index 6fcfe03f62..0000000000 --- a/packages/core-api/src/apis/system/ApiRegistry.ts +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, ApiHolder } from './types'; - -type ApiImpl = readonly [ApiRef, T]; - -class ApiRegistryBuilder { - private apis: [string, unknown][] = []; - - add(api: ApiRef, impl: I): I { - this.apis.push([api.id, impl]); - return impl; - } - - build(): ApiRegistry { - // eslint-disable-next-line @typescript-eslint/no-use-before-define - return new ApiRegistry(new Map(this.apis)); - } -} - -export class ApiRegistry implements ApiHolder { - static builder() { - return new ApiRegistryBuilder(); - } - - static from(apis: ApiImpl[]) { - return new ApiRegistry(new Map(apis.map(([api, impl]) => [api.id, impl]))); - } - - /** - * Creates a new ApiRegistry with a single API implementation. - * - * @param api ApiRef for the API to add - * @param impl Implementation of the API to add - */ - static with(api: ApiRef, impl: T): ApiRegistry { - return new ApiRegistry(new Map([[api.id, impl]])); - } - - constructor(private readonly apis: Map) {} - - /** - * Returns a new ApiRegistry with the provided API added to the existing ones. - * - * @param api ApiRef for the API to add - * @param impl Implementation of the API to add - */ - with(api: ApiRef, impl: T): ApiRegistry { - return new ApiRegistry(new Map([...this.apis, [api.id, impl]])); - } - - get(api: ApiRef): T | undefined { - return this.apis.get(api.id) as T | undefined; - } -} diff --git a/packages/core-api/src/apis/system/ApiResolver.test.ts b/packages/core-api/src/apis/system/ApiResolver.test.ts deleted file mode 100644 index 064a4f8d77..0000000000 --- a/packages/core-api/src/apis/system/ApiResolver.test.ts +++ /dev/null @@ -1,266 +0,0 @@ -/* - * Copyright 2020 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 { ApiResolver } from './ApiResolver'; -import { createApiRef } from './ApiRef'; -import { ApiFactoryRegistry } from './ApiFactoryRegistry'; - -const aRef = createApiRef({ id: 'a', description: '' }); -const otherARef = createApiRef({ id: 'a', description: 'other' }); -const bRef = createApiRef({ id: 'b', description: '' }); -const otherBRef = createApiRef({ id: 'b', description: 'other' }); -const cRef = createApiRef<{ x: string }>({ id: 'c', description: '' }); -const otherCRef = createApiRef<{ x: string }>({ - id: 'c', - description: 'other', -}); - -function createRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: {}, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: {}, - factory: () => 'b', - }); - registry.register('default', { - api: cRef, - deps: { b: otherBRef }, - factory: ({ b }) => ({ x: 'x', b }), - }); - return registry; -} - -function createSelfCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { a: aRef }, - factory: () => 1, - }); - return registry; -} - -function createShortCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: bRef }, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: { a: aRef }, - factory: () => 'x', - }); - return registry; -} - -function createShortCyclicRegistryWithOther() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: bRef }, - factory: () => 1, - }); - registry.register('default', { - api: otherBRef, - deps: { a: otherARef }, - factory: () => 'x', - }); - return registry; -} - -function createLongCyclicRegistry() { - const registry = new ApiFactoryRegistry(); - registry.register('default', { - api: aRef, - deps: { b: otherBRef }, - factory: () => 1, - }); - registry.register('default', { - api: bRef, - deps: { c: cRef }, - factory: () => 'b', - }); - registry.register('default', { - api: cRef, - deps: { a: aRef }, - factory: () => ({ x: 'x' }), - }); - return registry; -} - -describe('ApiResolver', () => { - it('should be created empty', () => { - const resolver = new ApiResolver(new ApiFactoryRegistry()); - expect(resolver.get(aRef)).toBe(undefined); - expect(resolver.get(bRef)).toBe(undefined); - expect(resolver.get(otherBRef)).toBe(undefined); - expect(resolver.get(cRef)).toBe(undefined); - }); - - it('should instantiate APIs', () => { - const resolver = new ApiResolver(createRegistry()); - expect(resolver.get(aRef)).toBe(1); - expect(resolver.get(otherARef)).toBe(1); - expect(resolver.get(bRef)).toBe('b'); - expect(resolver.get(otherBRef)).toBe('b'); - expect(resolver.get(cRef)).toEqual({ x: 'x', b: 'b' }); - expect(resolver.get(cRef)).toBe(resolver.get(otherCRef)); - }); - - it('should detect self dependency cycles', () => { - const resolver = new ApiResolver(createSelfCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - }); - - it('should detect short dependency cycles', () => { - const resolver = new ApiResolver(createShortCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - }); - - it('should detect short dependency cycles with other refs', () => { - const resolver = new ApiResolver(createShortCyclicRegistryWithOther()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(otherARef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(otherBRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - }); - - it('should detect long dependency cycles', () => { - const resolver = new ApiResolver(createLongCyclicRegistry()); - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - // Second call for same ref should still throw - expect(() => resolver.get(aRef)).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => resolver.get(bRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(otherBRef)).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => resolver.get(cRef)).toThrow( - 'Circular dependency of api factory for apiRef{c}', - ); - }); - - it('should validate a factory holder', () => { - expect(() => { - ApiResolver.validateFactories(createRegistry(), [ - aRef, - bRef, - otherBRef, - cRef, - ]); - }).not.toThrow(); - }); - - it('should find self cycles with validation', () => { - const self = createSelfCyclicRegistry(); - expect(() => ApiResolver.validateFactories(self, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(self, [otherARef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - }); - - it('should find dependency cycles with validation', () => { - const short = createShortCyclicRegistry(); - expect(() => ApiResolver.validateFactories(short, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(short, [otherARef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => ApiResolver.validateFactories(short, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(short, [otherBRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - - const shortOther = createShortCyclicRegistryWithOther(); - expect(() => ApiResolver.validateFactories(shortOther, [aRef])).toThrow( - 'Circular dependency of api factory for apiRef{a}', - ); - expect(() => - ApiResolver.validateFactories(shortOther, [otherARef]), - ).toThrow('Circular dependency of api factory for apiRef{a}'); - expect(() => ApiResolver.validateFactories(shortOther, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => - ApiResolver.validateFactories(shortOther, [otherBRef]), - ).toThrow('Circular dependency of api factory for apiRef{b}'); - - const long = createLongCyclicRegistry(); - expect(() => - ApiResolver.validateFactories(long, long.getAllApis()), - ).toThrow('Circular dependency of api factory for apiRef{a}'); - expect(() => ApiResolver.validateFactories(long, [bRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(long, [otherBRef])).toThrow( - 'Circular dependency of api factory for apiRef{b}', - ); - expect(() => ApiResolver.validateFactories(long, [cRef])).toThrow( - 'Circular dependency of api factory for apiRef{c}', - ); - }); - - it('should only call factory func once', () => { - const registry = new ApiFactoryRegistry(); - const factory = jest.fn().mockReturnValue(2); - registry.register('default', { - api: aRef, - deps: {}, - factory, - }); - - const resolver = new ApiResolver(registry); - expect(factory).toHaveBeenCalledTimes(0); - expect(resolver.get(aRef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - expect(resolver.get(aRef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - expect(resolver.get(otherARef)).toBe(2); - expect(factory).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/apis/system/ApiResolver.ts b/packages/core-api/src/apis/system/ApiResolver.ts deleted file mode 100644 index 4d69067b43..0000000000 --- a/packages/core-api/src/apis/system/ApiResolver.ts +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright 2020 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 { - ApiRef, - ApiHolder, - ApiFactoryHolder, - AnyApiRef, - TypesToApiRefs, -} from './types'; - -export class ApiResolver implements ApiHolder { - /** - * Validate factories by making sure that each of the apis can be created - * without hitting any circular dependencies. - */ - static validateFactories( - factories: ApiFactoryHolder, - apis: Iterable, - ) { - for (const api of apis) { - const heap = [api]; - const allDeps = new Set(); - - while (heap.length) { - const apiRef = heap.shift()!; - const factory = factories.get(apiRef); - if (!factory) { - continue; - } - - for (const dep of Object.values(factory.deps)) { - if (dep.id === api.id) { - throw new Error(`Circular dependency of api factory for ${api}`); - } - if (!allDeps.has(dep)) { - allDeps.add(dep); - heap.push(dep); - } - } - } - } - } - - private readonly apis = new Map(); - - constructor(private readonly factories: ApiFactoryHolder) {} - - get(ref: ApiRef): T | undefined { - return this.load(ref); - } - - private load(ref: ApiRef, loading: AnyApiRef[] = []): T | undefined { - const impl = this.apis.get(ref.id); - if (impl) { - return impl as T; - } - - const factory = this.factories.get(ref); - if (!factory) { - return undefined; - } - - if (loading.includes(factory.api)) { - throw new Error(`Circular dependency of api factory for ${factory.api}`); - } - - const deps = this.loadDeps(ref, factory.deps, [...loading, factory.api]); - const api = factory.factory(deps); - this.apis.set(ref.id, api); - return api as T; - } - - private loadDeps( - dependent: ApiRef, - apis: TypesToApiRefs, - loading: AnyApiRef[], - ): T { - const impls = {} as T; - - for (const key in apis) { - if (apis.hasOwnProperty(key)) { - const ref = apis[key]; - - const api = this.load(ref, loading); - if (!api) { - throw new Error( - `No API factory available for dependency ${ref} of dependent ${dependent}`, - ); - } - impls[key] = api; - } - } - - return impls; - } -} diff --git a/packages/core-api/src/apis/system/helpers.ts b/packages/core-api/src/apis/system/helpers.ts deleted file mode 100644 index 8e84dd6c09..0000000000 --- a/packages/core-api/src/apis/system/helpers.ts +++ /dev/null @@ -1,49 +0,0 @@ -/* - * Copyright 2020 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 { ApiRef, ApiFactory, TypesToApiRefs } from './types'; - -/** - * Used to infer types for a standalone ApiFactory that isn't immediately passed - * to another function. - * This function doesn't actually do anything, it's only used to infer types. - */ -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } ->(factory: ApiFactory): ApiFactory; -export function createApiFactory( - api: ApiRef, - instance: Impl, -): ApiFactory; -export function createApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } ->( - factory: ApiFactory | ApiRef, - instance?: Impl, -): ApiFactory { - if ('id' in factory) { - return { - api: factory, - deps: {} as TypesToApiRefs, - factory: () => instance!, - }; - } - return factory; -} diff --git a/packages/core-api/src/apis/system/index.ts b/packages/core-api/src/apis/system/index.ts deleted file mode 100644 index c9b9fac936..0000000000 --- a/packages/core-api/src/apis/system/index.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 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 { ApiProvider, useApi, useApiHolder } from './ApiProvider'; -export { ApiRegistry } from './ApiRegistry'; -export { ApiResolver } from './ApiResolver'; -export { ApiFactoryRegistry } from './ApiFactoryRegistry'; -export { createApiRef } from './ApiRef'; -export * from './types'; -export * from './helpers'; diff --git a/packages/core-api/src/apis/system/types.ts b/packages/core-api/src/apis/system/types.ts deleted file mode 100644 index 17ceff4649..0000000000 --- a/packages/core-api/src/apis/system/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 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 ApiRef = { - id: string; - description?: string; - T: T; -}; - -export type AnyApiRef = ApiRef; - -export type ApiRefType = T extends ApiRef ? U : never; - -export type TypesToApiRefs = { [key in keyof T]: ApiRef }; - -export type ApiRefsToTypes }> = { - [key in keyof T]: ApiRefType; -}; - -export type ApiHolder = { - get(api: ApiRef): T | undefined; -}; - -export type ApiFactory< - Api, - Impl extends Api, - Deps extends { [name in string]: unknown } -> = { - api: ApiRef; - deps: TypesToApiRefs; - factory(deps: Deps): Impl; -}; - -export type AnyApiFactory = ApiFactory< - unknown, - unknown, - { [key in string]: unknown } ->; - -export type ApiFactoryHolder = { - get( - api: ApiRef, - ): ApiFactory | undefined; -}; diff --git a/packages/core-api/src/app/App.test.tsx b/packages/core-api/src/app/App.test.tsx deleted file mode 100644 index 5404c111e4..0000000000 --- a/packages/core-api/src/app/App.test.tsx +++ /dev/null @@ -1,375 +0,0 @@ -/* - * Copyright 2020 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 { - configApiRef, - createApiFactory, - featureFlagsApiRef, - LocalStorageFeatureFlags, -} from '../apis'; -import { renderWithEffects, withLogCollector } from '@backstage/test-utils'; -import { lightTheme } from '@backstage/theme'; -import { render, screen } from '@testing-library/react'; -import React, { PropsWithChildren } from 'react'; -import { BrowserRouter, Routes } from 'react-router-dom'; -import { createRoutableExtension } from '../extensions'; -import { defaultSystemIcons } from '../icons'; -import { createPlugin } from '../plugin'; -import { useRouteRef } from '../routing/hooks'; -import { - createExternalRouteRef, - createRouteRef, - createSubRouteRef, -} from '../routing'; -import { generateBoundRoutes, PrivateAppImpl } from './App'; - -describe('generateBoundRoutes', () => { - it('runs happy path', () => { - const external = { myRoute: createExternalRouteRef({ id: '1' }) }; - const ref = createRouteRef({ id: 'ref-1' }); - const result = generateBoundRoutes(({ bind }) => { - bind(external, { myRoute: ref }); - }); - - expect(result.get(external.myRoute)).toBe(ref); - }); - - it('throws on unknown keys', () => { - const external = { myRoute: createExternalRouteRef({ id: '2' }) }; - const ref = createRouteRef({ id: 'ref-2' }); - expect(() => - generateBoundRoutes(({ bind }) => { - bind(external, { someOtherRoute: ref } as any); - }), - ).toThrow('Key someOtherRoute is not an existing external route'); - }); -}); - -describe('Integration Test', () => { - const plugin1RouteRef = createRouteRef({ id: 'ref-1' }); - const plugin2RouteRef = createRouteRef({ id: 'ref-2', params: ['x'] }); - const subRouteRef1 = createSubRouteRef({ - id: 'sub1', - path: '/sub1', - parent: plugin1RouteRef, - }); - const subRouteRef2 = createSubRouteRef({ - id: 'sub2', - path: '/sub2/:x', - parent: plugin1RouteRef, - }); - const subRouteRef3 = createSubRouteRef({ - id: 'sub3', - path: '/sub3', - parent: plugin2RouteRef, - }); - const subRouteRef4 = createSubRouteRef({ - id: 'sub4', - path: '/sub4/:y', - parent: plugin2RouteRef, - }); - const extRouteRef1 = createExternalRouteRef({ id: 'extRouteRef1' }); - const extRouteRef2 = createExternalRouteRef({ - id: 'extRouteRef2', - params: ['x'], - }); - const extRouteRef3 = createExternalRouteRef({ - id: 'extRouteRef3', - optional: true, - }); - const extRouteRef4 = createExternalRouteRef({ - id: 'extRouteRef4', - optional: true, - params: ['x'], - }); - - const plugin1 = createPlugin({ - id: 'blob', - // Both absolute and sub route refs should be assignable to the plugin routes - routes: { - ref1: plugin1RouteRef, - ref2: plugin2RouteRef, - ref3: subRouteRef1, - }, - externalRoutes: { - extRouteRef1, - extRouteRef2, - extRouteRef3, - extRouteRef4, - }, - }); - - const plugin2 = createPlugin({ - id: 'plugin2', - }); - - const HiddenComponent = plugin2.provide( - createRoutableExtension({ - component: () => Promise.resolve((_: { path?: string }) =>

    ), - mountPoint: plugin2RouteRef, - }), - ); - - const ExposedComponent = plugin1.provide( - createRoutableExtension({ - component: () => - Promise.resolve((_: PropsWithChildren<{ path?: string }>) => { - const link1 = useRouteRef(plugin1RouteRef); - const link2 = useRouteRef(plugin2RouteRef); - const subLink1 = useRouteRef(subRouteRef1); - const subLink2 = useRouteRef(subRouteRef2); - const subLink3 = useRouteRef(subRouteRef3); - const subLink4 = useRouteRef(subRouteRef4); - const extLink1 = useRouteRef(extRouteRef1); - const extLink2 = useRouteRef(extRouteRef2); - const extLink3 = useRouteRef(extRouteRef3); - const extLink4 = useRouteRef(extRouteRef4); - return ( -
    - link1: {link1()} - link2: {link2({ x: 'a' })} - subLink1: {subLink1()} - subLink2: {subLink2({ x: 'a' })} - subLink3: {subLink3({ x: 'b' })} - subLink4: {subLink4({ x: 'c', y: 'd' })} - extLink1: {extLink1()} - extLink2: {extLink2({ x: 'a' })} - extLink3: {extLink3?.() ?? ''} - extLink4: {extLink4?.({ x: 'b' }) ?? ''} -
    - ); - }), - mountPoint: plugin1RouteRef, - }), - ); - - const components = { - NotFoundErrorPage: () => null, - BootErrorPage: () => null, - Progress: () => null, - Router: BrowserRouter, - }; - - it('runs happy paths', async () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - extRouteRef3: subRouteRef1, - extRouteRef4: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - - await renderWithEffects( - - - - - - - - , - ); - - expect(screen.getByText('link1: /')).toBeInTheDocument(); - expect(screen.getByText('link2: /foo/a')).toBeInTheDocument(); - expect(screen.getByText('subLink1: /sub1')).toBeInTheDocument(); - expect(screen.getByText('subLink2: /sub2/a')).toBeInTheDocument(); - expect(screen.getByText('subLink3: /foo/b/sub3')).toBeInTheDocument(); - expect(screen.getByText('subLink4: /foo/c/sub4/d')).toBeInTheDocument(); - expect(screen.getByText('extLink1: /')).toBeInTheDocument(); - expect(screen.getByText('extLink2: /foo/a')).toBeInTheDocument(); - expect(screen.getByText('extLink3: /sub1')).toBeInTheDocument(); - expect(screen.getByText('extLink4: /foo/b')).toBeInTheDocument(); - - // Plugins should be discovered through element tree - expect(app.getPlugins()).toEqual([plugin1, plugin2]); - }); - - it('runs happy paths without optional routes', async () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - - await renderWithEffects( - - - - - - - - , - ); - - expect(screen.getByText('extLink1: /')).toBeInTheDocument(); - expect(screen.getByText('extLink2: /foo')).toBeInTheDocument(); - expect(screen.getByText('extLink3: ')).toBeInTheDocument(); - expect(screen.getByText('extLink4: ')).toBeInTheDocument(); - }); - - it('should wait for the config to load before calling feature flags', async () => { - const storageFlags = new LocalStorageFeatureFlags(); - jest.spyOn(storageFlags, 'registerFlag'); - - const apis = [ - createApiFactory({ - api: featureFlagsApiRef, - deps: { configApi: configApiRef }, - factory() { - return storageFlags; - }, - }), - ]; - - const app = new PrivateAppImpl({ - apis, - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [ - createPlugin({ - id: 'test', - register: p => p.featureFlags.register('name'), - }), - ], - components, - 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', - }); - }); - - it('should throw some error when the route has duplicate params', () => { - const app = new PrivateAppImpl({ - apis: [], - defaultApis: [], - themes: [ - { - id: 'light', - title: 'Light Theme', - variant: 'light', - theme: lightTheme, - }, - ], - icons: defaultSystemIcons, - plugins: [], - components, - bindRoutes: ({ bind }) => { - bind(plugin1.externalRoutes, { - extRouteRef1: plugin1RouteRef, - extRouteRef2: plugin2RouteRef, - }); - }, - }); - - const Provider = app.getProvider(); - const Router = app.getRouter(); - const { error: errorLogs } = withLogCollector(() => { - expect(() => - render( - - - - - - - - - , - ), - ).toThrow( - 'Parameter :thing is duplicated in path /test/:thing/some/:thing', - ); - }); - expect(errorLogs).toEqual([ - expect.stringContaining( - 'Parameter :thing is duplicated in path /test/:thing/some/:thing', - ), - expect.stringContaining( - 'The above error occurred in the component', - ), - ]); - }); -}); diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx deleted file mode 100644 index 3a5e151466..0000000000 --- a/packages/core-api/src/app/App.tsx +++ /dev/null @@ -1,525 +0,0 @@ -/* - * Copyright 2020 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 { Config } from '@backstage/config'; -import React, { - ComponentType, - PropsWithChildren, - ReactElement, - useEffect, - useMemo, - useState, -} from 'react'; -import { Navigate, Route, Routes } from 'react-router-dom'; -import { useAsync } from 'react-use'; -import { - AnyApiFactory, - ApiHolder, - ApiProvider, - ApiRegistry, - AppTheme, - appThemeApiRef, - AppThemeSelector, - configApiRef, - ConfigReader, - LocalStorageFeatureFlags, - useApi, -} from '../apis'; -import { - AppThemeApi, - ConfigApi, - featureFlagsApiRef, - identityApiRef, -} from '../apis/definitions'; -import { ApiFactoryRegistry, ApiResolver } from '../apis/system'; -import { - childDiscoverer, - routeElementDiscoverer, - traverseElementTree, -} from '../extensions/traversal'; -import { IconComponent, IconComponentMap, IconKey } from '../icons'; -import { BackstagePlugin } from '../plugin'; -import { pluginCollector } from '../plugin/collectors'; -import { AnyRoutes } from '../plugin/types'; -import { RouteRef, ExternalRouteRef, SubRouteRef } from '../routing'; -import { - routeObjectCollector, - routeParentCollector, - routePathCollector, -} from '../routing/collectors'; -import { RoutingProvider } from '../routing/hooks'; -import { validateRoutes } from '../routing/validation'; -import { AppContextProvider } from './AppContext'; -import { AppIdentity } from './AppIdentity'; -import { AppThemeProvider } from './AppThemeProvider'; -import { - AppComponents, - AppConfigLoader, - AppContext, - AppOptions, - AppRouteBinder, - BackstageApp, - SignInPageProps, - SignInResult, -} from './types'; - -export function generateBoundRoutes(bindRoutes: AppOptions['bindRoutes']) { - const result = new Map(); - - if (bindRoutes) { - const bind: AppRouteBinder = (externalRoutes, targetRoutes: AnyRoutes) => { - for (const [key, value] of Object.entries(targetRoutes)) { - const externalRoute = externalRoutes[key]; - if (!externalRoute) { - throw new Error(`Key ${key} is not an existing external route`); - } - if (!value && !externalRoute.optional) { - throw new Error( - `External route ${key} is required but was undefined`, - ); - } - if (value) { - result.set(externalRoute, value); - } - } - }; - bindRoutes({ bind }); - } - - return result; -} - -type FullAppOptions = { - apis: Iterable; - icons: IconComponentMap; - plugins: BackstagePlugin[]; - components: AppComponents; - themes: AppTheme[]; - configLoader?: AppConfigLoader; - defaultApis: Iterable; - bindRoutes?: AppOptions['bindRoutes']; -}; - -function useConfigLoader( - configLoader: AppConfigLoader | undefined, - components: AppComponents, - appThemeApi: AppThemeApi, -): { api: ConfigApi } | { node: JSX.Element } { - // Keeping this synchronous when a config loader isn't set simplifies tests a lot - const hasConfig = Boolean(configLoader); - const config = useAsync(configLoader || (() => Promise.resolve([]))); - - let noConfigNode = undefined; - - if (hasConfig && config.loading) { - const { Progress } = components; - noConfigNode = ; - } else if (config.error) { - const { BootErrorPage } = components; - noConfigNode = ; - } - - // Before the config is loaded we can't use a router, so exit early - if (noConfigNode) { - return { - node: ( - - {noConfigNode} - - ), - }; - } - - const configReader = ConfigReader.fromConfigs(config.value ?? []); - - return { api: configReader }; -} - -class AppContextImpl implements AppContext { - constructor(private readonly app: PrivateAppImpl) {} - - getPlugins(): BackstagePlugin[] { - // eslint-disable-next-line no-console - console.warn('appContext.getPlugins() is deprecated and will be removed'); - return this.app.getPlugins(); - } - - getSystemIcon(key: IconKey): IconComponent | undefined { - return this.app.getSystemIcon(key); - } - - getComponents(): AppComponents { - return this.app.getComponents(); - } - - getProvider(): React.ComponentType<{}> { - // eslint-disable-next-line no-console - console.warn('appContext.getProvider() is deprecated and will be removed'); - return this.app.getProvider(); - } - - getRouter(): React.ComponentType<{}> { - // eslint-disable-next-line no-console - console.warn('appContext.getRouter() is deprecated and will be removed'); - return this.app.getRouter(); - } - - getRoutes(): JSX.Element[] { - // eslint-disable-next-line no-console - console.warn('appContext.getRoutes() is deprecated and will be removed'); - return this.app.getRoutes(); - } -} - -export class PrivateAppImpl implements BackstageApp { - private apiHolder?: ApiHolder; - private configApi?: ConfigApi; - - private readonly apis: Iterable; - private readonly icons: IconComponentMap; - private readonly plugins: Set>; - private readonly components: AppComponents; - private readonly themes: AppTheme[]; - private readonly configLoader?: AppConfigLoader; - private readonly defaultApis: Iterable; - private readonly bindRoutes: AppOptions['bindRoutes']; - - private readonly identityApi = new AppIdentity(); - - constructor(options: FullAppOptions) { - this.apis = options.apis; - this.icons = options.icons; - this.plugins = new Set(options.plugins); - this.components = options.components; - this.themes = options.themes; - this.configLoader = options.configLoader; - this.defaultApis = options.defaultApis; - this.bindRoutes = options.bindRoutes; - } - - getPlugins(): BackstagePlugin[] { - return Array.from(this.plugins); - } - - getSystemIcon(key: IconKey): IconComponent | undefined { - return this.icons[key]; - } - - getComponents(): AppComponents { - return this.components; - } - - getRoutes(): JSX.Element[] { - const routes = new Array(); - - const { NotFoundErrorPage } = this.components; - - for (const plugin of this.plugins.values()) { - for (const output of plugin.output()) { - switch (output.type) { - case 'legacy-route': { - const { path, component: Component } = output; - routes.push( - } />, - ); - break; - } - case 'route': { - const { target, component: Component } = output; - routes.push( - } - />, - ); - break; - } - case 'legacy-redirect-route': { - const { path, target } = output; - routes.push(); - break; - } - case 'redirect-route': { - const { from, to } = output; - routes.push(); - break; - } - default: - break; - } - } - } - - routes.push( - } - />, - ); - - return routes; - } - - getProvider(): ComponentType<{}> { - const appContext = new AppContextImpl(this); - - const Provider = ({ children }: PropsWithChildren<{}>) => { - const appThemeApi = useMemo( - () => AppThemeSelector.createWithStorage(this.themes), - [], - ); - - const { routePaths, routeParents, routeObjects } = useMemo(() => { - const result = traverseElementTree({ - root: children, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - collectedPlugins: pluginCollector, - }, - }); - - validateRoutes(result.routePaths, result.routeParents); - - // TODO(Rugvip): Restructure the public API so that we can get an immediate view of - // the app, rather than having to wait for the provider to render. - // For now we need to push the additional plugins we find during - // collection and then make sure we initialize things afterwards. - result.collectedPlugins.forEach(plugin => this.plugins.add(plugin)); - this.verifyPlugins(this.plugins); - - // Initialize APIs once all plugins are available - this.getApiHolder(); - - return result; - }, [children]); - - const loadedConfig = useConfigLoader( - this.configLoader, - this.components, - appThemeApi, - ); - - const hasConfigApi = 'api' in loadedConfig; - if (hasConfigApi) { - const { api } = loadedConfig as { api: Config }; - this.configApi = api; - } - - useEffect(() => { - if (hasConfigApi) { - 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; - } - default: - break; - } - } - } - } - }, [hasConfigApi, loadedConfig]); - - if ('node' in loadedConfig) { - // Loading or error - return loadedConfig.node; - } - - return ( - - - - - {children} - - - - - ); - }; - return Provider; - } - - getRouter(): ComponentType<{}> { - const { - Router: RouterComponent, - SignInPage: SignInPageComponent, - } = this.components; - - // This wraps the sign-in page and waits for sign-in to be completed before rendering the app - const SignInPageWrapper = ({ - component: Component, - children, - }: { - component: ComponentType; - children: ReactElement; - }) => { - const [result, setResult] = useState(); - - if (result) { - this.identityApi.setSignInResult(result); - return children; - } - - return ; - }; - - const AppRouter = ({ children }: PropsWithChildren<{}>) => { - const configApi = useApi(configApiRef); - - let { pathname } = new URL( - configApi.getOptionalString('app.baseUrl') ?? '/', - 'http://dummy.dev', // baseUrl can be specified as just a path - ); - if (pathname.endsWith('/')) { - pathname = pathname.replace(/\/$/, ''); - } - - // If the app hasn't configured a sign-in page, we just continue as guest. - if (!SignInPageComponent) { - this.identityApi.setSignInResult({ - userId: 'guest', - profile: { - email: 'guest@example.com', - displayName: 'Guest', - }, - }); - - return ( - - - {children}} /> - - - ); - } - - return ( - - - - {children}} /> - - - - ); - }; - - return AppRouter; - } - - private getApiHolder(): ApiHolder { - if (this.apiHolder) { - return this.apiHolder; - } - - const registry = new ApiFactoryRegistry(); - - registry.register('static', { - api: appThemeApiRef, - deps: {}, - factory: () => AppThemeSelector.createWithStorage(this.themes), - }); - registry.register('static', { - api: configApiRef, - deps: {}, - factory: () => { - if (!this.configApi) { - throw new Error( - 'Tried to access config API before config was loaded', - ); - } - return this.configApi; - }, - }); - registry.register('static', { - api: identityApiRef, - deps: {}, - factory: () => this.identityApi, - }); - - // It's possible to replace the feature flag API, but since we must have at least - // one implementation we add it here directly instead of through the defaultApis. - registry.register('default', { - api: featureFlagsApiRef, - deps: {}, - factory: () => new LocalStorageFeatureFlags(), - }); - for (const factory of this.defaultApis) { - registry.register('default', factory); - } - - for (const plugin of this.plugins) { - for (const factory of plugin.getApis()) { - if (!registry.register('default', factory)) { - throw new Error( - `Plugin ${plugin.getId()} tried to register duplicate or forbidden API factory for ${ - factory.api - }`, - ); - } - } - } - - for (const factory of this.apis) { - if (!registry.register('app', factory)) { - throw new Error( - `Duplicate or forbidden API factory for ${factory.api} in app`, - ); - } - } - - ApiResolver.validateFactories(registry, registry.getAllApis()); - - this.apiHolder = new ApiResolver(registry); - - return this.apiHolder; - } - - /** - * @deprecated - */ - verify() {} - - private verifyPlugins(plugins: Iterable) { - const pluginIds = new Set(); - - for (const plugin of plugins) { - const id = plugin.getId(); - if (pluginIds.has(id)) { - throw new Error(`Duplicate plugin found '${id}'`); - } - pluginIds.add(id); - } - } -} diff --git a/packages/core-api/src/app/AppContext.test.tsx b/packages/core-api/src/app/AppContext.test.tsx deleted file mode 100644 index 526b397130..0000000000 --- a/packages/core-api/src/app/AppContext.test.tsx +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { useContext, Context } from 'react'; -import { renderHook } from '@testing-library/react-hooks'; -import { VersionedValue } from '../lib/versionedValues'; -import { getGlobalSingleton } from '../lib/globalObject'; -import { AppContext as AppContextV1 } from './types'; -import { AppContextProvider } from './AppContext'; - -describe('v1 consumer', () => { - const AppContext = getGlobalSingleton< - Context> - >('app-context'); - - function useMockAppV1(): AppContextV1 { - const impl = useContext(AppContext)?.atVersion(1); - if (!impl) { - throw new Error('no impl'); - } - return impl; - } - - it('should provide an app context', () => { - const mockContext: AppContextV1 = { - getComponents: jest.fn(), - getSystemIcon: jest.fn(), - getPlugins: jest.fn(), - getProvider: jest.fn(), - getRouter: jest.fn(), - getRoutes: jest.fn(), - }; - - const renderedHook = renderHook(() => useMockAppV1(), { - wrapper: ({ children }) => ( - - ), - }); - const result = renderedHook.result.current; - - expect(mockContext.getComponents).toHaveBeenCalledTimes(0); - result.getComponents(); - expect(mockContext.getComponents).toHaveBeenCalledTimes(1); - - expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(0); - result.getSystemIcon('icon'); - expect(mockContext.getSystemIcon).toHaveBeenCalledTimes(1); - expect(mockContext.getSystemIcon).toHaveBeenCalledWith('icon'); - - expect(mockContext.getPlugins).toHaveBeenCalledTimes(0); - result.getPlugins(); - expect(mockContext.getPlugins).toHaveBeenCalledTimes(1); - - expect(mockContext.getProvider).toHaveBeenCalledTimes(0); - result.getProvider(); - expect(mockContext.getProvider).toHaveBeenCalledTimes(1); - - expect(mockContext.getRouter).toHaveBeenCalledTimes(0); - result.getRouter(); - expect(mockContext.getRouter).toHaveBeenCalledTimes(1); - - expect(mockContext.getRoutes).toHaveBeenCalledTimes(0); - result.getRoutes(); - expect(mockContext.getRoutes).toHaveBeenCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/app/AppContext.tsx b/packages/core-api/src/app/AppContext.tsx deleted file mode 100644 index ab2d2d7861..0000000000 --- a/packages/core-api/src/app/AppContext.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { - createContext, - PropsWithChildren, - useContext, - Context, - useMemo, -} from 'react'; -import { - VersionedValue, - createVersionedValueMap, -} from '../lib/versionedValues'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../lib/globalObject'; -import { AppContext as AppContextV1 } from './types'; - -type AppContextType = VersionedValue<{ 1: AppContextV1 }> | undefined; -const AppContext = getOrCreateGlobalSingleton('app-context', () => - createContext(undefined), -); - -type Props = { - appContext: AppContextV1; -}; - -export const AppContextProvider = ({ - appContext, - children, -}: PropsWithChildren) => { - const versionedValue = useMemo( - () => createVersionedValueMap({ 1: appContext }), - [appContext], - ); - - return ; -}; - -export const useApp = (): AppContextV1 => { - const versionedContext = useContext( - getGlobalSingleton>('app-context'), - ); - if (!versionedContext) { - throw new Error('No app context available'); - } - const appContext = versionedContext.atVersion(1); - if (!appContext) { - throw new Error('AppContext v1 not available'); - } - return appContext; -}; diff --git a/packages/core-api/src/app/AppIdentity.ts b/packages/core-api/src/app/AppIdentity.ts deleted file mode 100644 index 7dd87de392..0000000000 --- a/packages/core-api/src/app/AppIdentity.ts +++ /dev/null @@ -1,85 +0,0 @@ -/* - * Copyright 2020 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 { IdentityApi, ProfileInfo } from '../apis'; -import { SignInResult } from './types'; - -/** - * Implementation of the connection between the App-wide IdentityApi - * and sign-in page. - */ -export class AppIdentity implements IdentityApi { - private hasIdentity = false; - private userId?: string; - private profile?: ProfileInfo; - private idTokenFunc?: () => Promise; - private signOutFunc?: () => Promise; - - getUserId(): string { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi userId before app was loaded', - ); - } - return this.userId!; - } - - getProfile(): ProfileInfo { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi profile before app was loaded', - ); - } - return this.profile!; - } - - async getIdToken(): Promise { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi idToken before app was loaded', - ); - } - return this.idTokenFunc?.(); - } - - async signOut(): Promise { - if (!this.hasIdentity) { - throw new Error( - 'Tried to access IdentityApi signOutFunc before app was loaded', - ); - } - await this.signOutFunc?.(); - location.reload(); - } - - // This is indirectly called by the sign-in page to continue into the app. - setSignInResult(result: SignInResult) { - if (this.hasIdentity) { - return; - } - if (!result.userId) { - throw new Error('Invalid sign-in result, userId not set'); - } - if (!result.profile) { - throw new Error('Invalid sign-in result, profile not set'); - } - this.hasIdentity = true; - this.userId = result.userId; - this.profile = result.profile; - this.idTokenFunc = result.getIdToken; - this.signOutFunc = result.signOut; - } -} diff --git a/packages/core-api/src/app/AppThemeProvider.tsx b/packages/core-api/src/app/AppThemeProvider.tsx deleted file mode 100644 index b6a88e6fcc..0000000000 --- a/packages/core-api/src/app/AppThemeProvider.tsx +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { useMemo, useEffect, useState, PropsWithChildren } from 'react'; -import { ThemeProvider, CssBaseline } from '@material-ui/core'; -import { useApi, appThemeApiRef, AppTheme } from '../apis'; -import { useObservable } from 'react-use'; - -// This tries to find the most accurate match, but also falls back to less -// accurate results in order to avoid errors. -function resolveTheme( - themeId: string | undefined, - shouldPreferDark: boolean, - themes: AppTheme[], -) { - if (themeId !== undefined) { - const selectedTheme = themes.find(theme => theme.id === themeId); - if (selectedTheme) { - return selectedTheme; - } - } - - if (shouldPreferDark) { - const darkTheme = themes.find(theme => theme.variant === 'dark'); - if (darkTheme) { - return darkTheme; - } - } - - const lightTheme = themes.find(theme => theme.variant === 'light'); - if (lightTheme) { - return lightTheme; - } - - return themes[0]; -} - -const useShouldPreferDarkTheme = () => { - const mediaQuery = useMemo( - () => window.matchMedia('(prefers-color-scheme: dark)'), - [], - ); - const [shouldPreferDark, setPrefersDark] = useState(mediaQuery.matches); - - useEffect(() => { - const listener = (event: MediaQueryListEvent) => { - setPrefersDark(event.matches); - }; - mediaQuery.addListener(listener); - return () => { - mediaQuery.removeListener(listener); - }; - }, [mediaQuery]); - - return shouldPreferDark; -}; - -export function AppThemeProvider({ children }: PropsWithChildren<{}>) { - const appThemeApi = useApi(appThemeApiRef); - const themeId = useObservable( - appThemeApi.activeThemeId$(), - appThemeApi.getActiveThemeId(), - ); - - // Browser feature detection won't change over time, so ignore lint rule - const shouldPreferDark = Boolean(window.matchMedia) - ? useShouldPreferDarkTheme() // eslint-disable-line react-hooks/rules-of-hooks - : false; - - const appTheme = resolveTheme( - themeId, - shouldPreferDark, - appThemeApi.getInstalledThemes(), - ); - if (!appTheme) { - throw new Error('App has no themes'); - } - - return ( - - {children} - - ); -} diff --git a/packages/core-api/src/app/index.ts b/packages/core-api/src/app/index.ts deleted file mode 100644 index 56e0800809..0000000000 --- a/packages/core-api/src/app/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 { useApp } from './AppContext'; -export * from './types'; diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts deleted file mode 100644 index a3eb6f423f..0000000000 --- a/packages/core-api/src/app/types.ts +++ /dev/null @@ -1,256 +0,0 @@ -/* - * Copyright 2020 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 { ComponentType } from 'react'; -import { IconComponent, IconComponentMap, IconKey } from '../icons/types'; -import { AnyExternalRoutes, BackstagePlugin } from '../plugin/types'; -import { ExternalRouteRef, RouteRef, SubRouteRef } from '../routing/types'; -import { AnyApiFactory } from '../apis/system'; -import { AppTheme, ProfileInfo } from '../apis/definitions'; -import { AppConfig } from '@backstage/config'; - -export type BootErrorPageProps = { - step: 'load-config' | 'load-chunk'; - error: Error; -}; - -export type SignInResult = { - /** - * User ID that will be returned by the IdentityApi - */ - userId: string; - - profile: ProfileInfo; - - /** - * Function used to retrieve an ID token for the signed in user. - */ - getIdToken?: () => Promise; - - /** - * Sign out handler that will be called if the user requests to sign out. - */ - signOut?: () => Promise; -}; - -export type SignInPageProps = { - /** - * Set the sign-in result for the app. This should only be called once. - */ - onResult(result: SignInResult): void; -}; - -export type AppComponents = { - NotFoundErrorPage: ComponentType<{}>; - BootErrorPage: ComponentType; - Progress: ComponentType<{}>; - Router: ComponentType<{}>; - - /** - * An optional sign-in page that will be rendered instead of the AppRouter at startup. - * - * If a sign-in page is set, it will always be shown before the app, and it is up - * to the sign-in page to handle e.g. saving of login methods for subsequent visits. - * - * The sign-in page will be displayed until it has passed up a result to the parent, - * and which point the AppRouter and all of its children will be rendered instead. - */ - SignInPage?: ComponentType; -}; - -/** - * A function that loads in the App config that will be accessible via the ConfigApi. - * - * If multiple config objects are returned in the array, values in the earlier configs - * will override later ones. - */ -export type AppConfigLoader = () => Promise; - -/** - * Extracts a union of the keys in a map whose value extends the given type - */ -type KeysWithType = { - [key in keyof Obj]: Obj[key] extends Type ? key : never; -}[keyof Obj]; - -/** - * Takes a map Map required values and makes all keys matching Keys optional - */ -type PartialKeys< - Map extends { [name in string]: any }, - Keys extends keyof Map -> = Partial> & Required>; - -/** - * Creates a map of target routes with matching parameters based on a map of external routes. - */ -type TargetRouteMap = { - [name in keyof ExternalRoutes]: ExternalRoutes[name] extends ExternalRouteRef< - infer Params, - any - > - ? RouteRef | SubRouteRef - : never; -}; - -export type AppRouteBinder = ( - externalRoutes: ExternalRoutes, - targetRoutes: PartialKeys< - TargetRouteMap, - KeysWithType> - >, -) => void; - -export type AppOptions = { - /** - * A collection of ApiFactories to register in the application to either - * add add new ones, or override factories provided by default or by plugins. - */ - apis?: Iterable; - - /** - * Supply icons to override the default ones. - */ - icons?: IconComponentMap; - - /** - * A list of all plugins to include in the app. - */ - plugins?: BackstagePlugin[]; - - /** - * Supply components to the app to override the default ones. - */ - components?: Partial; - - /** - * Themes provided as a part of the app. By default two themes are included, one - * light variant of the default backstage theme, and one dark. - * - * This is the default config: - * - * ``` - * [{ - * id: 'light', - * title: 'Light Theme', - * variant: 'light', - * theme: lightTheme, - * icon: , - * }, { - * id: 'dark', - * title: 'Dark Theme', - * variant: 'dark', - * theme: darkTheme, - * icon: , - * }] - * ``` - */ - themes?: AppTheme[]; - - /** - * A function that loads in App configuration that will be accessible via - * the ConfigApi. - * - * Defaults to an empty config. - * - * TODO(Rugvip): Omitting this should instead default to loading in configuration - * that was packaged by the backstage-cli and default docker container boot script. - */ - configLoader?: AppConfigLoader; - - /** - * A function that is used to register associations between cross-plugin route - * references, enabling plugins to navigate between each other. - * - * The `bind` function that is passed in should be used to bind all external - * routes of all used plugins. - * - * ```ts - * bindRoutes({ bind }) { - * bind(docsPlugin.externalRoutes, { - * homePage: managePlugin.routes.managePage, - * }) - * bind(homePagePlugin.externalRoutes, { - * settingsPage: settingsPlugin.routes.settingsPage, - * }) - * } - * ``` - */ - bindRoutes?(context: { bind: AppRouteBinder }): void; -}; - -export type BackstageApp = { - /** - * Returns all plugins registered for the app. - */ - getPlugins(): BackstagePlugin[]; - - /** - * Get a common or custom icon for this app. - */ - getSystemIcon(key: IconKey): IconComponent | undefined; - - /** - * Provider component that should wrap the Router created with getRouter() - * and any other components that need to be within the app context. - */ - getProvider(): ComponentType<{}>; - - /** - * Router component that should wrap the App Routes create with getRoutes() - * and any other components that should only be available while signed in. - */ - getRouter(): ComponentType<{}>; - - /** - * Routes component that contains all routes for plugin pages in the app. - * - * @deprecated Registering routes in plugins is deprecated and this method will be removed. - */ - getRoutes(): JSX.Element[]; -}; - -export type AppContext = { - /** - * @deprecated Will be removed - */ - getPlugins(): BackstagePlugin[]; - - /** - * Get a common or custom icon for this app. - */ - getSystemIcon(key: IconKey): IconComponent | undefined; - - /** - * Get the components registered for various purposes in the app. - */ - getComponents(): AppComponents; - - /** - * @deprecated Will be removed - */ - getProvider(): ComponentType<{}>; - - /** - * @deprecated Will be removed - */ - getRouter(): ComponentType<{}>; - - /** - * @deprecated Will be removed - */ - getRoutes(): JSX.Element[]; -}; diff --git a/packages/core-api/src/extensions/componentData.test.tsx b/packages/core-api/src/extensions/componentData.test.tsx deleted file mode 100644 index 808ab08cf1..0000000000 --- a/packages/core-api/src/extensions/componentData.test.tsx +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { attachComponentData, getComponentData } from './componentData'; - -describe('elementData', () => { - it('should attach a single piece of data', () => { - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - - it('should attach several distinct pieces of data', () => { - const data1 = { foo: 'bar' }; - const data2 = { test: 'value' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data1); - attachComponentData(Component, 'second', data2); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data1); - expect(getComponentData(element, 'second')).toBe(data2); - }); - - it('returns undefined for missing data', () => { - const data = { foo: 'bar' }; - const Component1 = () => null; - const Component2 = () => null; - attachComponentData(Component2, 'my-data', data); - - const element1 = ; - const element2 = ; - expect(getComponentData(element1, 'missing')).toBeUndefined(); - expect(getComponentData(element2, 'missing')).toBeUndefined(); - }); - - it('should throw when attempting to overwrite data', () => { - const data = { foo: 'bar' }; - const MyComponent = () => null; - attachComponentData(MyComponent, 'my-data', data); - expect(() => attachComponentData(MyComponent, 'my-data', data)).toThrow( - 'Attempted to attach duplicate data "my-data" to component "MyComponent"', - ); - }); - - describe('works across versions', () => { - function getDataSymbol() { - const Component = () => null; - attachComponentData(Component, 'my-data', {}); - const [symbol] = Object.getOwnPropertySymbols(Component); - return symbol; - } - - it('should should be able to get data from older versions', () => { - const symbol = getDataSymbol(); - - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - expect((element as any).type[symbol].map.get('my-data')).toBe(data); - }); - - it('should should be able to attach data for older versions', () => { - const symbol = getDataSymbol(); - - const data = { foo: 'bar' }; - const Component = () => null; - (Component as any)[symbol] = { - map: new Map([['my-data', data]]), - }; - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - - it('should be able to get data from newer versions', () => { - const data = { foo: 'bar' }; - const Component = () => null; - attachComponentData(Component, 'my-data', data); - - const element = ; - const container = (global as any)[ - '__@backstage/component-data-store__' - ].get(element.type); - expect(container.map.get('my-data')).toBe(data); - }); - - it('should should be able to attach data for newer versions', () => { - const data = { foo: 'bar' }; - const Component = () => null; - (global as any)['__@backstage/component-data-store__'].set(Component, { - map: new Map([['my-data', data]]), - }); - - const element = ; - expect(getComponentData(element, 'my-data')).toBe(data); - }); - }); -}); diff --git a/packages/core-api/src/extensions/componentData.tsx b/packages/core-api/src/extensions/componentData.tsx deleted file mode 100644 index d4975d9eef..0000000000 --- a/packages/core-api/src/extensions/componentData.tsx +++ /dev/null @@ -1,84 +0,0 @@ -/* - * Copyright 2020 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 { ComponentType, ReactNode } from 'react'; -import { getOrCreateGlobalSingleton } from '../lib/globalObject'; - -// TODO(Rugvip): Access via symbol is deprecated, remove once on 0.3.x -const DATA_KEY = Symbol('backstage-component-data'); - -type ComponentWithData

    = ComponentType

    & { - [DATA_KEY]?: DataContainer; -}; - -type DataContainer = { - map: Map; -}; - -type MaybeComponentNode = ReactNode & { - type?: ComponentType & { [DATA_KEY]?: DataContainer }; -}; - -// The store is bridged across versions using the global object -const store = getOrCreateGlobalSingleton( - 'component-data-store', - () => new WeakMap, DataContainer>(), -); - -export function attachComponentData

    ( - component: ComponentType

    , - type: string, - data: unknown, -) { - const dataComponent = component as ComponentWithData

    ; - - let container = store.get(component) || dataComponent[DATA_KEY]; - if (!container) { - container = { map: new Map() }; - store.set(component, container); - dataComponent[DATA_KEY] = container; - } - - if (container.map.has(type)) { - const name = component.displayName || component.name; - throw new Error( - `Attempted to attach duplicate data "${type}" to component "${name}"`, - ); - } - - container.map.set(type, data); -} - -export function getComponentData( - node: ReactNode, - type: string, -): T | undefined { - if (!node) { - return undefined; - } - - const component = (node as MaybeComponentNode).type; - if (!component) { - return undefined; - } - - const container = store.get(component) || component[DATA_KEY]; - if (!container) { - return undefined; - } - - return container.map.get(type) as T | undefined; -} diff --git a/packages/core-api/src/extensions/extensions.test.tsx b/packages/core-api/src/extensions/extensions.test.tsx deleted file mode 100644 index bd649bc0f8..0000000000 --- a/packages/core-api/src/extensions/extensions.test.tsx +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { createPlugin } from '../plugin'; -import { createRouteRef } from '../routing'; -import { getComponentData } from './componentData'; -import { - createComponentExtension, - createReactExtension, - createRoutableExtension, -} from './extensions'; - -const plugin = createPlugin({ - id: 'my-plugin', -}); - -describe('extensions', () => { - it('should create a react extension with component data', () => { - const Component = () =>

    ; - - const extension = createReactExtension({ - component: { - sync: Component, - }, - data: { - myData: { foo: 'bar' }, - }, - }); - - const ExtensionComponent = plugin.provide(extension); - const element = ; - - expect(getComponentData(element, 'core.plugin')).toBe(plugin); - expect(getComponentData(element, 'myData')).toEqual({ foo: 'bar' }); - }); - - it('should create react extensions of different types', () => { - const Component = () =>
    ; - const routeRef = createRouteRef({ path: '/foo', title: 'Foo' }); - - const extension1 = createComponentExtension({ - component: { - sync: Component, - }, - }); - - const extension2 = createRoutableExtension({ - component: () => Promise.resolve(Component), - mountPoint: routeRef, - }); - - const ExtensionComponent1 = plugin.provide(extension1); - const ExtensionComponent2 = plugin.provide(extension2); - - const element1 = ; - const element2 = ; - - expect(getComponentData(element1, 'core.plugin')).toBe(plugin); - expect(getComponentData(element2, 'core.plugin')).toBe(plugin); - expect(getComponentData(element2, 'core.mountPoint')).toBe(routeRef); - }); -}); diff --git a/packages/core-api/src/extensions/extensions.tsx b/packages/core-api/src/extensions/extensions.tsx deleted file mode 100644 index bca4c25f97..0000000000 --- a/packages/core-api/src/extensions/extensions.tsx +++ /dev/null @@ -1,141 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { lazy, Suspense } from 'react'; -import { useApp } from '../app'; -import { BackstagePlugin, Extension } from '../plugin/types'; -import { RouteRef, useRouteRef } from '../routing'; -import { attachComponentData } from './componentData'; - -type ComponentLoader = - | { - lazy: () => Promise; - } - | { - sync: T; - }; - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createRoutableExtension< - T extends (props: any) => JSX.Element | null ->(options: { - component: () => Promise; - mountPoint: RouteRef; -}): Extension { - const { component, mountPoint } = options; - return createReactExtension({ - component: { - lazy: () => - component().then( - InnerComponent => { - const RoutableExtensionWrapper: any = (props: any) => { - // Validate that the routing is wired up correctly in the App.tsx - try { - useRouteRef(mountPoint); - } catch (error) { - if (error?.message.startsWith('No path for ')) { - throw new Error( - `Routable extension component with mount point ${mountPoint} was not discovered in the app element tree. ` + - 'Routable extension components may not be rendered by other components and must be ' + - 'directly available as an element within the App provider component.', - ); - } - throw error; - } - return ; - }; - - const componentName = - (InnerComponent as { displayName?: string }).displayName || - InnerComponent.name || - 'LazyComponent'; - - RoutableExtensionWrapper.displayName = `RoutableExtension(${componentName})`; - - return RoutableExtensionWrapper as T; - }, - error => { - const RoutableExtensionWrapper: any = (_: any) => { - const app = useApp(); - const { BootErrorPage } = app.getComponents(); - - return ; - }; - return RoutableExtensionWrapper; - }, - ), - }, - data: { - 'core.mountPoint': mountPoint, - }, - }); -} - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createComponentExtension< - T extends (props: any) => JSX.Element | null ->(options: { component: ComponentLoader }): Extension { - const { component } = options; - return createReactExtension({ component }); -} - -// We do not use ComponentType as the return type, since it doesn't let us convey the children prop. -// ComponentType inserts children as an optional prop whether the inner component accepts it or not, -// making it impossible to make the usage of children type safe. -export function createReactExtension< - T extends (props: any) => JSX.Element | null ->(options: { - component: ComponentLoader; - data?: Record; -}): Extension { - const { data = {} } = options; - - let Component: T; - if ('lazy' in options.component) { - const lazyLoader = options.component.lazy; - Component = (lazy(() => - lazyLoader().then(component => ({ default: component })), - ) as unknown) as T; - } else { - Component = options.component.sync; - } - const componentName = - (Component as { displayName?: string }).displayName || - Component.name || - 'Component'; - - return { - expose(plugin: BackstagePlugin) { - const Result: any = (props: any) => ( - - - - ); - - attachComponentData(Result, 'core.plugin', plugin); - for (const [key, value] of Object.entries(data)) { - attachComponentData(Result, key, value); - } - - Result.displayName = `Extension(${componentName})`; - return Result; - }, - }; -} diff --git a/packages/core-api/src/extensions/index.ts b/packages/core-api/src/extensions/index.ts deleted file mode 100644 index 914d76aebf..0000000000 --- a/packages/core-api/src/extensions/index.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020 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 { attachComponentData, getComponentData } from './componentData'; -export { - createReactExtension, - createRoutableExtension, - createComponentExtension, -} from './extensions'; diff --git a/packages/core-api/src/extensions/traversal.test.tsx b/packages/core-api/src/extensions/traversal.test.tsx deleted file mode 100644 index 0acc51fb27..0000000000 --- a/packages/core-api/src/extensions/traversal.test.tsx +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { Children, isValidElement } from 'react'; -import { - childDiscoverer, - createCollector, - traverseElementTree, -} from './traversal'; - -describe('discovery', () => { - it('should collect element names', () => { - const root = ( -
    -
    -

    Title

    -

    Text

    -
    -
    -
    -

    Title

    - Text -
    -
    - ); - - const { names } = traverseElementTree({ - root, - discoverers: [childDiscoverer], - collectors: { - names: createCollector( - () => Array(), - (acc, el) => { - if (typeof el.type === 'string') { - acc.push(el.type); - } - }, - ), - }, - }); - - expect(names).toEqual([ - 'main', - 'div', - 'hr', - 'div', - 'h1', - 'p', - 'h2', - 'span', - ]); - }); - - it('should collect element names while skipping one level of children', () => { - const root = ( -
    -
    -

    Title

    -

    Text

    -
    -
    -
    -

    Title

    - Text -
    -
    - ); - - const { names } = traverseElementTree({ - root, - discoverers: [ - el => - Children.toArray(el.props.children).flatMap(child => - isValidElement(child) ? child?.props?.children : [], - ), - ], - collectors: { - names: createCollector( - () => Array(), - (acc, el) => { - if (typeof el.type === 'string') { - acc.push(el.type); - } - }, - ), - }, - }); - - expect(names).toEqual(['main', 'h1', 'p', 'h2', 'span']); - }); -}); diff --git a/packages/core-api/src/extensions/traversal.ts b/packages/core-api/src/extensions/traversal.ts deleted file mode 100644 index 79f44523b3..0000000000 --- a/packages/core-api/src/extensions/traversal.ts +++ /dev/null @@ -1,129 +0,0 @@ -/* - * Copyright 2020 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 { isValidElement, ReactNode, ReactElement, Children } from 'react'; - -export type Discoverer = (element: ReactElement) => ReactNode; - -export type Collector = () => { - accumulator: Result; - visit( - accumulator: Result, - element: ReactElement, - parent: ReactElement | undefined, - context: Context, - ): Context; -}; - -/** - * A function that allows you to traverse a tree of React elements using - * varying methods to discover child nodes and collect data along the way. - */ -export function traverseElementTree(options: { - root: ReactNode; - discoverers: Discoverer[]; - collectors: { [name in keyof Results]: Collector }; -}): Results { - const collectors: { - [name in string]: ReturnType>; - } = {}; - - // Bootstrap all collectors, initializing the accumulators and providing the visitor function - for (const name in options.collectors) { - if (options.collectors.hasOwnProperty(name)) { - collectors[name] = options.collectors[name](); - } - } - - // Internal representation of an element in the tree that we're iterating over - type QueueItem = { - node: ReactNode; - parent: ReactElement | undefined; - contexts: { [name in string]: unknown }; - }; - - const queue = [ - { - node: Children.toArray(options.root), - parent: undefined, - contexts: {}, - } as QueueItem, - ]; - - while (queue.length !== 0) { - const { node, parent, contexts } = queue.shift()!; - - // While the parent and the element we pass on to collectors and discoverers - // have been validated and are known to be React elements, the child nodes - // emitted by the discoverers are not. - Children.forEach(node, element => { - if (!isValidElement(element)) { - return; - } - - const nextContexts: QueueItem['contexts'] = {}; - - // Collectors populate their result data using the current node, and compute - // context for the next iteration - for (const name in collectors) { - if (collectors.hasOwnProperty(name)) { - const collector = collectors[name]; - - nextContexts[name] = collector.visit( - collector.accumulator, - element, - parent, - contexts[name], - ); - } - } - - // Discoverers provide ways to continue the traversal from the current element - for (const discoverer of options.discoverers) { - const children = discoverer(element); - if (children) { - queue.push({ - node: children, - parent: element, - contexts: nextContexts, - }); - } - } - }); - } - - return Object.fromEntries( - Object.entries(collectors).map(([name, c]) => [name, c.accumulator]), - ) as Results; -} - -export function createCollector( - accumulatorFactory: () => Result, - visit: ReturnType>['visit'], -): Collector { - return () => ({ accumulator: accumulatorFactory(), visit }); -} - -export function childDiscoverer(element: ReactElement): ReactNode { - return element.props?.children; -} - -export function routeElementDiscoverer(element: ReactElement): ReactNode { - if (element.props?.path && element.props?.element) { - return element.props?.element; - } - return undefined; -} diff --git a/packages/core-api/src/icons/icons.tsx b/packages/core-api/src/icons/icons.tsx deleted file mode 100644 index c90ee5cfc4..0000000000 --- a/packages/core-api/src/icons/icons.tsx +++ /dev/null @@ -1,67 +0,0 @@ -/* - * Copyright 2020 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 { SvgIconProps } from '@material-ui/core'; -import MuiMenuBookIcon from '@material-ui/icons/MenuBook'; -import MuiBrokenImageIcon from '@material-ui/icons/BrokenImage'; -import MuiChatIcon from '@material-ui/icons/Chat'; -import MuiDashboardIcon from '@material-ui/icons/Dashboard'; -import MuiEmailIcon from '@material-ui/icons/Email'; -import MuiGitHubIcon from '@material-ui/icons/GitHub'; -import MuiHelpIcon from '@material-ui/icons/Help'; -import MuiPeopleIcon from '@material-ui/icons/People'; -import MuiPersonIcon from '@material-ui/icons/Person'; -import MuiWarningIcon from '@material-ui/icons/Warning'; -import MuiDocsIcon from '@material-ui/icons/Description'; - -import React from 'react'; -import { useApp } from '../app/AppContext'; -import { IconComponent, IconComponentMap, SystemIconKey } from './types'; - -export const defaultSystemIcons: IconComponentMap = { - brokenImage: MuiBrokenImageIcon, - // To be confirmed: see https://github.com/backstage/backstage/issues/4970 - catalog: MuiMenuBookIcon, - chat: MuiChatIcon, - dashboard: MuiDashboardIcon, - email: MuiEmailIcon, - github: MuiGitHubIcon, - group: MuiPeopleIcon, - help: MuiHelpIcon, - user: MuiPersonIcon, - warning: MuiWarningIcon, - docs: MuiDocsIcon, -}; - -const overridableSystemIcon = (key: SystemIconKey): IconComponent => { - const Component = (props: SvgIconProps) => { - const app = useApp(); - const Icon = app.getSystemIcon(key); - return Icon ? : ; - }; - return Component; -}; - -export const BrokenImageIcon = overridableSystemIcon('brokenImage'); -export const ChatIcon = overridableSystemIcon('chat'); -export const DashboardIcon = overridableSystemIcon('dashboard'); -export const EmailIcon = overridableSystemIcon('email'); -export const GitHubIcon = overridableSystemIcon('github'); -export const GroupIcon = overridableSystemIcon('group'); -export const HelpIcon = overridableSystemIcon('help'); -export const UserIcon = overridableSystemIcon('user'); -export const WarningIcon = overridableSystemIcon('warning'); -export const DocsIcon = overridableSystemIcon('docs'); diff --git a/packages/core-api/src/icons/index.ts b/packages/core-api/src/icons/index.ts deleted file mode 100644 index 10045c4513..0000000000 --- a/packages/core-api/src/icons/index.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 './icons'; -export * from './types'; diff --git a/packages/core-api/src/icons/types.ts b/packages/core-api/src/icons/types.ts deleted file mode 100644 index bfaf9c5122..0000000000 --- a/packages/core-api/src/icons/types.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 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 { ComponentType } from 'react'; -import { SvgIconProps } from '@material-ui/core'; - -export type SystemIconKey = - | 'brokenImage' - | 'chat' - | 'dashboard' - | 'email' - | 'github' - | 'group' - | 'help' - | 'user' - | 'warning' - | 'docs'; - -export type IconComponent = ComponentType; -export type IconKey = SystemIconKey | string; -export type IconComponentMap = { [key in IconKey]: IconComponent }; diff --git a/packages/core-api/src/index.ts b/packages/core-api/src/index.ts deleted file mode 100644 index 9d4b2f770a..0000000000 --- a/packages/core-api/src/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 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 './public'; -import * as privateExports from './private'; - -export default privateExports; diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts deleted file mode 100644 index 524a0c5709..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.test.ts +++ /dev/null @@ -1,182 +0,0 @@ -/* - * Copyright 2020 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 ProviderIcon from '@material-ui/icons/AcUnit'; -import { DefaultAuthConnector } from './DefaultAuthConnector'; -import MockOAuthApi from '../../apis/implementations/OAuthRequestApi/MockOAuthApi'; -import * as loginPopup from '../loginPopup'; -import { UrlPatternDiscovery } from '../../apis'; -import { msw } from '@backstage/test-utils'; -import { setupServer } from 'msw/node'; -import { rest } from 'msw'; - -const defaultOptions = { - discoveryApi: UrlPatternDiscovery.compile('http://my-host/api/{{pluginId}}'), - environment: 'production', - provider: { - id: 'my-provider', - title: 'My Provider', - icon: ProviderIcon, - }, - oauthRequestApi: new MockOAuthApi(), - sessionTransform: ({ expiresInSeconds, ...res }: any) => ({ - ...res, - scopes: new Set(res.scopes.split(' ')), - expiresAt: new Date(Date.now() + expiresInSeconds * 1000), - }), -}; - -describe('DefaultAuthConnector', () => { - const server = setupServer(); - msw.setupDefaultHandlers(server); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should refresh a session', async () => { - server.use( - rest.get('*', (_req, res, ctx) => - res( - ctx.json({ - idToken: 'mock-id-token', - accessToken: 'mock-access-token', - scopes: 'a b c', - expiresInSeconds: '60', - }), - ), - ), - ); - - const helper = new DefaultAuthConnector(defaultOptions); - const session = await helper.refreshSession(); - expect(session.idToken).toBe('mock-id-token'); - expect(session.accessToken).toBe('mock-access-token'); - expect(session.scopes).toEqual(new Set(['a', 'b', 'c'])); - expect(session.expiresAt.getTime()).toBeLessThan(Date.now() + 70000); - expect(session.expiresAt.getTime()).toBeGreaterThan(Date.now() + 50000); - }); - - it('should handle failure to refresh session', async () => { - server.use( - rest.get('*', (_req, res, ctx) => - res(ctx.status(500, 'Error: Network NOPE')), - ), - ); - - const helper = new DefaultAuthConnector(defaultOptions); - await expect(helper.refreshSession()).rejects.toThrow( - 'Auth refresh request failed, Error: Network NOPE', - ); - }); - - it('should handle failure response when refreshing session', async () => { - server.use(rest.get('*', (_req, res, ctx) => res(ctx.status(401, 'NOPE')))); - - const helper = new DefaultAuthConnector(defaultOptions); - await expect(helper.refreshSession()).rejects.toThrow( - 'Auth refresh request failed, NOPE', - ); - }); - - it('should fail if popup was rejected', async () => { - const mockOauth = new MockOAuthApi(); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: mockOauth, - }); - const promise = helper.createSession({ scopes: new Set(['a', 'b']) }); - await mockOauth.rejectAll(); - await expect(promise).rejects.toMatchObject({ name: 'RejectedError' }); - }); - - it('should create a session', async () => { - const mockOauth = new MockOAuthApi(); - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue({ - idToken: 'my-id-token', - accessToken: 'my-access-token', - scopes: 'a b', - expiresInSeconds: 3600, - }); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: mockOauth, - }); - - const sessionPromise = helper.createSession({ - scopes: new Set(['a', 'b']), - }); - - await mockOauth.triggerAll(); - - expect(popupSpy).toBeCalledTimes(1); - expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=a%20b&env=production', - }); - - await expect(sessionPromise).resolves.toEqual({ - idToken: 'my-id-token', - accessToken: 'my-access-token', - scopes: expect.any(Set), - expiresAt: expect.any(Date), - }); - }); - - it('should instantly show popup if option is set', async () => { - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue('my-session'); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - oauthRequestApi: new MockOAuthApi(), - sessionTransform: str => str, - }); - - const sessionPromise = helper.createSession({ - scopes: new Set(), - instantPopup: true, - }); - - await expect(sessionPromise).resolves.toBe('my-session'); - - expect(popupSpy).toBeCalledTimes(1); - }); - - it('should use join func to join scopes', async () => { - const mockOauth = new MockOAuthApi(); - const popupSpy = jest - .spyOn(loginPopup, 'showLoginPopup') - .mockResolvedValue({ scopes: '' }); - const helper = new DefaultAuthConnector({ - ...defaultOptions, - joinScopes: scopes => `-${[...scopes].join('')}-`, - oauthRequestApi: mockOauth, - }); - - helper.createSession({ scopes: new Set(['a', 'b']) }); - - await mockOauth.triggerAll(); - - expect(popupSpy).toBeCalledTimes(1); - expect(popupSpy.mock.calls[0][0]).toMatchObject({ - url: - 'http://my-host/api/auth/my-provider/start?scope=-ab-&env=production', - }); - }); -}); diff --git a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts deleted file mode 100644 index 41a281dc6c..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DefaultAuthConnector.ts +++ /dev/null @@ -1,204 +0,0 @@ -/* - * Copyright 2020 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 { - AuthRequester, - OAuthRequestApi, - AuthProvider, - DiscoveryApi, -} from '../../apis/definitions'; -import { showLoginPopup } from '../loginPopup'; -import { AuthConnector, CreateSessionOptions } from './types'; - -type Options = { - /** - * DiscoveryApi instance used to locate the auth backend endpoint. - */ - discoveryApi: DiscoveryApi; - /** - * Environment hint passed on to auth backend, for example 'production' or 'development' - */ - environment: string; - /** - * Information about the auth provider to be shown to the user. - * The ID Must match the backend auth plugin configuration, for example 'google'. - */ - provider: AuthProvider & { id: string }; - /** - * API used to instantiate an auth requester. - */ - oauthRequestApi: OAuthRequestApi; - /** - * Function used to join together a set of scopes, defaults to joining with a space character. - */ - joinScopes?: (scopes: Set) => string; - /** - * Function used to transform an auth response into the session type. - */ - sessionTransform?(response: any): AuthSession | Promise; -}; - -function defaultJoinScopes(scopes: Set) { - return [...scopes].join(' '); -} - -/** - * DefaultAuthConnector is the default auth connector in Backstage. It talks to the - * backend auth plugin through the standardized API, and requests user permission - * via the OAuthRequestApi. - */ -export class DefaultAuthConnector - implements AuthConnector { - private readonly discoveryApi: DiscoveryApi; - private readonly environment: string; - private readonly provider: AuthProvider & { id: string }; - private readonly joinScopesFunc: (scopes: Set) => string; - private readonly authRequester: AuthRequester; - private readonly sessionTransform: (response: any) => Promise; - - constructor(options: Options) { - const { - discoveryApi, - environment, - provider, - joinScopes = defaultJoinScopes, - oauthRequestApi, - sessionTransform = id => id, - } = options; - - this.authRequester = oauthRequestApi.createAuthRequester({ - provider, - onAuthRequest: scopes => this.showPopup(scopes), - }); - - this.discoveryApi = discoveryApi; - this.environment = environment; - this.provider = provider; - this.joinScopesFunc = joinScopes; - this.sessionTransform = sessionTransform; - } - - async createSession(options: CreateSessionOptions): Promise { - if (options.instantPopup) { - return this.showPopup(options.scopes); - } - return this.authRequester(options.scopes); - } - - async refreshSession(): Promise { - const res = await fetch( - await this.buildUrl('/refresh', { optional: true }), - { - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }, - ).catch(error => { - throw new Error(`Auth refresh request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error( - `Auth refresh request failed, ${res.statusText}`, - ); - error.status = res.status; - throw error; - } - - const authInfo = await res.json(); - - if (authInfo.error) { - const error = new Error(authInfo.error.message); - if (authInfo.error.name) { - error.name = authInfo.error.name; - } - throw error; - } - return await this.sessionTransform(authInfo); - } - - async removeSession(): Promise { - const res = await fetch(await this.buildUrl('/logout'), { - method: 'POST', - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }).catch(error => { - throw new Error(`Logout request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error(`Logout request failed, ${res.statusText}`); - error.status = res.status; - throw error; - } - } - - private async showPopup(scopes: Set): Promise { - const scope = this.joinScopesFunc(scopes); - const popupUrl = await this.buildUrl('/start', { scope }); - - const payload = await showLoginPopup({ - url: popupUrl, - name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, - width: 450, - height: 730, - }); - - return await this.sessionTransform(payload); - } - - private async buildUrl( - path: string, - query?: { [key: string]: string | boolean | undefined }, - ): Promise { - const baseUrl = await this.discoveryApi.getBaseUrl('auth'); - const queryString = this.buildQueryString({ - ...query, - env: this.environment, - }); - - return `${baseUrl}/${this.provider.id}${path}${queryString}`; - } - - private buildQueryString(query?: { - [key: string]: string | boolean | undefined; - }): string { - if (!query) { - return ''; - } - - const queryString = Object.entries(query) - .map(([key, value]) => { - if (typeof value === 'string') { - return `${encodeURIComponent(key)}=${encodeURIComponent(value)}`; - } else if (value) { - return encodeURIComponent(key); - } - return undefined; - }) - .filter(Boolean) - .join('&'); - - if (!queryString) { - return ''; - } - return `?${queryString}`; - } -} diff --git a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts deleted file mode 100644 index ea3704f7f9..0000000000 --- a/packages/core-api/src/lib/AuthConnector/DirectAuthConnector.ts +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright 2020 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 { AuthProvider, DiscoveryApi } from '../../apis/definitions'; -import { showLoginPopup } from '../loginPopup'; - -type Options = { - discoveryApi: DiscoveryApi; - environment?: string; - provider: AuthProvider & { id: string }; -}; -export class DirectAuthConnector { - private readonly discoveryApi: DiscoveryApi; - private readonly environment: string | undefined; - private readonly provider: AuthProvider & { id: string }; - - constructor(options: Options) { - const { discoveryApi, environment, provider } = options; - - this.discoveryApi = discoveryApi; - this.environment = environment; - this.provider = provider; - } - - async createSession(): Promise { - const popupUrl = await this.buildUrl('/start'); - const payload = await showLoginPopup({ - url: popupUrl, - name: `${this.provider.title} Login`, - origin: new URL(popupUrl).origin, - width: 450, - height: 730, - }); - - return { - ...payload, - id: payload.profile.email, - }; - } - - async refreshSession(): Promise {} - - async removeSession(): Promise { - const res = await fetch(await this.buildUrl('/logout'), { - method: 'POST', - headers: { - 'x-requested-with': 'XMLHttpRequest', - }, - credentials: 'include', - }).catch(error => { - throw new Error(`Logout request failed, ${error}`); - }); - - if (!res.ok) { - const error: any = new Error(`Logout request failed, ${res.statusText}`); - error.status = res.status; - throw error; - } - } - - private async buildUrl(path: string): Promise { - const baseUrl = await this.discoveryApi.getBaseUrl('auth'); - return `${baseUrl}/${this.provider.id}${path}?env=${this.environment}`; - } -} diff --git a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts deleted file mode 100644 index 0758c0ba29..0000000000 --- a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.test.ts +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright 2020 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 { MockAuthConnector, mockAccessToken } from './MockAuthConnector'; - -describe('MockAuthConnector', () => { - it('should return mock tokens', async () => { - const helper = new MockAuthConnector(); - - await expect(helper.createSession()).resolves.toEqual({ - accessToken: mockAccessToken, - expiresAt: expect.any(Date), - scopes: expect.any(String), - }); - - await expect(helper.refreshSession()).resolves.toEqual({ - accessToken: mockAccessToken, - expiresAt: expect.any(Date), - scopes: expect.any(String), - }); - }); -}); diff --git a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts b/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts deleted file mode 100644 index db978e7018..0000000000 --- a/packages/core-api/src/lib/AuthConnector/MockAuthConnector.ts +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright 2020 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 { AuthConnector } from './types'; - -export const mockAccessToken = 'mock-access-token'; - -type MockSession = { - accessToken: string; - expiresAt: Date; - scopes: string; -}; - -const defaultMockSession: MockSession = { - accessToken: mockAccessToken, - expiresAt: new Date(), - scopes: 'profile email', -}; - -export class MockAuthConnector implements AuthConnector { - constructor(private readonly mockSession: MockSession = defaultMockSession) {} - - async createSession() { - return this.mockSession; - } - - async refreshSession() { - return this.mockSession; - } - - async removeSession() {} -} diff --git a/packages/core-api/src/lib/AuthConnector/index.ts b/packages/core-api/src/lib/AuthConnector/index.ts deleted file mode 100644 index 047583f3ac..0000000000 --- a/packages/core-api/src/lib/AuthConnector/index.ts +++ /dev/null @@ -1,19 +0,0 @@ -/* - * Copyright 2020 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 { DefaultAuthConnector } from './DefaultAuthConnector'; -export { DirectAuthConnector } from './DirectAuthConnector'; -export * from './types'; diff --git a/packages/core-api/src/lib/AuthConnector/types.ts b/packages/core-api/src/lib/AuthConnector/types.ts deleted file mode 100644 index 464a2ed627..0000000000 --- a/packages/core-api/src/lib/AuthConnector/types.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 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 CreateSessionOptions = { - scopes: Set; - instantPopup?: boolean; -}; - -/** - * An AuthConnector is responsible for realizing auth session actions - * by for example communicating with a backend or interacting with the user. - */ -export type AuthConnector = { - createSession(options: CreateSessionOptions): Promise; - refreshSession(): Promise; - removeSession(): Promise; -}; diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts deleted file mode 100644 index 4ceadd51ba..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.test.ts +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright 2020 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 { AuthSessionStore } from './AuthSessionStore'; -import { SessionManager } from './types'; - -const defaultOptions = { - storageKey: 'my-key', - sessionScopes: (session: string) => new Set(session.split(' ')), -}; - -class LocalStorage { - private store: Record = {}; - - getItem(key: string) { - return this.store[key] || null; - } - setItem(key: string, value: string) { - this.store[key] = value.toString(); - } - removeItem(key: string) { - delete this.store[key]; - } -} - -class MockManager implements SessionManager { - setSession = jest.fn(); - getSession = jest.fn(); - removeSession = jest.fn(); - sessionState$ = jest.fn(); -} - -describe('GheAuth AuthSessionStore', () => { - beforeEach(() => { - delete (window as any).localStorage; - (window as any).localStorage = new LocalStorage(); - }); - - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should load session', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('a b c'); - expect(manager.getSession).not.toHaveBeenCalled(); - expect(manager.setSession).toHaveBeenCalledWith('a b c'); - }); - - it('should not use session without enough scope', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('a b c d'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({ scopes: new Set(['d']) })).resolves.toBe( - 'a b c d', - ); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should not use expired session', async () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ - manager, - ...defaultOptions, - sessionShouldRefresh: () => true, - }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should not load missing session', async () => { - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - - expect(localStorage.getItem('my-key')).toBe('"123"'); - }); - - it('should ignore bad session values', async () => { - localStorage.setItem('my-key', 'derp'); - - const manager = new MockManager(); - manager.getSession.mockResolvedValue('123'); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - - await expect(store.getSession({})).resolves.toBe('123'); - expect(manager.getSession).toHaveBeenCalledTimes(1); - expect(manager.setSession).not.toHaveBeenCalled(); - }); - - it('should clear session', () => { - localStorage.setItem('my-key', '"a b c"'); - - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - store.removeSession(); - - expect(localStorage.getItem('my-key')).toBe(null); - expect(manager.removeSession).toHaveBeenCalled(); - }); - - it('should forward sessionState calls', () => { - const manager = new MockManager(); - const store = new AuthSessionStore({ manager, ...defaultOptions }); - store.sessionState$(); - expect(manager.sessionState$).toHaveBeenCalled(); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts b/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts deleted file mode 100644 index 057a70e58d..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/AuthSessionStore.ts +++ /dev/null @@ -1,131 +0,0 @@ -/* - * Copyright 2020 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 { - SessionManager, - MutableSessionManager, - SessionScopesFunc, - SessionShouldRefreshFunc, - GetSessionOptions, -} from './types'; -import { SessionScopeHelper } from './common'; - -type Options = { - /** The connector used for acting on the auth session */ - manager: MutableSessionManager; - /** Storage key to use to store sessions */ - storageKey: string; - /** Used to get the scope of the session */ - sessionScopes?: SessionScopesFunc; - /** Used to check if the session needs to be refreshed, defaults to never refresh */ - sessionShouldRefresh?: SessionShouldRefreshFunc; -}; - -/** - * AuthSessionStore decorates another SessionManager with a functionality - * to store the session in local storage. - * - * Session is serialized to JSON with special support for following types: Set. - */ -export class AuthSessionStore implements SessionManager { - private readonly manager: MutableSessionManager; - private readonly storageKey: string; - private readonly sessionShouldRefreshFunc: SessionShouldRefreshFunc; - private readonly helper: SessionScopeHelper; - - constructor(options: Options) { - const { - manager, - storageKey, - sessionScopes, - sessionShouldRefresh = () => false, - } = options; - - this.manager = manager; - this.storageKey = storageKey; - this.sessionShouldRefreshFunc = sessionShouldRefresh; - this.helper = new SessionScopeHelper({ - sessionScopes, - defaultScopes: new Set(), - }); - } - - async getSession(options: GetSessionOptions): Promise { - const { scopes } = options; - const session = this.loadSession(); - - if (this.helper.sessionExistsAndHasScope(session, scopes)) { - const shouldRefresh = this.sessionShouldRefreshFunc(session!); - - if (!shouldRefresh) { - this.manager.setSession(session!); - return session!; - } - } - - const newSession = await this.manager.getSession(options); - this.saveSession(newSession); - return newSession; - } - - async removeSession() { - localStorage.removeItem(this.storageKey); - await this.manager.removeSession(); - } - - sessionState$() { - return this.manager.sessionState$(); - } - - private loadSession(): T | undefined { - try { - const sessionJson = localStorage.getItem(this.storageKey); - if (sessionJson) { - const session = JSON.parse(sessionJson, (_key, value) => { - if (value?.__type === 'Set') { - return new Set(value.__value); - } - return value; - }); - return session; - } - - return undefined; - } catch (error) { - localStorage.removeItem(this.storageKey); - return undefined; - } - } - - private saveSession(session: T | undefined) { - if (session === undefined) { - localStorage.removeItem(this.storageKey); - } else { - localStorage.setItem( - this.storageKey, - JSON.stringify(session, (_key, value) => { - if (value instanceof Set) { - return { - __type: 'Set', - __value: Array.from(value), - }; - } - return value; - }), - ); - } - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts deleted file mode 100644 index 99a10002c3..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.test.ts +++ /dev/null @@ -1,168 +0,0 @@ -/* - * Copyright 2020 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 { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; -import { SessionState } from '../../apis'; - -const defaultOptions = { - sessionScopes: (session: { scopes: Set }) => session.scopes, - sessionShouldRefresh: (session: { expired: boolean }) => session.expired, -}; - -describe('RefreshingAuthSessionManager', () => { - it('should save result from createSession', async () => { - const createSession = jest.fn().mockResolvedValue({ expired: false }); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const removeSession = jest.fn(); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession, removeSession }, - ...defaultOptions, - } as any); - const stateSubscriber = jest.fn(); - manager.sessionState$().subscribe(stateSubscriber); - - await Promise.resolve(); // Wait a tick for observer to post a value - - expect(stateSubscriber.mock.calls).toEqual([[SessionState.SignedOut]]); - await manager.getSession({}); - expect(createSession).toBeCalledTimes(1); - - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - ]); - await manager.getSession({}); - expect(createSession).toBeCalledTimes(1); - - expect(refreshSession).toBeCalledTimes(1); - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - ]); - - expect(removeSession).toHaveBeenCalledTimes(0); - await manager.removeSession(); - expect(removeSession).toHaveBeenCalledTimes(1); - expect(stateSubscriber.mock.calls).toEqual([ - [SessionState.SignedOut], - [SessionState.SignedIn], - [SessionState.SignedOut], - ]); - }); - - it('should ask consent only if scopes have changed', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockResolvedValue({ - scopes: new Set(['a']), - expired: false, - }); - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['b']) }); - expect(createSession).toBeCalledTimes(2); - }); - - it('should check for session expiry', async () => { - const createSession = jest.fn(); - const refreshSession = jest - .fn() - .mockRejectedValueOnce(new Error('NOPE')) - .mockResolvedValue({ scopes: new Set(['a']) }); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockResolvedValue({ - scopes: new Set(['a']), - expired: true, - }); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - expect(refreshSession).toBeCalledTimes(1); - - await manager.getSession({ scopes: new Set(['a']) }); - expect(createSession).toBeCalledTimes(1); - expect(refreshSession).toBeCalledTimes(2); - }); - - it('should handle user closed popup', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - createSession.mockRejectedValueOnce(new Error('some error')); - await expect( - manager.getSession({ scopes: new Set(['a']) }), - ).rejects.toThrow('some error'); - }); - - it('should not get optional session', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - expect(await manager.getSession({ optional: true })).toBe(undefined); - expect(createSession).toBeCalledTimes(0); - expect(refreshSession).toBeCalledTimes(1); - }); - - it('should forward option to instantly show auth popup and not attempt refresh', async () => { - const createSession = jest.fn(); - const refreshSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new RefreshingAuthSessionManager({ - connector: { createSession, refreshSession }, - ...defaultOptions, - } as any); - - expect(await manager.getSession({ instantPopup: true })).toBe(undefined); - expect(createSession).toBeCalledTimes(1); - expect(createSession).toHaveBeenCalledWith({ - scopes: new Set(), - instantPopup: true, - }); - expect(refreshSession).toBeCalledTimes(0); - }); - - it('should remove session straight away', async () => { - const removeSession = jest.fn(); - const manager = new RefreshingAuthSessionManager({ - connector: { removeSession }, - ...defaultOptions, - } as any); - - await manager.removeSession(); - expect(removeSession).toHaveBeenCalled(); - expect(await manager.getSession({ optional: true })).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts deleted file mode 100644 index d31f5e29bf..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/RefreshingAuthSessionManager.ts +++ /dev/null @@ -1,148 +0,0 @@ -/* - * Copyright 2020 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 { - SessionManager, - SessionScopesFunc, - SessionShouldRefreshFunc, - GetSessionOptions, -} from './types'; -import { AuthConnector } from '../AuthConnector'; -import { SessionScopeHelper, hasScopes } from './common'; -import { SessionStateTracker } from './SessionStateTracker'; - -type Options = { - /** The connector used for acting on the auth session */ - connector: AuthConnector; - /** Used to get the scope of the session */ - sessionScopes: SessionScopesFunc; - /** Used to check if the session needs to be refreshed */ - sessionShouldRefresh: SessionShouldRefreshFunc; - /** The default scopes that should always be present in a session, defaults to none. */ - defaultScopes?: Set; -}; - -/** - * RefreshingAuthSessionManager manages an underlying session that has - * and expiration time and needs to be refreshed periodically. - */ -export class RefreshingAuthSessionManager implements SessionManager { - private readonly connector: AuthConnector; - private readonly helper: SessionScopeHelper; - private readonly sessionScopesFunc: SessionScopesFunc; - private readonly sessionShouldRefreshFunc: SessionShouldRefreshFunc; - private readonly stateTracker = new SessionStateTracker(); - - private refreshPromise?: Promise; - private currentSession: T | undefined; - - constructor(options: Options) { - const { - connector, - defaultScopes = new Set(), - sessionScopes, - sessionShouldRefresh, - } = options; - - this.connector = connector; - this.sessionScopesFunc = sessionScopes; - this.sessionShouldRefreshFunc = sessionShouldRefresh; - this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); - } - - async getSession(options: GetSessionOptions): Promise { - if ( - this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) - ) { - const shouldRefresh = this.sessionShouldRefreshFunc(this.currentSession!); - if (!shouldRefresh) { - return this.currentSession!; - } - - try { - const refreshedSession = await this.collapsedSessionRefresh(); - const currentScopes = this.sessionScopesFunc(this.currentSession!); - const refreshedScopes = this.sessionScopesFunc(refreshedSession); - if (hasScopes(refreshedScopes, currentScopes)) { - this.currentSession = refreshedSession; - } - return refreshedSession; - } catch (error) { - if (options.optional) { - return undefined; - } - throw error; - } - } - - // The user may still have a valid refresh token in their cookies. Attempt to - // initiate a fresh session through the backend using that refresh token. - // - // We skip this check if an instant login popup is requested, as we need to - // stay in a synchronous call stack from the user interaction. The downside - // is that that the user will sometimes be requested to log in even if they - // already had an existing session. - if (!this.currentSession && !options.instantPopup) { - try { - const newSession = await this.collapsedSessionRefresh(); - this.currentSession = newSession; - // The session might not have the scopes requested so go back and check again - return this.getSession(options); - } catch { - // If the refresh attempt fails we assume we don't have a session, so continue to create one. - } - } - - // If we continue here we will show a popup, so exit if this is an optional session request. - if (options.optional) { - return undefined; - } - - // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession({ - ...options, - scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), - }); - this.stateTracker.setIsSignedIn(true); - return this.currentSession; - } - - async removeSession() { - this.currentSession = undefined; - await this.connector.removeSession(); - this.stateTracker.setIsSignedIn(false); - } - - sessionState$() { - return this.stateTracker.sessionState$(); - } - - private async collapsedSessionRefresh(): Promise { - if (this.refreshPromise) { - return this.refreshPromise; - } - - this.refreshPromise = this.connector.refreshSession(); - - try { - const session = await this.refreshPromise; - this.stateTracker.setIsSignedIn(true); - return session; - } finally { - delete this.refreshPromise; - } - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts b/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts deleted file mode 100644 index 525cfd7313..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/SessionStateTracker.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * Copyright 2020 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 { SessionState } from '../../apis/definitions'; -import { Observable } from '../../types'; -import { BehaviorSubject } from '../subjects'; - -export class SessionStateTracker { - private readonly subject = new BehaviorSubject( - SessionState.SignedOut, - ); - - private signedIn: boolean = false; - - setIsSignedIn(isSignedIn: boolean) { - if (this.signedIn !== isSignedIn) { - this.signedIn = isSignedIn; - this.subject.next( - this.signedIn ? SessionState.SignedIn : SessionState.SignedOut, - ); - } - } - - sessionState$(): Observable { - return this.subject; - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts deleted file mode 100644 index 26f489a1b4..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.test.ts +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright 2020 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 { StaticAuthSessionManager } from './StaticAuthSessionManager'; - -const defaultOptions = { - sessionScopes: (session: string) => new Set(session.split(' ')), -}; - -describe('StaticAuthSessionManager', () => { - const baseConnector = { - refreshSession() { - throw new Error('refreshSession should not be called'); - }, - removeSession() { - throw new Error('removeSession should not be called'); - }, - }; - - it('should get session by creating session once', async () => { - const createSession = jest.fn().mockResolvedValue('my-session'); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({})).resolves.toBe('my-session'); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({})).resolves.toBe('my-session'); - expect(createSession).toHaveBeenCalledTimes(1); - }); - - it('should fail to get session if user rejects the request', async () => { - const createSession = jest.fn().mockRejectedValue(new Error('NOPE')); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({})).rejects.toThrow('NOPE'); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ optional: true })).resolves.toBe( - undefined, - ); - }); - - it('should only request auth once for same scopes', async () => { - const createSession = jest - .fn() - .mockImplementation(({ scopes }) => [...scopes].join(' ')); - const manager = new StaticAuthSessionManager({ - connector: { createSession, ...baseConnector }, - ...defaultOptions, - }); - - expect(createSession).toHaveBeenCalledTimes(0); - await expect(manager.getSession({ scopes: new Set(['a']) })).resolves.toBe( - 'a', - ); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ scopes: new Set(['a']) })).resolves.toBe( - 'a', - ); - expect(createSession).toHaveBeenCalledTimes(1); - await expect(manager.getSession({ scopes: new Set(['b']) })).resolves.toBe( - 'a b', - ); - expect(createSession).toHaveBeenCalledTimes(2); - }); - - it('should remove session and reload', async () => { - const removeSession = jest.fn(); - const manager = new StaticAuthSessionManager({ - connector: { removeSession }, - ...defaultOptions, - } as any); - - await manager.removeSession(); - expect(removeSession).toHaveBeenCalled(); - expect(await manager.getSession({ optional: true })).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts b/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts deleted file mode 100644 index b7940d88c4..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/StaticAuthSessionManager.ts +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright 2020 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 { MutableSessionManager, GetSessionOptions } from './types'; -import { AuthConnector } from '../AuthConnector'; -import { SessionScopeHelper } from './common'; -import { SessionStateTracker } from './SessionStateTracker'; - -type Options = { - /** The connector used for acting on the auth session */ - connector: AuthConnector; - /** Used to get the scope of the session */ - sessionScopes?: (session: T) => Set; - /** The default scopes that should always be present in a session, defaults to none. */ - defaultScopes?: Set; -}; - -/** - * StaticAuthSessionManager manages an underlying session that does not expire. - */ -export class StaticAuthSessionManager implements MutableSessionManager { - private readonly connector: AuthConnector; - private readonly helper: SessionScopeHelper; - private readonly stateTracker = new SessionStateTracker(); - - private currentSession: T | undefined; - - constructor(options: Options) { - const { connector, defaultScopes = new Set(), sessionScopes } = options; - - this.connector = connector; - this.helper = new SessionScopeHelper({ sessionScopes, defaultScopes }); - } - - setSession(session: T | undefined): void { - this.currentSession = session; - this.stateTracker.setIsSignedIn(Boolean(session)); - } - - async getSession(options: GetSessionOptions): Promise { - if ( - this.helper.sessionExistsAndHasScope(this.currentSession, options.scopes) - ) { - return this.currentSession; - } - - // If we continue here we will show a popup, so exit if this is an optional session request. - if (options.optional) { - return undefined; - } - - // We can call authRequester multiple times, the returned session will contain all requested scopes. - this.currentSession = await this.connector.createSession({ - ...options, - scopes: this.helper.getExtendedScope(this.currentSession, options.scopes), - }); - this.stateTracker.setIsSignedIn(true); - return this.currentSession; - } - - async removeSession() { - this.currentSession = undefined; - await this.connector.removeSession(); - this.stateTracker.setIsSignedIn(false); - } - - sessionState$() { - return this.stateTracker.sessionState$(); - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/common.ts b/packages/core-api/src/lib/AuthSessionManager/common.ts deleted file mode 100644 index 002b6c616b..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/common.ts +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright 2020 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 { SessionScopesFunc } from './types'; - -export function hasScopes( - searched: Set, - searchFor: Set, -): boolean { - for (const scope of searchFor) { - if (!searched.has(scope)) { - return false; - } - } - return true; -} - -type ScopeHelperOptions = { - sessionScopes: SessionScopesFunc | undefined; - defaultScopes?: Set; -}; - -export class SessionScopeHelper { - constructor(private readonly options: ScopeHelperOptions) {} - - sessionExistsAndHasScope( - session: T | undefined, - scopes?: Set, - ): boolean { - if (!session) { - return false; - } - if (!scopes) { - return true; - } - if (this.options.sessionScopes === undefined) { - return true; - } - const sessionScopes = this.options.sessionScopes(session); - return hasScopes(sessionScopes, scopes); - } - - getExtendedScope(session: T | undefined, scopes?: Set) { - const newScope = new Set(this.options.defaultScopes); - if (session && this.options.sessionScopes !== undefined) { - const sessionScopes = this.options.sessionScopes(session); - for (const scope of sessionScopes) { - newScope.add(scope); - } - } - if (scopes) { - for (const scope of scopes) { - newScope.add(scope); - } - } - return newScope; - } -} diff --git a/packages/core-api/src/lib/AuthSessionManager/index.ts b/packages/core-api/src/lib/AuthSessionManager/index.ts deleted file mode 100644 index 85ef2013e9..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 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 { RefreshingAuthSessionManager } from './RefreshingAuthSessionManager'; -export { StaticAuthSessionManager } from './StaticAuthSessionManager'; -export { AuthSessionStore } from './AuthSessionStore'; -export * from './types'; diff --git a/packages/core-api/src/lib/AuthSessionManager/types.ts b/packages/core-api/src/lib/AuthSessionManager/types.ts deleted file mode 100644 index a58f838037..0000000000 --- a/packages/core-api/src/lib/AuthSessionManager/types.ts +++ /dev/null @@ -1,57 +0,0 @@ -/* - * Copyright 2020 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 { Observable } from '../../types'; -import { SessionState } from '../../apis/definitions'; - -export type GetSessionOptions = { - optional?: boolean; - instantPopup?: boolean; - scopes?: Set; -}; - -/** - * A sessions manager keeps track of the current session and makes sure that - * multiple simultaneous requests for sessions with different scope are handled - * in a correct way. - */ -export type SessionManager = { - getSession(options: GetSessionOptions): Promise; - - removeSession(): Promise; - - sessionState$(): Observable; -}; - -/** - * An extension of the session manager where the session can also be pushed from the manager. - */ -export interface MutableSessionManager extends SessionManager { - setSession(session: T | undefined): void; -} - -/** - * A function called to determine the scopes of a session. - */ -export type SessionScopesFunc = (session: T) => Set; - -/** - * A function called to determine whether it's time for a session to refresh. - * - * This should return true before the session expires, for example, if a session - * expires after 60 minutes, you could return true if the session is older than 45 minutes. - */ -export type SessionShouldRefreshFunc = (session: T) => boolean; diff --git a/packages/core-api/src/lib/globalObject.test.ts b/packages/core-api/src/lib/globalObject.test.ts deleted file mode 100644 index a658b253a6..0000000000 --- a/packages/core-api/src/lib/globalObject.test.ts +++ /dev/null @@ -1,90 +0,0 @@ -/* - * 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 { - getGlobalSingleton, - getOrCreateGlobalSingleton, - setGlobalSingleton, -} from './globalObject'; - -const anyGlobal = global as any; - -describe('getGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should return an existing value', () => { - const myThing = {}; - const myOtherThing = {}; - - anyGlobal['__@backstage/my-thing__'] = myThing; - expect(getGlobalSingleton('my-thing')).toBe(myThing); - expect(getGlobalSingleton('my-thing')).toBe(myThing); - anyGlobal['__@backstage/my-thing__'] = myOtherThing; - expect(getGlobalSingleton('my-thing')).toBe(myOtherThing); - }); - - it('should throw if the value is not set', () => { - expect(() => getGlobalSingleton('my-thing')).toThrow( - 'Global my-thing is not set', - ); - }); -}); - -describe('getOrCreateGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should return an existing value', () => { - const myThing = {}; - anyGlobal['__@backstage/my-thing__'] = myThing; - - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myThing); - }); - - it('should should create a new value', () => { - const myNewThing = {}; - - expect(anyGlobal['__@backstage/my-thing__']).toBe(undefined); - expect(getOrCreateGlobalSingleton('my-thing', () => myNewThing)).toBe( - myNewThing, - ); - expect(anyGlobal['__@backstage/my-thing__']).toBe(myNewThing); - expect(getOrCreateGlobalSingleton('my-thing', () => ({}))).toBe(myNewThing); - }); -}); - -describe('setGlobalSingleton', () => { - beforeEach(() => { - delete anyGlobal['__@backstage/my-thing__']; - }); - - it('should set a global value', () => { - setGlobalSingleton('my-thing', 'global value'); - - expect(anyGlobal['__@backstage/my-thing__']).toBe('global value'); - }); - - it('should throw if global value is set', () => { - anyGlobal['__@backstage/my-thing__'] = 'already defined'; - expect(() => setGlobalSingleton('my-thing', () => 'global value')).toThrow( - 'Global my-thing is already se', - ); - }); -}); diff --git a/packages/core-api/src/lib/globalObject.ts b/packages/core-api/src/lib/globalObject.ts deleted file mode 100644 index ad70a61110..0000000000 --- a/packages/core-api/src/lib/globalObject.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * 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. - */ - -// https://github.com/zloirock/core-js/issues/86#issuecomment-115759028 -function getGlobalObject() { - if (typeof window !== 'undefined' && window.Math === Math) { - return window; - } - if (typeof self !== 'undefined' && self.Math === Math) { - return self; - } - // eslint-disable-next-line no-new-func - return Function('return this')(); -} - -const globalObject = getGlobalObject(); - -const makeKey = (id: string) => `__@backstage/${id}__`; - -/** - * Used to provide a global singleton value, failing if it is already set. - */ -export function setGlobalSingleton(id: string, value: unknown): void { - const key = makeKey(id); - if (key in globalObject) { - throw new Error(`Global ${id} is already set`); // TODO some sort of special build err - } - globalObject[key] = value; -} - -/** - * Used to access a global singleton value, failing if it is not already set. - */ -export function getGlobalSingleton(id: string): T { - const key = makeKey(id); - if (!(key in globalObject)) { - throw new Error(`Global ${id} is not set`); // TODO some sort of special build err - } - - return globalObject[key]; -} - -/** - * Serializes access to a global singleton value, with the first caller creating the value. - */ -export function getOrCreateGlobalSingleton( - id: string, - supplier: () => T, -): T { - const key = makeKey(id); - - let value = globalObject[key]; - if (value) { - return value; - } - - value = supplier(); - globalObject[key] = value; - return value; -} diff --git a/packages/core-api/src/lib/index.ts b/packages/core-api/src/lib/index.ts deleted file mode 100644 index 1327aab4c2..0000000000 --- a/packages/core-api/src/lib/index.ts +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright 2020 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 './subjects'; -export * from './loginPopup'; -export * from './AuthConnector'; -export * from './AuthSessionManager'; diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts deleted file mode 100644 index 1eb7c3f8b9..0000000000 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ /dev/null @@ -1,218 +0,0 @@ -/* - * Copyright 2020 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 { showLoginPopup } from './loginPopup'; - -describe('showLoginPopup', () => { - afterEach(() => { - jest.resetAllMocks(); - }); - - it('should show an auth popup', async () => { - const popupMock = { closed: false }; - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue(popupMock as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - - const payloadPromise = showLoginPopup({ - url: - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', - name: 'test-popup', - origin: 'my-origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(openSpy.mock.calls[0][0]).toBe( - 'my-origin/api/backend/auth/start?scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fa%20https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fb', - ); - expect(openSpy.mock.calls[0][1]).toBe('test-popup'); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - listener({} as MessageEvent); - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - // None of these should be accepted - listener({ source: popupMock } as MessageEvent); - listener({ origin: 'my-origin' } as MessageEvent); - listener({ data: { type: 'authorization_response' } } as MessageEvent); - listener({ - source: popupMock, - origin: 'my-origin', - data: {}, - } as MessageEvent); - listener({ - source: popupMock, - origin: 'my-origin', - data: { type: 'not-auth-result', response: {} }, - } as MessageEvent); - - await expect(Promise.race([payloadPromise, 'waiting'])).resolves.toBe( - 'waiting', - ); - - const myResponse = {}; - - // This should be accepted as a valid sessions response - listener({ - source: popupMock, - origin: 'my-origin', - data: { - type: 'authorization_response', - response: myResponse, - }, - } as MessageEvent); - - await expect(payloadPromise).resolves.toBe(myResponse); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should fail if popup returns error', async () => { - const popupMock = { closed: false }; - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue(popupMock as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'my-origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - - listener({ - source: popupMock, - origin: 'my-origin', - data: { - type: 'authorization_response', - error: { - message: 'NOPE', - name: 'NopeError', - }, - }, - } as MessageEvent); - - await expect(payloadPromise).rejects.toThrow({ - name: 'NopeError', - message: 'NOPE', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should fail if popup is closed', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://localhost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, popup was closed', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should indicate if origin does not match', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://differenthost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, Incorrect app origin, expected http://differenthost', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); -}); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts deleted file mode 100644 index b6c14d60c9..0000000000 --- a/packages/core-api/src/lib/loginPopup.ts +++ /dev/null @@ -1,143 +0,0 @@ -/* - * Copyright 2020 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. - */ - -/** - * Options used to open a login popup. - */ -export type LoginPopupOptions = { - /** - * The URL that the auth popup should point to - */ - url: string; - - /** - * The name of the popup, as in second argument to window.open - */ - name: string; - - /** - * The origin of the final popup page that will post a message to this window. - */ - origin: string; - - /** - * The width of the popup in pixels, defaults to 500 - */ - width?: number; - - /** - * The height of the popup in pixels, defaults to 700 - */ - height?: number; -}; - -type AuthResult = - | { - type: 'authorization_response'; - response: unknown; - } - | { - type: 'authorization_response'; - error: { - name: string; - message: string; - }; - }; - -/** - * Show a popup pointing to a URL that starts an auth flow. Implementing the receiving - * end of the postMessage mechanism outlined in https://tools.ietf.org/html/draft-sakimura-oauth-wmrm-00 - * - * The redirect handler of the flow should use postMessage to communicate back - * to the app window. The message posted to the app must match the AuthResult type. - * - * The returned promise resolves to the response of the message that was posted from the auth popup. - */ -export function showLoginPopup(options: LoginPopupOptions): Promise { - return new Promise((resolve, reject) => { - const width = options.width || 500; - const height = options.height || 700; - const left = window.screen.width / 2 - width / 2; - const top = window.screen.height / 2 - height / 2; - - const popup = window.open( - options.url, - options.name, - `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, - ); - - let targetOrigin = ''; - - if (!popup || typeof popup.closed === 'undefined' || popup.closed) { - const error = new Error('Failed to open auth popup.'); - error.name = 'PopupRejectedError'; - reject(error); - return; - } - - const messageListener = (event: MessageEvent) => { - if (event.source !== popup) { - return; - } - if (event.origin !== options.origin) { - return; - } - const { data } = event; - - if (data.type === 'config_info') { - targetOrigin = data.targetOrigin; - return; - } - - if (data.type !== 'authorization_response') { - return; - } - const authResult = data as AuthResult; - - if ('error' in authResult) { - const error = new Error(authResult.error.message); - error.name = authResult.error.name; - // TODO: proper error type - // error.extra = authResult.error.extra; - reject(error); - } else { - resolve(authResult.response); - } - done(); - }; - - const intervalId = setInterval(() => { - if (popup.closed) { - const errMessage = `Login failed, ${ - targetOrigin && targetOrigin !== window.location.origin - ? `Incorrect app origin, expected ${targetOrigin}` - : 'popup was closed' - }`; - const error = new Error(errMessage); - error.name = 'PopupClosedError'; - reject(error); - done(); - } - }, 100); - - function done() { - window.removeEventListener('message', messageListener); - clearInterval(intervalId); - } - - window.addEventListener('message', messageListener); - }); -} diff --git a/packages/core-api/src/lib/subjects.test.ts b/packages/core-api/src/lib/subjects.test.ts deleted file mode 100644 index eab5757898..0000000000 --- a/packages/core-api/src/lib/subjects.test.ts +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright 2020 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 { PublishSubject, BehaviorSubject } from './subjects'; - -function observerSpy() { - return { - next: jest.fn(), - error: jest.fn(), - complete: jest.fn(), - }; -} - -describe('PublishSubject', () => { - it('should be completed', async () => { - const subj = new PublishSubject(); - subj.complete(); - - const spy = observerSpy(); - subj.subscribe(spy); - await 'a tick'; - - expect(spy.next).not.toHaveBeenCalled(); - expect(spy.error).not.toHaveBeenCalled(); - expect(spy.complete).toHaveBeenCalledTimes(1); - - expect(() => subj.next(1)).toThrow('PublishSubject is closed'); - expect(() => subj.error(new Error())).toThrow('PublishSubject is closed'); - expect(() => subj.complete()).toThrow('PublishSubject is closed'); - - expect(subj.closed).toBe(true); - }); - - it('should forward values to all subscribers', async () => { - const subj = new PublishSubject(); - - const spy1 = observerSpy(); - const spy2 = observerSpy(); - subj.subscribe(spy1); - const sub = subj.subscribe(spy2); - - subj.next(42); - - expect(spy1.next).toHaveBeenCalledTimes(1); - expect(spy1.next).toHaveBeenCalledWith(42); - expect(spy2.next).toHaveBeenCalledTimes(1); - expect(spy2.next).toHaveBeenCalledWith(42); - - sub.unsubscribe(); - - subj.next(1337); - - expect(spy1.next).toHaveBeenCalledTimes(2); - expect(spy1.next).toHaveBeenCalledWith(1337); - expect(spy2.next).toHaveBeenCalledTimes(1); - - expect(spy1.error).not.toHaveBeenCalled(); - expect(spy2.error).not.toHaveBeenCalled(); - expect(spy1.complete).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(false); - }); - - it('should forward errors', async () => { - const subj = new PublishSubject(); - - const spy1 = observerSpy(); - subj.subscribe(spy1); - - const error = new Error('NOPE'); - subj.error(error); - expect(spy1.error).toHaveBeenCalledWith(error); - expect(spy1.next).not.toHaveBeenCalled(); - expect(spy1.complete).not.toHaveBeenCalled(); - - const spy2 = observerSpy(); - subj.subscribe(spy2); - await 'a tick'; - expect(spy2.error).toHaveBeenCalledWith(error); - expect(spy2.next).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(true); - }); -}); - -describe('BehaviorSubject', () => { - it('should be completed', async () => { - const subj = new BehaviorSubject(0); - subj.complete(); - - const next = jest.fn(); - const complete = jest.fn(); - subj.subscribe({ next, complete }); - await 'a tick'; - - expect(complete).toHaveBeenCalledTimes(1); - - expect(() => subj.next(1)).toThrow('BehaviorSubject is closed'); - expect(() => subj.error(new Error())).toThrow('BehaviorSubject is closed'); - expect(() => subj.complete()).toThrow('BehaviorSubject is closed'); - - expect(subj.closed).toBe(true); - }); - - it('should forward values to all subscribers', async () => { - const subj = new BehaviorSubject(0); - - const obs1 = jest.fn(); - const obs2 = jest.fn(); - subj.subscribe(obs1); - const sub = subj.subscribe(obs2); - await 'a tick'; - - expect(obs1).toHaveBeenCalledTimes(1); - expect(obs1).toHaveBeenCalledWith(0); - expect(obs2).toHaveBeenCalledTimes(1); - expect(obs2).toHaveBeenCalledWith(0); - - subj.next(42); - - expect(obs1).toHaveBeenCalledTimes(2); - expect(obs1).toHaveBeenCalledWith(42); - expect(obs2).toHaveBeenCalledTimes(2); - expect(obs2).toHaveBeenCalledWith(42); - - sub.unsubscribe(); - - subj.next(1337); - - expect(obs1).toHaveBeenCalledTimes(3); - expect(obs1).toHaveBeenCalledWith(1337); - expect(obs2).toHaveBeenCalledTimes(2); - - expect(subj.closed).toBe(false); - }); - - it('should forward errors', async () => { - const subj = new BehaviorSubject(0); - - const spy1 = observerSpy(); - subj.subscribe(spy1); - await 'a tick'; - - expect(spy1.error).not.toHaveBeenCalled(); - expect(spy1.next).toHaveBeenCalledWith(0); - - const error = new Error('NOPE'); - subj.error(error); - expect(spy1.error).toHaveBeenCalledWith(error); - expect(spy1.next).toHaveBeenCalledWith(0); - expect(spy1.next).toHaveBeenCalledTimes(1); - expect(spy1.complete).not.toHaveBeenCalled(); - - const spy2 = observerSpy(); - subj.subscribe(spy2); - await 'a tick'; - expect(spy2.error).toHaveBeenCalledWith(error); - expect(spy2.next).not.toHaveBeenCalled(); - expect(spy2.complete).not.toHaveBeenCalled(); - - expect(subj.closed).toBe(true); - }); -}); diff --git a/packages/core-api/src/lib/subjects.ts b/packages/core-api/src/lib/subjects.ts deleted file mode 100644 index 4b60596bd9..0000000000 --- a/packages/core-api/src/lib/subjects.ts +++ /dev/null @@ -1,210 +0,0 @@ -/* - * Copyright 2020 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 { Observable } from '../types'; -import ObservableImpl from 'zen-observable'; - -// TODO(Rugvip): These are stopgap and probably incomplete implementations of subjects. -// If we add a more complete Observables library they should be replaced. - -/** - * A basic implementation of ReactiveX publish subjects. - * - * A subject is a convenient way to create an observable when you want - * to fan out a single value to all subscribers. - * - * See http://reactivex.io/documentation/subject.html - */ -export class PublishSubject - implements Observable, ZenObservable.SubscriptionObserver { - private isClosed = false; - private terminatingError?: Error; - - private readonly observable = new ObservableImpl(subscriber => { - if (this.isClosed) { - if (this.terminatingError) { - subscriber.error(this.terminatingError); - } else { - subscriber.complete(); - } - return () => {}; - } - - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }); - - private readonly subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - [Symbol.observable]() { - return this; - } - - get closed() { - return this.isClosed; - } - - next(value: T) { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.subscribers.forEach(subscriber => subscriber.next(value)); - } - - error(error: Error) { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.isClosed = true; - this.terminatingError = error; - this.subscribers.forEach(subscriber => subscriber.error(error)); - } - - complete() { - if (this.isClosed) { - throw new Error('PublishSubject is closed'); - } - this.isClosed = true; - this.subscribers.forEach(subscriber => subscriber.complete()); - } - - subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; - subscribe( - onNext: (value: T) => void, - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription; - subscribe( - onNext: ZenObservable.Observer | ((value: T) => void), - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription { - const observer = - typeof onNext === 'function' - ? { - next: onNext, - error: onError, - complete: onComplete, - } - : onNext; - - return this.observable.subscribe(observer); - } -} - -/** - * A basic implementation of ReactiveX behavior subjects. - * - * A subject is a convenient way to create an observable when you want - * to fan out a single value to all subscribers. - * - * The BehaviorSubject will emit the most recently emitted value or error - * whenever a new observer subscribes to the subject. - * - * See http://reactivex.io/documentation/subject.html - */ -export class BehaviorSubject - implements Observable, ZenObservable.SubscriptionObserver { - private isClosed = false; - private currentValue: T; - private terminatingError?: Error; - - constructor(value: T) { - this.currentValue = value; - } - - private readonly observable = new ObservableImpl(subscriber => { - if (this.isClosed) { - if (this.terminatingError) { - subscriber.error(this.terminatingError); - } else { - subscriber.complete(); - } - return () => {}; - } - - subscriber.next(this.currentValue); - - this.subscribers.add(subscriber); - return () => { - this.subscribers.delete(subscriber); - }; - }); - - private readonly subscribers = new Set< - ZenObservable.SubscriptionObserver - >(); - - [Symbol.observable]() { - return this; - } - - get closed() { - return this.isClosed; - } - - next(value: T) { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.currentValue = value; - this.subscribers.forEach(subscriber => subscriber.next(value)); - } - - error(error: Error) { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.isClosed = true; - this.terminatingError = error; - this.subscribers.forEach(subscriber => subscriber.error(error)); - } - - complete() { - if (this.isClosed) { - throw new Error('BehaviorSubject is closed'); - } - this.isClosed = true; - this.subscribers.forEach(subscriber => subscriber.complete()); - } - - subscribe(observer: ZenObservable.Observer): ZenObservable.Subscription; - subscribe( - onNext: (value: T) => void, - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription; - subscribe( - onNext: ZenObservable.Observer | ((value: T) => void), - onError?: (error: any) => void, - onComplete?: () => void, - ): ZenObservable.Subscription { - const observer = - typeof onNext === 'function' - ? { - next: onNext, - error: onError, - complete: onComplete, - } - : onNext; - - return this.observable.subscribe(observer); - } -} diff --git a/packages/core-api/src/lib/versionedValues.test.ts b/packages/core-api/src/lib/versionedValues.test.ts deleted file mode 100644 index 19f9c8f349..0000000000 --- a/packages/core-api/src/lib/versionedValues.test.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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 { createVersionedValueMap } from './versionedValues'; - -describe('createVersionedValueMap', () => { - it('should be empty', () => { - const map = createVersionedValueMap({}); - - // @ts-expect-error - expect(map.atVersion(1 as any)).toBe(undefined); - }); - - it('should access values by version', () => { - const map = createVersionedValueMap({ 1: 'v1', 2: 'v2' }); - - expect(map.atVersion(1)).toBe('v1'); - expect(map.atVersion(2)).toBe('v2'); - - // @ts-expect-error - expect(map.atVersion(0)).toBe(undefined); - // @ts-expect-error - expect(map.atVersion(NaN)).toBe(undefined); - // @ts-expect-error - expect(map.atVersion(Infinity)).toBe(undefined); - }); -}); diff --git a/packages/core-api/src/lib/versionedValues.ts b/packages/core-api/src/lib/versionedValues.ts deleted file mode 100644 index 3d0a4a41ae..0000000000 --- a/packages/core-api/src/lib/versionedValues.ts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * 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. - */ - -/** - * The versioned value interface is a container for a set of values that - * can be looked up by version. It is intended to be used as a container - * for values that can be versioned independently of package versions. - */ -export type VersionedValue = { - atVersion( - version: Version, - ): Versions[Version] | undefined; -}; - -/** - * Creates a container for a map of versioned values that implements VersionedValue. - */ -export function createVersionedValueMap< - Versions extends { [version: number]: any } ->(versions: Versions): VersionedValue { - Object.freeze(versions); - return { - atVersion(version) { - return versions[version]; - }, - }; -} diff --git a/packages/core-api/src/plugin/Plugin.tsx b/packages/core-api/src/plugin/Plugin.tsx deleted file mode 100644 index 8ccdb267ba..0000000000 --- a/packages/core-api/src/plugin/Plugin.tsx +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright 2020 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 { - PluginConfig, - PluginOutput, - BackstagePlugin, - Extension, - AnyRoutes, - AnyExternalRoutes, -} from './types'; -import { AnyApiFactory } from '../apis'; - -export class PluginImpl< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes -> implements BackstagePlugin { - private storedOutput?: PluginOutput[]; - - constructor(private readonly config: PluginConfig) {} - - getId(): string { - return this.config.id; - } - - getApis(): Iterable { - return this.config.apis ?? []; - } - - get routes(): Routes { - return this.config.routes ?? ({} as Routes); - } - - get externalRoutes(): ExternalRoutes { - return this.config.externalRoutes ?? ({} as ExternalRoutes); - } - - output(): PluginOutput[] { - if (this.storedOutput) { - return this.storedOutput; - } - if (!this.config.register) { - return []; - } - - const outputs = new Array(); - - this.config.register({ - router: { - addRoute(target, component, options) { - outputs.push({ - type: 'route', - target, - component, - options, - }); - }, - }, - featureFlags: { - register(name) { - outputs.push({ type: 'feature-flag', name }); - }, - }, - }); - - this.storedOutput = outputs; - return this.storedOutput; - } - - provide(extension: Extension): T { - return extension.expose(this); - } - - toString() { - return `plugin{${this.config.id}}`; - } -} - -export function createPlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} ->( - config: PluginConfig, -): BackstagePlugin { - return new PluginImpl(config); -} diff --git a/packages/core-api/src/plugin/collectors.test.tsx b/packages/core-api/src/plugin/collectors.test.tsx deleted file mode 100644 index 8c0ba2bc9e..0000000000 --- a/packages/core-api/src/plugin/collectors.test.tsx +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { PropsWithChildren } from 'react'; -import { createRouteRef } from '../routing'; -import { createPlugin } from './Plugin'; -import { - createRoutableExtension, - createComponentExtension, -} from '../extensions'; -import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { - traverseElementTree, - childDiscoverer, - routeElementDiscoverer, -} from '../extensions/traversal'; -import { pluginCollector } from './collectors'; - -const mockConfig = () => ({ path: '/foo', title: 'Foo' }); -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const pluginA = createPlugin({ id: 'my-plugin-a' }); -const pluginB = createPlugin({ id: 'my-plugin-b' }); -const pluginC = createPlugin({ id: 'my-plugin-c' }); - -const ref1 = createRouteRef(mockConfig()); -const ref2 = createRouteRef(mockConfig()); - -const Extension1 = pluginA.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = pluginB.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref2, - }), -); -const Extension3 = pluginA.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); -const Extension4 = pluginB.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); -const Extension5 = pluginC.provide( - createComponentExtension({ component: { sync: MockComponent } }), -); - -describe('collection', () => { - it('should collect the plugins', () => { - const root = ( - - - -
    - -
    -
    - {[]} - Some text here shouldn't be a problem -
    - {null} -
    - -
    - - {false} - {true} - {0} -
    - -
    - } /> -
    - - - ); - - const { plugins } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - plugins: pluginCollector, - }, - }); - - expect(plugins).toEqual(new Set([pluginA, pluginB, pluginC])); - }); -}); diff --git a/packages/core-api/src/plugin/collectors.ts b/packages/core-api/src/plugin/collectors.ts deleted file mode 100644 index 7e021beb24..0000000000 --- a/packages/core-api/src/plugin/collectors.ts +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2020 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 { BackstagePlugin } from './types'; -import { getComponentData } from '../extensions'; -import { createCollector } from '../extensions/traversal'; - -export const pluginCollector = createCollector( - () => new Set>(), - (acc, node) => { - const plugin = getComponentData>( - node, - 'core.plugin', - ); - if (plugin) { - acc.add(plugin); - } - }, -); diff --git a/packages/core-api/src/plugin/index.ts b/packages/core-api/src/plugin/index.ts deleted file mode 100644 index 54903ecf17..0000000000 --- a/packages/core-api/src/plugin/index.ts +++ /dev/null @@ -1,33 +0,0 @@ -/* - * Copyright 2020 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 { createPlugin } from './Plugin'; -export type { - BackstagePlugin, - Extension, - FeatureFlagOutput, - FeatureFlagsHooks, - LegacyRedirectRouteOutput, - LegacyRouteOutput, - PluginConfig, - PluginHooks, - PluginOutput, - RedirectRouteOutput, - RouteOptions, - RouteOutput, - RoutePath, - RouterHooks, -} from './types'; diff --git a/packages/core-api/src/plugin/types.ts b/packages/core-api/src/plugin/types.ts deleted file mode 100644 index a00897da43..0000000000 --- a/packages/core-api/src/plugin/types.ts +++ /dev/null @@ -1,121 +0,0 @@ -/* - * Copyright 2020 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 { ComponentType } from 'react'; -import { RouteRef, SubRouteRef, ExternalRouteRef } from '../routing'; -import { AnyApiFactory } from '../apis/system'; - -export type RouteOptions = { - // Whether the route path must match exactly, defaults to true. - exact?: boolean; -}; - -export type RoutePath = string; - -// Replace with using RouteRefs -export type LegacyRouteOutput = { - type: 'legacy-route'; - path: RoutePath; - component: ComponentType<{}>; - options?: RouteOptions; -}; - -export type RouteOutput = { - type: 'route'; - target: RouteRef; - component: ComponentType<{}>; - options?: RouteOptions; -}; - -export type RedirectRouteOutput = { - type: 'redirect-route'; - from: RouteRef; - to: RouteRef; - options?: RouteOptions; -}; - -export type LegacyRedirectRouteOutput = { - type: 'legacy-redirect-route'; - path: RoutePath; - target: RoutePath; - options?: RouteOptions; -}; - -export type FeatureFlagOutput = { - type: 'feature-flag'; - name: string; -}; - -export type PluginOutput = - | LegacyRouteOutput - | RouteOutput - | LegacyRedirectRouteOutput - | RedirectRouteOutput - | FeatureFlagOutput; - -export type Extension = { - expose(plugin: BackstagePlugin): T; -}; - -export type AnyRoutes = { [name: string]: RouteRef | SubRouteRef }; - -export type AnyExternalRoutes = { [name: string]: ExternalRouteRef }; - -export type BackstagePlugin< - Routes extends AnyRoutes = {}, - ExternalRoutes extends AnyExternalRoutes = {} -> = { - getId(): string; - output(): PluginOutput[]; - getApis(): Iterable; - provide(extension: Extension): T; - routes: Routes; - externalRoutes: ExternalRoutes; -}; - -export type PluginConfig< - Routes extends AnyRoutes, - ExternalRoutes extends AnyExternalRoutes -> = { - id: string; - apis?: Iterable; - register?(hooks: PluginHooks): void; - routes?: Routes; - externalRoutes?: ExternalRoutes; -}; - -export type PluginHooks = { - /** - * @deprecated All router hooks have been deprecated - */ - router: RouterHooks; - featureFlags: FeatureFlagsHooks; -}; - -export type RouterHooks = { - /** - * @deprecated Use a routable extension instead, see https://backstage.io/docs/plugins/composability#porting-existing-plugins - */ - addRoute( - target: RouteRef, - Component: ComponentType, - options?: RouteOptions, - ): void; -}; - -export type FeatureFlagsHooks = { - register(name: string): void; -}; diff --git a/packages/core-api/src/private.ts b/packages/core-api/src/private.ts deleted file mode 100644 index 56a7546925..0000000000 --- a/packages/core-api/src/private.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { PrivateAppImpl } from './app/App'; diff --git a/packages/core-api/src/public.ts b/packages/core-api/src/public.ts deleted file mode 100644 index 28b078357d..0000000000 --- a/packages/core-api/src/public.ts +++ /dev/null @@ -1,23 +0,0 @@ -/* - * Copyright 2020 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 './apis'; -export * from './app'; -export * from './extensions'; -export * from './icons'; -export * from './plugin'; -export * from './routing'; -export * from './types'; diff --git a/packages/core-api/src/routing/ExternalRouteRef.test.ts b/packages/core-api/src/routing/ExternalRouteRef.test.ts deleted file mode 100644 index 458f7d9426..0000000000 --- a/packages/core-api/src/routing/ExternalRouteRef.test.ts +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020 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 { AnyParams, ExternalRouteRef } from './types'; -import { createExternalRouteRef, isExternalRouteRef } from './ExternalRouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isRouteRef } from './RouteRef'; - -describe('ExternalRouteRef', () => { - it('should be created', () => { - const routeRef: ExternalRouteRef = createExternalRouteRef({ - id: 'my-route-ref', - }); - expect(routeRef.params).toEqual([]); - expect(routeRef.optional).toBe(false); - expect(String(routeRef)).toBe('routeRef{type=external,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(false); - expect(isSubRouteRef(routeRef)).toBe(false); - expect(isExternalRouteRef(routeRef)).toBe(true); - - expect(isRouteRef({} as ExternalRouteRef)).toBe(false); - }); - - it('should be created as optional', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: [], - optional: true, - }); - expect(routeRef.params).toEqual([]); - expect(routeRef.optional).toEqual(true); - }); - - it('should be created with params', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - }); - expect(routeRef.params).toEqual(['x', 'y']); - expect(routeRef.optional).toEqual(false); - }); - - it('should be created as optional with params', () => { - const routeRef: ExternalRouteRef<{ - x: string; - y: string; - }> = createExternalRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - optional: true, - }); - expect(routeRef.params).toEqual(['x', 'y']); - expect(routeRef.optional).toEqual(true); - }); - - it('should properly infer and validate parameter types and assignments', () => { - function validateType( - _ref: ExternalRouteRef, - ) {} - - const _1 = createExternalRouteRef({ id: '1', params: ['notX'] }); - // @ts-expect-error - validateType<{ x: string }, any>(_1); - validateType<{ notX: string }, any>(_1); - - const _2 = createExternalRouteRef({ - id: '2', - params: ['x'], - optional: true, - }); - // @ts-expect-error - validateType(_2); - validateType<{ x: string }, true>(_2); - - const _3 = createExternalRouteRef({ id: '3', params: ['x', 'y'] }); - // @ts-expect-error - validateType<{ x: string }, any>(_3); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }, any>(_3); - validateType<{ x: string; y: string }, false>(_3); - - const _4 = createExternalRouteRef({ id: '4', params: [] }); - // @ts-expect-error - validateType<{ x: string }, any>(_4); - validateType(_4); - - const _5 = createExternalRouteRef({ id: '5' }); - // @ts-expect-error - validateType<{ x: string }, any>(_5); - validateType(_5); - - const _6 = createExternalRouteRef({ id: '6', optional: true }); - // @ts-expect-error - validateType(_6); - validateType(_6); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4, _5, _6].join('')).toEqual(expect.any(String)); - }); -}); diff --git a/packages/core-api/src/routing/ExternalRouteRef.ts b/packages/core-api/src/routing/ExternalRouteRef.ts deleted file mode 100644 index 0bc5809449..0000000000 --- a/packages/core-api/src/routing/ExternalRouteRef.ts +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2020 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, - routeRefType, - AnyParams, -} from './types'; - -export { createExternalRouteRef } from '@backstage/core-plugin-api'; - -export function isExternalRouteRef< - Params extends AnyParams, - Optional extends boolean ->( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is ExternalRouteRef { - return routeRef[routeRefType] === 'external'; -} diff --git a/packages/core-api/src/routing/FlatRoutes.test.tsx b/packages/core-api/src/routing/FlatRoutes.test.tsx deleted file mode 100644 index 20ee0db235..0000000000 --- a/packages/core-api/src/routing/FlatRoutes.test.tsx +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright 2020 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 { render, RenderResult } from '@testing-library/react'; -import React, { ReactNode } from 'react'; -import { MemoryRouter, Route, Routes, useOutlet } from 'react-router-dom'; -import { AppContext } from '../app'; -import { AppContextProvider } from '../app/AppContext'; -import { FlatRoutes } from './FlatRoutes'; - -function makeRouteRenderer(node: ReactNode) { - let rendered: RenderResult | undefined = undefined; - return (path: string) => { - const content = ( - ({ - NotFoundErrorPage: () => <>Not Found, - }), - } as unknown) as AppContext - } - > - - - ); - if (rendered) { - rendered.unmount(); - rendered.rerender(content); - } else { - rendered = render(content); - } - return rendered; - }; -} - -describe('FlatRoutes', () => { - it('renders some routes', () => { - const renderRoute = makeRouteRenderer( - - a} /> - b} /> - , - ); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderRoute('/b').getByText('b')).toBeInTheDocument(); - expect(renderRoute('/c').getByText('Not Found')).toBeInTheDocument(); - expect(renderRoute('/b').queryByText('Not Found')).not.toBeInTheDocument(); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - }); - - it('is not sensitive to ordering and overlapping routes', () => { - // The '/*' suffixes here are intentional and will be ignored by FlatRoutes - const routes = ( - <> - a-1} /> - a} /> - a-2} /> - - ); - const renderRoute = makeRouteRenderer({routes}); - expect(renderRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderRoute('/a-1').getByText('a-1')).toBeInTheDocument(); - expect(renderRoute('/a-2').getByText('a-2')).toBeInTheDocument(); - renderRoute('').unmount(); - - // This uses regular Routes from react-router, not that a-2 renders a, which is the behavior we're working around - const renderBadRoute = makeRouteRenderer({routes}); - expect(renderBadRoute('/a').getByText('a')).toBeInTheDocument(); - expect(renderBadRoute('/a-1').getByText('a-1')).toBeInTheDocument(); - expect(renderBadRoute('/a-2').getByText('a')).toBeInTheDocument(); - }); - - it('renders children straight as outlets', () => { - const MyPage = () => { - return <>Outlet: {useOutlet()}; - }; - - // The '/*' suffixes here are intentional and will be ignored by FlatRoutes - const routes = ( - <> - }> - a - - }> - a-b - - }> - b - - - ); - const renderRoute = makeRouteRenderer({routes}); - expect(renderRoute('/a').getByText('Outlet: a')).toBeInTheDocument(); - expect(renderRoute('/a/b').getByText('Outlet: a-b')).toBeInTheDocument(); - expect(renderRoute('/b').getByText('Outlet: b')).toBeInTheDocument(); - }); -}); diff --git a/packages/core-api/src/routing/FlatRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx deleted file mode 100644 index 9280e183be..0000000000 --- a/packages/core-api/src/routing/FlatRoutes.tsx +++ /dev/null @@ -1,87 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { ReactNode, Children, isValidElement, Fragment } from 'react'; -import { useRoutes } from 'react-router-dom'; -import { useApp } from '../app'; - -type RouteObject = { - path: string; - element: JSX.Element; - children?: RouteObject[]; -}; - -// Similar to the same function from react-router, this collects routes from the -// children, but only the first level of routes -function createRoutesFromChildren(childrenNode: ReactNode): RouteObject[] { - return Children.toArray(childrenNode).flatMap(child => { - if (!isValidElement(child)) { - return []; - } - - const { children } = child.props; - - if (child.type === Fragment) { - return createRoutesFromChildren(children); - } - - let path = child.props.path as string | undefined; - - // TODO(Rugvip): Work around plugins registering empty paths, remove once deprecated routes are gone - if (path === '') { - return []; - } - path = path?.replace(/\/\*$/, '') ?? '/'; - - return [ - { - path, - element: child, - children: children && [ - { - path: '/*', - element: children, - }, - ], - }, - ]; - }); -} - -type FlatRoutesProps = { - children: ReactNode; -}; - -export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { - const app = useApp(); - const { NotFoundErrorPage } = app.getComponents(); - const routes = createRoutesFromChildren(props.children) - // Routes are sorted to work around a bug where prefixes are unexpectedly matched - .sort((a, b) => b.path.localeCompare(a.path)) - // We make sure all routes have '/*' appended, except '/' - .map(obj => { - obj.path = obj.path === '/' ? '/' : `${obj.path}/*`; - return obj; - }); - - // TODO(Rugvip): Possibly add a way to skip this, like a noNotFoundPage prop - routes.push({ - element: , - path: '/*', - }); - - return useRoutes(routes); -}; diff --git a/packages/core-api/src/routing/RouteRef.test.ts b/packages/core-api/src/routing/RouteRef.test.ts deleted file mode 100644 index 92d218eb5b..0000000000 --- a/packages/core-api/src/routing/RouteRef.test.ts +++ /dev/null @@ -1,94 +0,0 @@ -/* - * Copyright 2020 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 { AnyParams, RouteRef } from './types'; -import { createRouteRef, isRouteRef } from './RouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; -import MyIcon from '@material-ui/icons/AcUnit'; - -describe('RouteRef', () => { - it('should be created', () => { - const routeRef: RouteRef = createRouteRef({ - id: 'my-route-ref', - }); - expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(true); - expect(isSubRouteRef(routeRef)).toBe(false); - expect(isExternalRouteRef(routeRef)).toBe(false); - - expect(isRouteRef({} as RouteRef)).toBe(false); - }); - - it('should be created with params', () => { - const routeRef: RouteRef<{ - x: string; - y: string; - }> = createRouteRef({ - id: 'my-other-route-ref', - params: ['x', 'y'], - }); - expect(routeRef.params).toEqual(['x', 'y']); - }); - - it('should properly infer and validate parameter types and assignments', () => { - function validateType(_ref: RouteRef) {} - - const _1 = createRouteRef({ id: '1', params: ['x'] }); - // @ts-expect-error - validateType<{ y: string }>(_1); - // @ts-expect-error - validateType(_1); - validateType<{ x: string }>(_1); - - const _2 = createRouteRef({ id: '2', params: ['x', 'y'] }); - // @ts-expect-error - validateType<{ x: string }>(_2); - // @ts-expect-error - validateType(_2); - // @ts-expect-error - validateType<{ x: string; z: string }>(_2); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }>(_2); - validateType<{ x: string; y: string }>(_2); - - const _3 = createRouteRef({ id: '3', params: [] }); - // @ts-expect-error - validateType<{ x: string }>(_3); - validateType(_3); - - const _4 = createRouteRef({ id: '4' }); - // @ts-expect-error - validateType<{ x: string }>(_4); - validateType(_4); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); - }); - - it('should support deprecated access', () => { - const routeRef = createRouteRef({ - title: 'My Ref', - path: '/my-path', - icon: MyIcon, - }); - expect(routeRef.title).toBe('My Ref'); - expect(routeRef.path).toBe('/my-path'); - expect(routeRef.icon).toBe(MyIcon); - expect(String(routeRef)).toBe('routeRef{type=absolute,id=My Ref}'); - }); -}); diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts deleted file mode 100644 index 21f28eba86..0000000000 --- a/packages/core-api/src/routing/RouteRef.ts +++ /dev/null @@ -1,44 +0,0 @@ -/* - * Copyright 2020 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, - routeRefType, - AnyParams, - ParamKeys, -} from './types'; -import { IconComponent } from '../icons/types'; - -export { createRouteRef } from '@backstage/core-plugin-api'; - -// TODO(Rugvip): Remove this in the next breaking release, it's exported but unused -export type RouteRefConfig = { - params?: ParamKeys; - path?: string; - icon?: IconComponent; - title: string; -}; - -export function isRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is RouteRef { - return routeRef[routeRefType] === 'absolute'; -} diff --git a/packages/core-api/src/routing/RouteResolver.test.ts b/packages/core-api/src/routing/RouteResolver.test.ts deleted file mode 100644 index 901c30dfc1..0000000000 --- a/packages/core-api/src/routing/RouteResolver.test.ts +++ /dev/null @@ -1,293 +0,0 @@ -/* - * Copyright 2020 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 { createRouteRef } from './RouteRef'; -import { createSubRouteRef } from './SubRouteRef'; -import { createExternalRouteRef } from './ExternalRouteRef'; -import { RouteResolver } from './RouteResolver'; -import { ExternalRouteRef, RouteRef, SubRouteRef } from './types'; -import { MATCH_ALL_ROUTE } from './collectors'; - -const element = () => null; -const rest = { element, caseSensitive: false, children: [MATCH_ALL_ROUTE] }; - -const ref1 = createRouteRef({ id: 'rr1' }); -const ref2 = createRouteRef({ id: 'rr2', params: ['x'] }); -const ref3 = createRouteRef({ id: 'rr3', params: ['y'] }); -const subRef1 = createSubRouteRef({ - id: 'srr1', - parent: ref1, - path: '/foo', -}); -const subRef2 = createSubRouteRef({ - id: 'srr2', - parent: ref1, - path: '/foo/:a', -}); -const subRef3 = createSubRouteRef({ - id: 'srr3', - parent: ref2, - path: '/bar', -}); -const subRef4 = createSubRouteRef({ - id: 'srr4', - parent: ref2, - path: '/bar/:a', -}); -const externalRef1 = createExternalRouteRef({ id: 'err1' }); -const externalRef2 = createExternalRouteRef({ - id: 'err2', - optional: true, -}); -const externalRef3 = createExternalRouteRef({ id: 'err3', params: ['x'] }); -const externalRef4 = createExternalRouteRef({ - id: 'err4', - optional: true, - params: ['x'], -}); - -describe('RouteResolver', () => { - it('should not resolve anything with an empty resolver', () => { - const r = new RouteResolver(new Map(), new Map(), [], new Map()); - - expect(r.resolve(ref1, '/')?.()).toBe(undefined); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe(undefined); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe(undefined); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); - }); - - it('should resolve an absolute route', () => { - const r = new RouteResolver( - new Map([[ref1, '/my-route']]), - new Map(), - [{ routeRefs: new Set([ref1]), path: '/my-route', ...rest }], - new Map(), - ); - - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe(undefined); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe(undefined); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe(undefined); - expect(r.resolve(externalRef1, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe(undefined); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe(undefined); - }); - - it('should resolve an absolute route with a param and with a parent', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/my-route'], - [ref2, '/my-parent/:x'], - ]), - new Map([[ref2, ref1]]), - [ - { - routeRefs: new Set([ref2]), - path: '/my-parent/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { routeRefs: new Set([ref1]), path: '/my-route', ...rest }, - ], - }, - ], - new Map([ - [externalRef1, ref1], - [externalRef3, ref2], - [externalRef4, subRef3], - ]), - ); - - expect(r.resolve(ref1, '/')?.()).toBe('/my-route'); - expect(r.resolve(ref2, '/')?.({ x: '1x' })).toBe('/my-route/my-parent/1x'); - expect(r.resolve(subRef1, '/')?.()).toBe('/my-route/foo'); - expect(r.resolve(subRef2, '/')?.({ a: '2a' })).toBe('/my-route/foo/2a'); - expect(r.resolve(subRef3, '/')?.({ x: '3x' })).toBe( - '/my-route/my-parent/3x/bar', - ); - expect(r.resolve(subRef4, '/')?.({ x: '4x', a: '4a' })).toBe( - '/my-route/my-parent/4x/bar/4a', - ); - expect(r.resolve(externalRef1, '/')?.()).toBe('/my-route'); - expect(r.resolve(externalRef2, '/')?.()).toBe(undefined); - expect(r.resolve(externalRef3, '/')?.({ x: '5x' })).toBe( - '/my-route/my-parent/5x', - ); - expect(r.resolve(externalRef4, '/')?.({ x: '6x' })).toBe( - '/my-route/my-parent/6x/bar', - ); - }); - - it('should resolve the most specific match', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/deep'], - [ref2, '/root/:x'], - [ref3, '/sub/:y'], - ]), - new Map([ - [ref3, ref2], - [ref1, ref3], - ]), - [ - { - routeRefs: new Set([ref2]), - path: '/root/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref3]), - path: '/sub/:y', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref1]), - path: '/deep', - ...rest, - }, - ], - }, - ], - }, - ], - new Map(), - ); - - expect(r.resolve(ref2, '/')?.({ x: 'x' })).toBe('/root/x'); - expect(r.resolve(ref3, '/root/x')?.({ y: 'y' })).toBe('/root/x/sub/y'); - - expect(() => r.resolve(ref1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(() => r.resolve(ref1, '/root/x')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(ref1, '/root/x/sub/y')?.()).toBe('/root/x/sub/y/deep'); - // Without the MATCH_ALL_ROUTE, we wouldn't properly match the route here - expect(r.resolve(ref1, '/root/x/sub/y/any/nested/path/here')?.()).toBe( - '/root/x/sub/y/deep', - ); - }); - - it('should resolve an absolute route with multiple parents', () => { - const r = new RouteResolver( - new Map([ - [ref1, '/my-route'], - [ref2, '/my-parent/:x'], - [ref3, '/my-grandparent/:y'], - ]), - new Map([ - [ref1, ref2], - [ref2, ref3], - ]), - [ - { - routeRefs: new Set([ref3]), - path: '/my-grandparent/:y', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { - routeRefs: new Set([ref2]), - path: '/my-parent/:x', - ...rest, - children: [ - MATCH_ALL_ROUTE, - { routeRefs: new Set([ref1]), path: '/my-route', ...rest }, - ], - }, - ], - }, - ], - new Map([ - [externalRef1, ref1], - [externalRef3, ref2], - [externalRef4, subRef3], - ]), - ); - - const l = '/my-grandparent/my-y/my-parent/my-x'; - expect(r.resolve(ref1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route', - ); - expect(() => r.resolve(ref1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(ref2, l)?.({ x: '1x' })).toBe( - '/my-grandparent/my-y/my-parent/1x', - ); - expect(r.resolve(ref2, '/my-grandparent/my-y')?.({ x: '1x' })).toBe( - '/my-grandparent/my-y/my-parent/1x', - ); - expect(() => r.resolve(ref2, '/')?.({ x: '1x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route/foo', - ); - expect(() => r.resolve(subRef1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef2, l)?.({ a: '2a' })).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route/foo/2a', - ); - expect(() => r.resolve(subRef2, '/')?.({ a: '2a' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(subRef3, l)?.({ x: '3x' })).toBe( - '/my-grandparent/my-y/my-parent/3x/bar', - ); - expect(r.resolve(subRef3, '/my-grandparent/my-y')?.({ x: '3x' })).toBe( - '/my-grandparent/my-y/my-parent/3x/bar', - ); - expect(r.resolve(subRef4, l)?.({ x: '4x', a: '4a' })).toBe( - '/my-grandparent/my-y/my-parent/4x/bar/4a', - ); - expect( - r.resolve(subRef4, '/my-grandparent/my-y')?.({ x: '4x', a: '4a' }), - ).toBe('/my-grandparent/my-y/my-parent/4x/bar/4a'); - expect(r.resolve(externalRef1, l)?.()).toBe( - '/my-grandparent/my-y/my-parent/my-x/my-route', - ); - expect(() => r.resolve(externalRef1, '/')?.()).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(externalRef2, l)?.()).toBe(undefined); - expect(r.resolve(externalRef3, l)?.({ x: '5x' })).toBe( - '/my-grandparent/my-y/my-parent/5x', - ); - expect(() => r.resolve(externalRef3, '/')?.({ x: '5x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - expect(r.resolve(externalRef4, l)?.({ x: '6x' })).toBe( - '/my-grandparent/my-y/my-parent/6x/bar', - ); - expect(() => r.resolve(externalRef4, '/')?.({ x: '6x' })).toThrow( - /^Cannot route.*with parent.*as it has parameters$/, - ); - }); -}); diff --git a/packages/core-api/src/routing/RouteResolver.ts b/packages/core-api/src/routing/RouteResolver.ts deleted file mode 100644 index 4d4096a026..0000000000 --- a/packages/core-api/src/routing/RouteResolver.ts +++ /dev/null @@ -1,223 +0,0 @@ -/* - * Copyright 2020 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 { generatePath, matchRoutes } from 'react-router-dom'; -import { - AnyRouteRef, - BackstageRouteObject, - RouteRef, - ExternalRouteRef, - AnyParams, - SubRouteRef, - routeRefType, - RouteFunc, -} from './types'; -import { isRouteRef } from './RouteRef'; -import { isSubRouteRef } from './SubRouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; - -// Joins a list of paths together, avoiding trailing and duplicate slashes -function joinPaths(...paths: string[]): string { - const normalized = paths.join('/').replace(/\/\/+/g, '/'); - if (normalized !== '/' && normalized.endsWith('/')) { - return normalized.slice(0, -1); - } - return normalized; -} - -/** - * Resolves the absolute route ref that our target route ref is pointing pointing to, as well - * as the relative target path. - * - * Returns an undefined target ref if one could not be fully resolved. - */ -function resolveTargetRef( - anyRouteRef: AnyRouteRef, - routePaths: Map, - routeBindings: Map, -): readonly [RouteRef | undefined, string] { - // First we figure out which absolute route ref we're dealing with, an if there was an sub route path to append. - // For sub routes it will be the parent path, while for external routes it will be the bound route. - let targetRef: RouteRef; - let subRoutePath = ''; - if (isRouteRef(anyRouteRef)) { - targetRef = anyRouteRef; - } else if (isSubRouteRef(anyRouteRef)) { - targetRef = anyRouteRef.parent; - subRoutePath = anyRouteRef.path; - } else if (isExternalRouteRef(anyRouteRef)) { - const resolvedRoute = routeBindings.get(anyRouteRef); - if (!resolvedRoute) { - return [undefined, '']; - } - if (isRouteRef(resolvedRoute)) { - targetRef = resolvedRoute; - } else if (isSubRouteRef(resolvedRoute)) { - targetRef = resolvedRoute.parent; - subRoutePath = resolvedRoute.path; - } else { - throw new Error( - `ExternalRouteRef was bound to invalid target, ${resolvedRoute}`, - ); - } - } else if (anyRouteRef[routeRefType]) { - throw new Error( - `Unknown or invalid route ref type, ${anyRouteRef[routeRefType]}`, - ); - } else { - throw new Error(`Unknown object passed to useRouteRef, got ${anyRouteRef}`); - } - - // Bail if no absolute path could be resolved - if (!targetRef) { - return [undefined, '']; - } - - // Find the path that our target route is bound to - const resolvedPath = routePaths.get(targetRef); - if (!resolvedPath) { - return [undefined, '']; - } - - // SubRouteRefs join the path from the parent route with its own path - const targetPath = joinPaths(resolvedPath, subRoutePath); - return [targetRef, targetPath]; -} - -/** - * Resolves the complete base path for navigating to the target RouteRef. - */ -function resolveBasePath( - targetRef: RouteRef, - sourceLocation: Parameters[1], - routePaths: Map, - routeParents: Map, - routeObjects: BackstageRouteObject[], -) { - // While traversing the app element tree we build up the routeObjects structure - // used here. It is the same kind of structure that react-router creates, with the - // addition that associated route refs are stored throughout the tree. This lets - // us look up all route refs that can be reached from our source location. - // Because of the similar route object structure, we can use `matchRoutes` from - // react-router to do the lookup of our current location. - const match = matchRoutes(routeObjects, sourceLocation) ?? []; - - // While we search for a common routing root between our current location and - // the target route, we build a list of all route refs we find that we need - // to traverse to reach the target. - const refDiffList = Array(); - - let matchIndex = -1; - for ( - let targetSearchRef: RouteRef | undefined = targetRef; - targetSearchRef; - targetSearchRef = routeParents.get(targetSearchRef) - ) { - // The match contains a list of all ancestral route refs present at our current location - // Starting at the desired target ref and traversing back through its parents, we search - // for a target ref that is present in the match for our current location. When a match - // is found it means we have found a common base to resolve the route from. - matchIndex = match.findIndex(m => - (m.route as BackstageRouteObject).routeRefs.has(targetSearchRef!), - ); - if (matchIndex !== -1) { - break; - } - - // Every time we move a step up in the ancestry of the target ref, we add the current ref - // to the diff list, which ends up being the list of route refs to traverse form the common base - // in order to reach our target. - refDiffList.unshift(targetSearchRef); - } - - // If our target route is present in the initial match we need to construct the final path - // from the parent of the matched route segment. That's to allow the caller of the route - // function to supply their own params. - if (refDiffList.length === 0) { - matchIndex -= 1; - } - - // This is the part of the route tree that the target and source locations have in common. - // We re-use the existing pathname directly along with all params. - const parentPath = matchIndex === -1 ? '' : match[matchIndex].pathname; - - // This constructs the mid section of the path using paths resolved from all route refs - // we need to traverse to reach our target except for the very last one. None of these - // paths are allowed to require any parameters, as the caller would have no way of knowing - // what parameters those are. - const diffPath = joinPaths( - ...refDiffList.slice(0, -1).map(ref => { - const path = routePaths.get(ref); - if (!path) { - throw new Error(`No path for ${ref}`); - } - if (path.includes(':')) { - throw new Error( - `Cannot route to ${targetRef} with parent ${ref} as it has parameters`, - ); - } - return path; - }), - ); - - return parentPath + diffPath; -} - -export class RouteResolver { - constructor( - private readonly routePaths: Map, - private readonly routeParents: Map, - private readonly routeObjects: BackstageRouteObject[], - private readonly routeBindings: Map< - ExternalRouteRef, - RouteRef | SubRouteRef - >, - ) {} - - resolve( - anyRouteRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, - sourceLocation: Parameters[1], - ): RouteFunc | undefined { - // First figure out what our target absolute ref is, as well as our target path. - const [targetRef, targetPath] = resolveTargetRef( - anyRouteRef, - this.routePaths, - this.routeBindings, - ); - if (!targetRef) { - return undefined; - } - - // Next we figure out the base path, which is the combination of the common parent path - // between our current location and our target location, as well as the additional path - // that is the difference between the parent path and the base of our target location. - const basePath = resolveBasePath( - targetRef, - sourceLocation, - this.routePaths, - this.routeParents, - this.routeObjects, - ); - - const routeFunc: RouteFunc = (...[params]) => { - return basePath + generatePath(targetPath, params); - }; - return routeFunc; - } -} diff --git a/packages/core-api/src/routing/SubRouteRef.test.ts b/packages/core-api/src/routing/SubRouteRef.test.ts deleted file mode 100644 index 0a12b2a153..0000000000 --- a/packages/core-api/src/routing/SubRouteRef.test.ts +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright 2020 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 { AnyParams, SubRouteRef } from './types'; -import { createSubRouteRef, isSubRouteRef } from './SubRouteRef'; -import { createRouteRef, isRouteRef } from './RouteRef'; -import { isExternalRouteRef } from './ExternalRouteRef'; - -const parent = createRouteRef({ id: 'parent' }); -const parentX = createRouteRef({ id: 'parent-x', params: ['x'] }); - -describe('SubRouteRef', () => { - it('should be created', () => { - const routeRef: SubRouteRef = createSubRouteRef({ - parent, - id: 'my-route-ref', - path: '/foo', - }); - expect(routeRef.path).toBe('/foo'); - expect(routeRef.parent).toBe(parent); - expect(routeRef.params).toEqual([]); - expect(String(routeRef)).toBe('routeRef{type=sub,id=my-route-ref}'); - expect(isRouteRef(routeRef)).toBe(false); - expect(isSubRouteRef(routeRef)).toBe(true); - expect(isExternalRouteRef(routeRef)).toBe(false); - - expect(isRouteRef({} as SubRouteRef)).toBe(false); - }); - - it('should be created with params', () => { - const routeRef: SubRouteRef<{ bar: string }> = createSubRouteRef({ - parent, - id: 'my-other-route-ref', - path: '/foo/:bar', - }); - expect(routeRef.path).toBe('/foo/:bar'); - expect(routeRef.parent).toBe(parent); - expect(routeRef.params).toEqual(['bar']); - }); - - it('should be created with merged params', () => { - const routeRef: SubRouteRef<{ - x: string; - y: string; - z: string; - }> = createSubRouteRef({ - parent: parentX, - id: 'my-other-route-ref', - path: '/foo/:y/:z', - }); - expect(routeRef.path).toBe('/foo/:y/:z'); - expect(routeRef.parent).toBe(parentX); - expect(routeRef.params).toEqual(['x', 'y', 'z']); - }); - - it('should be created with params from parent', () => { - const routeRef: SubRouteRef<{ x: string }> = createSubRouteRef({ - parent: parentX, - id: 'my-other-route-ref', - path: '/foo/bar', - }); - expect(routeRef.path).toBe('/foo/bar'); - expect(routeRef.parent).toBe(parentX); - expect(routeRef.params).toEqual(['x']); - }); - - it.each([ - ['foo', "SubRouteRef path must start with '/', got 'foo'"], - [':foo', "SubRouteRef path must start with '/', got ':foo'"], - ['', "SubRouteRef path must start with '/', got ''"], - ['/', "SubRouteRef path must not end with '/', got '/'"], - ['/foo/', "SubRouteRef path must not end with '/', got '/foo/'"], - ['/foo/:x', 'SubRouteRef may not have params that overlap with its parent'], - ['/:/foo', "SubRouteRef path has invalid param, got ''"], - ['/:inva:lid/foo', "SubRouteRef path has invalid param, got 'inva:lid'"], - ['/:inva=lid/foo', "SubRouteRef path has invalid param, got 'inva=lid'"], - ])('should throw if path is invalid, %s', (path, message) => { - expect(() => - createSubRouteRef({ path, parent: parentX, id: path }), - ).toThrow(message); - }); - - it('should properly infer and parse path parameters', () => { - function validateType(_ref: SubRouteRef) {} - - const _1 = createSubRouteRef({ id: '1', parent, path: '/foo/bar' }); - // @ts-expect-error - validateType<{ x: string }>(_1); - validateType(_1); - - const _2 = createSubRouteRef({ id: '2', parent, path: '/foo/:x/:y' }); - // @ts-expect-error - validateType(_2); - // @ts-expect-error - validateType<{ x: string; z: string }>(_2); - // @ts-expect-error - validateType<{ y: string }>(_2); - // TODO(Rugvip): Ideally this would fail as well, but settle for validating it at runtime instead - validateType<{ x: string; y: string; z: string }>(_2); - validateType<{ x: string; y: string }>(_2); - - const _3 = createSubRouteRef({ id: '3', parent: parentX, path: '/foo' }); - // @ts-expect-error - validateType(_3); - // @ts-expect-error - validateType<{ y: string }>(_3); - validateType<{ x: string }>(_3); - - const _4 = createSubRouteRef({ id: '4', parent: parentX, path: '/foo/:y' }); - // @ts-expect-error - validateType(_4); - // @ts-expect-error - validateType<{ x: string; z: string }>(_4); - // @ts-expect-error - validateType<{ y: string }>(_4); - validateType<{ x: string; y: string }>(_4); - - // To avoid complains about missing expectations and unused vars - expect([_1, _2, _3, _4].join('')).toEqual(expect.any(String)); - }); -}); diff --git a/packages/core-api/src/routing/SubRouteRef.ts b/packages/core-api/src/routing/SubRouteRef.ts deleted file mode 100644 index b64bce9d8a..0000000000 --- a/packages/core-api/src/routing/SubRouteRef.ts +++ /dev/null @@ -1,34 +0,0 @@ -/* - * Copyright 2020 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 { - AnyParams, - ExternalRouteRef, - RouteRef, - routeRefType, - SubRouteRef, -} from './types'; - -export { createSubRouteRef } from '@backstage/core-plugin-api'; - -export function isSubRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): routeRef is SubRouteRef { - return routeRef[routeRefType] === 'sub'; -} diff --git a/packages/core-api/src/routing/collectors.test.tsx b/packages/core-api/src/routing/collectors.test.tsx deleted file mode 100644 index 21ef219555..0000000000 --- a/packages/core-api/src/routing/collectors.test.tsx +++ /dev/null @@ -1,374 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { PropsWithChildren } from 'react'; -import { - routePathCollector, - routeParentCollector, - routeObjectCollector, -} from './collectors'; - -import { - traverseElementTree, - childDiscoverer, - routeElementDiscoverer, -} from '../extensions/traversal'; -import { createRouteRef } from './RouteRef'; -import { createPlugin } from '../plugin'; -import { attachComponentData, createRoutableExtension } from '../extensions'; -import { MemoryRouter, Routes, Route } from 'react-router-dom'; -import { RouteRef } from './types'; - -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const plugin = createPlugin({ id: 'my-plugin' }); - -const ref1 = createRouteRef({ path: '/foo1', title: 'Foo' }); -const ref2 = createRouteRef({ path: '/foo2', title: 'Foo' }); -const ref3 = createRouteRef({ path: '/foo3', title: 'Foo' }); -const ref4 = createRouteRef({ path: '/foo4', title: 'Foo' }); -const ref5 = createRouteRef({ path: '/foo5', title: 'Foo' }); -const refOrder = [ref1, ref2, ref3, ref4, ref5]; - -const Extension1 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref2, - }), -); -const Extension3 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref3, - }), -); -const Extension4 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref4, - }), -); -const Extension5 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref5, - }), -); - -const AggregationComponent = ({ - children, -}: PropsWithChildren<{ - path: string; -}>) => <>{children}; - -attachComponentData(AggregationComponent, 'core.gatherMountPoints', true); - -function sortedEntries(map: Map): [RouteRef, T][] { - return Array.from(map).sort( - ([a], [b]) => refOrder.indexOf(a) - refOrder.indexOf(b), - ); -} - -function routeObj( - path: string, - refs: RouteRef[], - children: any[] = [], - type: 'mounted' | 'gathered' = 'mounted', -) { - return { - path: path, - caseSensitive: false, - element: type, - routeRefs: new Set(refs), - children: [ - { - path: '/*', - caseSensitive: false, - element: 'match-all', - routeRefs: new Set(), - }, - ...children, - ], - }; -} - -describe('discovery', () => { - it('should collect routes', () => { - const list = [ -
    , -
    , -
    - -
    , - ]; - - const root = ( - - - -
    - -
    -
    - Some text here shouldn't be a problem -
    - {null} -
    - -
    - - {false} - {list} - {true} - {0} -
    - -
    - } /> -
    - - - ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar/:id'], - [ref3, '/baz'], - [ref4, '/divsoup'], - [ref5, '/blop'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, ref2], - [ref4, undefined], - [ref5, ref1], - ]); - expect(routeObjects).toEqual([ - routeObj( - '/foo', - [ref1], - [ - routeObj('/bar/:id', [ref2], [routeObj('/baz', [ref3])]), - routeObj('/blop', [ref5]), - ], - ), - routeObj('/divsoup', [ref4]), - ]); - }); - - it('should handle all react router Route patterns', () => { - const root = ( - - - - - - - - } - /> - }> - } /> - - - - - ); - - const { routes, routeParents } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar/:id'], - [ref3, '/baz'], - [ref4, '/divsoup'], - [ref5, '/blop'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, undefined], - [ref4, ref3], - [ref5, ref3], - ]); - }); - - it('should use the route aggregator key to bind child routes to the same path', () => { - const root = ( - - - - -
    - -
    - HELLO -
    - - - - - - - -
    -
    - ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/foo'], - [ref3, '/bar'], - [ref4, '/baz'], - [ref5, '/baz'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, undefined], - [ref3, undefined], - [ref4, ref3], - [ref5, ref3], - ]); - expect(routeObjects).toEqual([ - routeObj('/foo', [ref1, ref2], [], 'gathered'), - routeObj( - '/bar', - [ref3], - [routeObj('/baz', [ref4, ref5], [], 'gathered')], - ), - ]); - }); - - it('should use the route aggregator but stop when encountering explicit path', () => { - const root = ( - - - - - - - - - - - - - - - ); - - const { routes, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - expect(sortedEntries(routes)).toEqual([ - [ref1, '/foo'], - [ref2, '/bar'], - [ref3, '/baz'], - [ref4, '/blop'], - [ref5, '/bar'], - ]); - expect(sortedEntries(routeParents)).toEqual([ - [ref1, undefined], - [ref2, ref1], - [ref3, ref1], - [ref4, ref3], - [ref5, ref1], - ]); - expect(routeObjects).toEqual([ - routeObj( - '/foo', - [ref1], - [ - routeObj( - '/bar', - [ref2, ref5], - [routeObj('/baz', [ref3], [routeObj('/blop', [ref4])])], - 'gathered', - ), - ], - ), - ]); - }); - - it('should stop gathering mount points after encountering explicit path', () => { - const root = ( - - - - - - - - - - - - ); - - expect(() => { - traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routes: routePathCollector, - routeParents: routeParentCollector, - }, - }); - }).toThrow('Mounted routable extension must have a path'); - }); -}); diff --git a/packages/core-api/src/routing/collectors.tsx b/packages/core-api/src/routing/collectors.tsx deleted file mode 100644 index 7db51e5014..0000000000 --- a/packages/core-api/src/routing/collectors.tsx +++ /dev/null @@ -1,172 +0,0 @@ -/* - * Copyright 2020 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 { isValidElement, ReactElement, ReactNode } from 'react'; -import { BackstageRouteObject, RouteRef } from '../routing/types'; -import { getComponentData } from '../extensions'; -import { createCollector } from '../extensions/traversal'; - -function getMountPoint(node: ReactElement): RouteRef | undefined { - const element: ReactNode = node.props?.element; - - let routeRef = getComponentData(node, 'core.mountPoint'); - if (!routeRef && isValidElement(element)) { - routeRef = getComponentData(element, 'core.mountPoint'); - } - - return routeRef; -} - -export const routePathCollector = createCollector( - () => new Map(), - (acc, node, parent, ctxPath: string | undefined) => { - // The context path is used during mount point gathering to assign the same path - // to all discovered mount points - let currentCtxPath = ctxPath; - - if (parent?.props.element === node) { - return currentCtxPath; - } - - // Start gathering mount points when we encounter a mount point gathering flag - if (getComponentData(node, 'core.gatherMountPoints')) { - const path: string | undefined = node.props?.path; - if (!path) { - throw new Error('Mount point gatherer must have a path'); - } - currentCtxPath = path; - } - - const routeRef = getMountPoint(node); - if (routeRef) { - let path: string | undefined = node.props?.path; - // If we're gathering mount points we use the context path as out path, unless - // the element has its own path, in which case we use that instead and stop gathering - if (currentCtxPath) { - if (path) { - currentCtxPath = undefined; - } else { - path = currentCtxPath; - } - } - if (!path) { - throw new Error('Mounted routable extension must have a path'); - } - acc.set(routeRef, path); - } - return currentCtxPath; - }, -); - -export const routeParentCollector = createCollector( - () => new Map(), - (acc, node, parent, parentRouteRef?: RouteRef | { sticky: RouteRef }) => { - if (parent?.props.element === node) { - return parentRouteRef; - } - - let nextParent = parentRouteRef; - - const routeRef = getMountPoint(node); - if (routeRef) { - // "sticky" route ref is when we've encountered a mount point gatherer, and we want a - // mount points beneath it to have the same parent, regardless of internal structure - if (parentRouteRef && 'sticky' in parentRouteRef) { - acc.set(routeRef, parentRouteRef.sticky); - - // When we encounter a mount point with an explicit path, we stop gathering - // mount points withing the children and remove the sticky state - if (node.props?.path) { - nextParent = routeRef; - } else { - nextParent = parentRouteRef; - } - } else { - acc.set(routeRef, parentRouteRef); - nextParent = routeRef; - } - } - - // Mount point gatherers are marked as "sticky" - if (getComponentData(node, 'core.gatherMountPoints')) { - return { sticky: nextParent }; - } - - return nextParent; - }, -); - -// We always add a child that matches all subroutes but without any route refs. This makes -// sure that we're always able to match each route no matter how deep the navigation goes. -// The route resolver then takes care of selecting the most specific match in order to find -// mount points that are as deep in the routing tree as possible. -export const MATCH_ALL_ROUTE: BackstageRouteObject = { - caseSensitive: false, - path: '/*', - element: 'match-all', // These elements aren't used, so we add in a bit of debug information - routeRefs: new Set(), -}; - -export const routeObjectCollector = createCollector( - () => Array(), - (acc, node, parent, parentObj: BackstageRouteObject | undefined) => { - const parentChildren = parentObj?.children ?? acc; - if (parent?.props.element === node) { - return parentObj; - } - - const path: string | undefined = node.props?.path; - const caseSensitive: boolean = Boolean(node.props?.caseSensitive); - - const routeRef = getMountPoint(node); - if (routeRef) { - if (path) { - const newObject: BackstageRouteObject = { - caseSensitive, - path, - element: 'mounted', - routeRefs: new Set([routeRef]), - children: [MATCH_ALL_ROUTE], - }; - parentChildren.push(newObject); - return newObject; - } - - parentObj?.routeRefs.add(routeRef); - } - - const isGatherer = getComponentData( - node, - 'core.gatherMountPoints', - ); - if (isGatherer) { - if (!path) { - throw new Error('Mount point gatherer must have a path'); - } - const newObject: BackstageRouteObject = { - caseSensitive, - path, - element: 'gathered', - routeRefs: new Set(), - children: [MATCH_ALL_ROUTE], - }; - parentChildren.push(newObject); - return newObject; - } - - return parentObj; - }, -); diff --git a/packages/core-api/src/routing/hooks.test.tsx b/packages/core-api/src/routing/hooks.test.tsx deleted file mode 100644 index 3d723053b0..0000000000 --- a/packages/core-api/src/routing/hooks.test.tsx +++ /dev/null @@ -1,405 +0,0 @@ -/* - * Copyright 2020 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 { render } from '@testing-library/react'; -import { renderHook } from '@testing-library/react-hooks'; -import React, { - Context, - PropsWithChildren, - ReactElement, - useContext, -} from 'react'; -import { MemoryRouter, Route, Routes } from 'react-router-dom'; -import { createRoutableExtension } from '../extensions'; -import { - childDiscoverer, - routeElementDiscoverer, - traverseElementTree, -} from '../extensions/traversal'; -import { getGlobalSingleton } from '../lib/globalObject'; -import { VersionedValue } from '../lib/versionedValues'; -import { createPlugin } from '../plugin'; -import { - routeObjectCollector, - routeParentCollector, - routePathCollector, -} from './collectors'; -import { createExternalRouteRef } from './ExternalRouteRef'; -import { RoutingProvider, useRouteRef, useRouteRefParams } from './hooks'; -import { createRouteRef, RouteRefConfig } from './RouteRef'; -import { RouteResolver } from './RouteResolver'; -import { AnyRouteRef, ExternalRouteRef, RouteFunc, RouteRef } from './types'; -import { validateRoutes } from './validation'; - -const mockConfig = (extra?: Partial>) => ({ - path: '/unused', - title: 'Unused', - ...extra, -}); -const MockComponent = ({ children }: PropsWithChildren<{ path?: string }>) => ( - <>{children} -); - -const plugin = createPlugin({ id: 'my-plugin' }); - -const ref1 = createRouteRef(mockConfig({ path: '/wat1' })); -const ref2 = createRouteRef(mockConfig({ path: '/wat2' })); -const ref3 = createRouteRef(mockConfig({ path: '/wat3' })); -const ref4 = createRouteRef(mockConfig({ path: '/wat4' })); -const ref5 = createRouteRef({ - ...mockConfig({ path: '/wat5' }), - params: ['x'], -}); -const eRefA = createExternalRouteRef({ id: '1' }); -const eRefB = createExternalRouteRef({ id: '2' }); -const eRefC = createExternalRouteRef({ id: '3', params: ['y'] }); -const eRefD = createExternalRouteRef({ id: '4', optional: true }); -const eRefE = createExternalRouteRef({ - id: '5', - optional: true, - params: ['z'], -}); - -const MockRouteSource = (props: { - path?: string; - name: string; - routeRef: AnyRouteRef; - params?: T; -}) => { - try { - const routeFunc = useRouteRef(props.routeRef as any) as - | RouteFunc - | undefined; - return ( -
    - Path at {props.name}: {routeFunc?.(props.params) ?? ''} -
    - ); - } catch (ex) { - return ( -
    - Error at {props.name}: {ex.message} -
    - ); - } -}; - -const Extension1 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref1, - }), -); -const Extension2 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockRouteSource), - mountPoint: ref2, - }), -); -const Extension3 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref3, - }), -); -const Extension4 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockRouteSource), - mountPoint: ref4, - }), -); -const Extension5 = plugin.provide( - createRoutableExtension({ - component: () => Promise.resolve(MockComponent), - mountPoint: ref5, - }), -); - -function withRoutingProvider( - root: ReactElement, - routeBindings: [ExternalRouteRef, RouteRef][] = [], -) { - const { routePaths, routeParents, routeObjects } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - routeObjects: routeObjectCollector, - }, - }); - - return ( - - {root} - - ); -} - -describe('discovery', () => { - it('should handle simple routeRef path creation for routeRefs used in other parts of the app', async () => { - const root = ( - - - - - - - - - - - - - - - ); - - const rendered = render( - withRoutingProvider(root, [ - [eRefA, ref3], - [eRefB, ref1], - [eRefC, ref2], - [eRefD, ref1], - ]), - ); - - await expect( - rendered.findByText('Path at inside: /foo/bar'), - ).resolves.toBeInTheDocument(); - expect( - rendered.getByText('Path at insideExternal: /baz'), - ).toBeInTheDocument(); - expect(rendered.getByText('Path at outside: /foo/bar')).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal1: /foo'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal2: /foo/bar'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal3: /foo'), - ).toBeInTheDocument(); - expect( - rendered.getByText('Path at outsideExternal4: '), - ).toBeInTheDocument(); - }); - - it('should handle routeRefs with parameters', async () => { - const root = ( - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - await expect( - rendered.findByText('Path at inside: /foo/bar/bleb'), - ).resolves.toBeInTheDocument(); - expect( - rendered.getByText('Path at outside: /foo/bar/blob'), - ).toBeInTheDocument(); - }); - - it('should handle relative routing within parameterized routePaths', async () => { - const root = ( - - - - - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - await expect( - rendered.findByText('Path at inside: /foo/blob/baz'), - ).resolves.toBeInTheDocument(); - }); - - it('should throw errors for routing to other routeRefs with unsupported parameters', () => { - const root = ( - - - - - - - - - - - ); - - const rendered = render(withRoutingProvider(root)); - - expect( - rendered.getByText( - `Error at outsideWithParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`, - ), - ).toBeInTheDocument(); - expect( - rendered.getByText( - `Error at outsideNoParams: Cannot route to ${ref3} with parent ${ref5} as it has parameters`, - ), - ).toBeInTheDocument(); - }); - - it('should handle relative routing of parameterized routePaths with duplicate param names', () => { - const root = ( - - - - - - - - ); - - const { routePaths, routeParents } = traverseElementTree({ - root, - discoverers: [childDiscoverer, routeElementDiscoverer], - collectors: { - routePaths: routePathCollector, - routeParents: routeParentCollector, - }, - }); - - expect(() => validateRoutes(routePaths, routeParents)).toThrow( - 'Parameter :id is duplicated in path /foo/:id/bar/:id', - ); - }); -}); - -describe('v1 consumer', () => { - const RoutingContext = getGlobalSingleton< - Context> - >('routing-context'); - - function useMockRouteRefV1( - routeRef: AnyRouteRef, - location: string, - ): RouteFunc | undefined { - const resolver = useContext(RoutingContext)?.atVersion(1); - if (!resolver) { - throw new Error('no impl'); - } - return resolver.resolve(routeRef, location); - } - - it('should resolve routes', () => { - const routeRef1 = createRouteRef({ id: 'ref1' }); - const routeRef2 = createRouteRef({ id: 'ref2' }); - const routeRef3 = createRouteRef({ id: 'ref3', params: ['x'] }); - - const renderedHook = renderHook( - ({ routeRef }) => useMockRouteRefV1(routeRef, '/'), - { - initialProps: { - routeRef: routeRef1 as AnyRouteRef, - }, - wrapper: ({ children }) => ( - , string>([ - [routeRef2, '/foo'], - [routeRef3, '/bar/:x'], - ]) - } - routeParents={new Map()} - routeObjects={[]} - routeBindings={new Map()} - children={children} - /> - ), - }, - ); - - expect(renderedHook.result.current).toBe(undefined); - renderedHook.rerender({ routeRef: routeRef2 }); - expect(renderedHook.result.current?.()).toBe('/foo'); - renderedHook.rerender({ routeRef: routeRef3 }); - expect(renderedHook.result.current?.({ x: 'my-x' })).toBe('/bar/my-x'); - }); -}); - -describe('useRouteRefParams', () => { - it('should provide types params', () => { - const routeRef = createRouteRef({ - id: 'ref1', - params: ['a', 'b'], - }); - - const Page = () => { - const params: { a: string; b: string } = useRouteRefParams(routeRef); - - return ( -
    - {params.a} - {params.b} -
    - ); - }; - - const { getByText } = render( - - - - - - - , - ); - - expect(getByText('foo')).toBeInTheDocument(); - expect(getByText('bar')).toBeInTheDocument(); - }); -}); diff --git a/packages/core-api/src/routing/hooks.tsx b/packages/core-api/src/routing/hooks.tsx deleted file mode 100644 index d498027efb..0000000000 --- a/packages/core-api/src/routing/hooks.tsx +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { - createContext, - ReactNode, - useContext, - useMemo, - Context, -} from 'react'; -import { useLocation, useParams } from 'react-router-dom'; -import { - BackstageRouteObject, - RouteRef, - ExternalRouteRef, - AnyParams, - SubRouteRef, - RouteFunc, -} from './types'; -import { RouteResolver } from './RouteResolver'; -import { - VersionedValue, - createVersionedValueMap, -} from '../lib/versionedValues'; -import { - getGlobalSingleton, - getOrCreateGlobalSingleton, -} from '../lib/globalObject'; - -type RoutingContextType = VersionedValue<{ 1: RouteResolver }> | undefined; -const RoutingContext = getOrCreateGlobalSingleton('routing-context', () => - createContext(undefined), -); - -export function useRouteRef( - routeRef: ExternalRouteRef, -): Optional extends true ? RouteFunc | undefined : RouteFunc; -export function useRouteRef( - routeRef: RouteRef | SubRouteRef, -): RouteFunc; -export function useRouteRef( - routeRef: - | RouteRef - | SubRouteRef - | ExternalRouteRef, -): RouteFunc | undefined { - const sourceLocation = useLocation(); - const versionedContext = useContext( - getGlobalSingleton>('routing-context'), - ); - const resolver = versionedContext?.atVersion(1); - const routeFunc = useMemo( - () => resolver && resolver.resolve(routeRef, sourceLocation), - [resolver, routeRef, sourceLocation], - ); - - if (!versionedContext) { - throw new Error('useRouteRef used outside of routing context'); - } - if (!resolver) { - throw new Error('RoutingContext v1 not available'); - } - - const isOptional = 'optional' in routeRef && routeRef.optional; - if (!routeFunc && !isOptional) { - throw new Error(`No path for ${routeRef}`); - } - - return routeFunc; -} - -type ProviderProps = { - routePaths: Map; - routeParents: Map; - routeObjects: BackstageRouteObject[]; - routeBindings: Map; - children: ReactNode; -}; - -export const RoutingProvider = ({ - routePaths, - routeParents, - routeObjects, - routeBindings, - children, -}: ProviderProps) => { - const resolver = new RouteResolver( - routePaths, - routeParents, - routeObjects, - routeBindings, - ); - - const versionedValue = createVersionedValueMap({ 1: resolver }); - return ( - - {children} - - ); -}; - -export function useRouteRefParams( - _routeRef: RouteRef | SubRouteRef, -): Params { - return useParams() as Params; -} diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts deleted file mode 100644 index 835568c313..0000000000 --- a/packages/core-api/src/routing/index.ts +++ /dev/null @@ -1,30 +0,0 @@ -/* - * Copyright 2020 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 { createExternalRouteRef } from './ExternalRouteRef'; -export { FlatRoutes } from './FlatRoutes'; -export { useRouteRef, useRouteRefParams } from './hooks'; -export { createRouteRef } from './RouteRef'; -export type { RouteRefConfig } from './RouteRef'; -export { createSubRouteRef } from './SubRouteRef'; -export type { - AbsoluteRouteRef, - ConcreteRoute, - ExternalRouteRef, - MutableRouteRef, - RouteRef, - SubRouteRef, -} from './types'; diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts deleted file mode 100644 index 5ecb64c7fe..0000000000 --- a/packages/core-api/src/routing/types.ts +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright 2020 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 { - RouteRef, - SubRouteRef, - ExternalRouteRef, -} from '@backstage/core-plugin-api'; -import { getOrCreateGlobalSingleton } from '../lib/globalObject'; - -export type { RouteRef, SubRouteRef, ExternalRouteRef }; - -export type AnyParams = { [param in string]: string } | undefined; -export type ParamKeys = keyof Params extends never - ? [] - : (keyof Params)[]; -export type OptionalParams< - Params extends { [param in string]: string } -> = Params[keyof Params] extends never ? undefined : Params; - -// The extra TS magic here is to require a single params argument if the RouteRef -// had at least one param defined, but require 0 arguments if there are no params defined. -// Without this we'd have to pass in empty object to all parameter-less RouteRefs -// just to make TypeScript happy, or we would have to make the argument optional in -// which case you might forget to pass it in when it is actually required. -export type RouteFunc = ( - ...[params]: Params extends undefined ? readonly [] : readonly [Params] -) => string; - -type RouteRefType = Exclude< - keyof RouteRef, - 'params' | 'path' | 'title' | 'icon' ->; -export const routeRefType: RouteRefType = getOrCreateGlobalSingleton( - 'route-ref-type', - () => Symbol('route-ref-type'), -); - -export type AnyRouteRef = - | RouteRef - | SubRouteRef - | ExternalRouteRef; - -// TODO(Rugvip): None of these should be found in the wild anymore, remove in next minor release -/** @deprecated */ -export type ConcreteRoute = {}; -/** @deprecated */ -export type AbsoluteRouteRef = RouteRef<{}>; -/** @deprecated */ -export type MutableRouteRef = RouteRef<{}>; - -// A duplicate of the react-router RouteObject, but with routeRef added -export interface BackstageRouteObject { - caseSensitive: boolean; - children?: BackstageRouteObject[]; - element: React.ReactNode; - path: string; - routeRefs: Set; -} diff --git a/packages/core-api/src/routing/validation.ts b/packages/core-api/src/routing/validation.ts deleted file mode 100644 index 51078a0130..0000000000 --- a/packages/core-api/src/routing/validation.ts +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 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 { AnyRouteRef } from './types'; - -export function validateRoutes( - routePaths: Map, - routeParents: Map, -) { - const notLeafRoutes = new Set(routeParents.values()); - notLeafRoutes.delete(undefined); - - for (const route of routeParents.keys()) { - if (notLeafRoutes.has(route)) { - continue; - } - - let currentRouteRef: AnyRouteRef | undefined = route; - - let fullPath = ''; - while (currentRouteRef) { - const path = routePaths.get(currentRouteRef); - if (!path) { - throw new Error(`No path for ${currentRouteRef}`); - } - fullPath = `${path}${fullPath}`; - currentRouteRef = routeParents.get(currentRouteRef); - } - - const params = fullPath.match(/:(\w+)/g); - if (params) { - for (let j = 0; j < params.length; j++) { - for (let i = j + 1; i < params.length; i++) { - if (params[i] === params[j]) { - throw new Error( - `Parameter ${params[i]} is duplicated in path ${fullPath}`, - ); - } - } - } - } - } -} diff --git a/packages/core-api/src/setupTests.ts b/packages/core-api/src/setupTests.ts deleted file mode 100644 index c1d649f2ad..0000000000 --- a/packages/core-api/src/setupTests.ts +++ /dev/null @@ -1,18 +0,0 @@ -/* - * Copyright 2020 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 '@testing-library/jest-dom'; -import 'cross-fetch/polyfill'; diff --git a/packages/core-api/src/types.ts b/packages/core-api/src/types.ts deleted file mode 100644 index fff4cb1515..0000000000 --- a/packages/core-api/src/types.ts +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright 2020 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. - */ - -/** - * This file contains non-react related core types used throughout Backstage. - */ - -/** - * Observer interface for consuming an Observer, see TC39. - */ -export type Observer = { - next?(value: T): void; - error?(error: Error): void; - complete?(): void; -}; - -/** - * Subscription returned when subscribing to an Observable, see TC39. - */ -export type Subscription = { - /** - * Cancels the subscription - */ - unsubscribe(): void; - - /** - * Value indicating whether the subscription is closed. - */ - readonly closed: boolean; -}; - -// Declares the global well-known Symbol.observable -// We get the actual runtime polyfill from zen-observable -declare global { - interface SymbolConstructor { - readonly observable: symbol; - } -} - -/** - * Observable sequence of values and errors, see TC39. - * - * https://github.com/tc39/proposal-observable - * - * This is used as a common return type for observable values and can be created - * using many different observable implementations, such as zen-observable or RxJS 5. - */ -export type Observable = { - [Symbol.observable](): Observable; - - /** - * Subscribes to this observable to start receiving new values. - */ - subscribe(observer: Observer): Subscription; - subscribe( - onNext?: (value: T) => void, - onError?: (error: Error) => void, - onComplete?: () => void, - ): Subscription; -}; diff --git a/packages/core/.eslintrc.js b/packages/core/.eslintrc.js deleted file mode 100644 index d592a653c8..0000000000 --- a/packages/core/.eslintrc.js +++ /dev/null @@ -1,8 +0,0 @@ -module.exports = { - extends: [require.resolve('@backstage/cli/config/eslint')], - rules: { - // TODO: add prop types to JS and remove - 'react/prop-types': 0, - 'jest/expect-expect': 0, - }, -}; diff --git a/packages/core/.npmrc b/packages/core/.npmrc deleted file mode 100644 index 214c29d139..0000000000 --- a/packages/core/.npmrc +++ /dev/null @@ -1 +0,0 @@ -registry=https://registry.npmjs.org/ diff --git a/packages/core/CHANGELOG.md b/packages/core/CHANGELOG.md deleted file mode 100644 index 789aeddc29..0000000000 --- a/packages/core/CHANGELOG.md +++ /dev/null @@ -1,516 +0,0 @@ -# @backstage/core - -## 0.7.14 - -### Patch Changes - -- a1c30d7ea: Add deprecation warning to package README. -- Updated dependencies - - @backstage/core-api@0.2.23 - -## 0.7.13 - -### Patch Changes - -- d4644f592: Use the Backstage `Link` component in the `Button` - -## 0.7.12 - -### Patch Changes - -- 1cf1d351f: Export `CheckboxTree` as we have a storybook for it -- Updated dependencies [e7c5e4b30] -- Updated dependencies [0160678b1] - - @backstage/theme@0.2.8 - - @backstage/core-api@0.2.21 - -## 0.7.11 - -### Patch Changes - -- cc592248b: SignInPage: Show login page while pop-up is being displayed when `auto` prop is set. -- Updated dependencies [d597a50c6] - - @backstage/core-api@0.2.20 - -## 0.7.10 - -### Patch Changes - -- 65e6c4541: Remove circular dependencies -- 5da6a561d: Fix a bug where users are asked to log-in on every page refresh. This is specific to people with only one sign-in provider in their SignInPage component. -- Updated dependencies [61c3f927c] -- Updated dependencies [65e6c4541] - - @backstage/core-api@0.2.19 - -## 0.7.9 - -### Patch Changes - -- 062bbf90f: chore: bump `@testing-library/user-event` from 12.8.3 to 13.1.8 -- 889d89b6e: Fix state persisted in the URL make search input in the table toolbar lose their - focus. -- 3f988cb63: Add count of older messages when multiple messages exist in AlertDisplay -- 675a569a9: chore: bump `react-use` dependency in all packages -- Updated dependencies [062bbf90f] -- Updated dependencies [675a569a9] - - @backstage/core-api@0.2.18 - -## 0.7.8 - -### Patch Changes - -- f65adcde7: Fix some transitive dependency warnings in yarn -- 80888659b: Bump react-hook-form version to be the same for the entire project. -- Updated dependencies [7b8272fb7] -- Updated dependencies [d8b81fd28] - - @backstage/theme@0.2.7 - - @backstage/config@0.1.5 - -## 0.7.7 - -### Patch Changes - -- 9afcac5af: Allow passing NavLinkProps to SidebarItem component to use in NavLink -- e0c9ed759: Add `if` prop to `EntityLayout.Route` to conditionally render tabs -- 6eaecbd81: Improve owner example value in `MissingAnnotationEmptyState`. - -## 0.7.6 - -### Patch Changes - -- 94da20976: Sort the table filter options by name. -- d8cc7e67a: Exposing Material UI extension point for tabs to be able to add additional information to them -- 99fbef232: Adding Headings for Accessibility on the Scaffolder Plugin -- ab07d77f6: Add support for discovering plugins through the app element tree, removing the need to register them explicitly. -- 937ed39ce: Exported SignInProviderConfig to strongly type SignInPage providers -- 9a9e7a42f: Adding close button on support menu -- 50ce875a0: Fixed a potentially confusing error being thrown about misuse of routable extensions where the error was actually something different. -- Updated dependencies [ab07d77f6] -- Updated dependencies [931b21a12] -- Updated dependencies [50ce875a0] - - @backstage/core-api@0.2.17 - - @backstage/theme@0.2.6 - -## 0.7.5 - -### Patch Changes - -- d0d1c2f7b: Pass `inverse` prop to Gauge from GaugeCard -- 5cafcf452: add debounce time attribute for apis-docs for search, giving more time to the users when they are typing. -- 86a95ba67: exposes undocumented `PageTheme` -- e27cb6c45: Don't use a drag & drop cursor when clicking on disabled `IconLinkVertical`. - -## 0.7.4 - -### Patch Changes - -- 1279a3325: Introduce a `load-chunk` step in the `BootErrorPage` to show make chunk loading - errors visible to the user. -- 4a4681b1b: Improved error messaging for routable extension errors, making it easier to identify the component and mount point that caused the error. -- b051e770c: Fixed a bug with `useRouteRef` where navigating from routes beneath a mount point would often fail. -- 98dd5da71: Add support for multiple links to post-scaffold task summary page -- Updated dependencies [1279a3325] -- Updated dependencies [4a4681b1b] -- Updated dependencies [b051e770c] - - @backstage/core-api@0.2.16 - -## 0.7.3 - -### Patch Changes - -- fcc3ada24: Reuse ResponseErrorList for non ResponseErrors -- 4618774ff: Changed color for Add Item, Support & Choose buttons with low contrast/readability in dark mode -- df59930b3: Fix PropTypes error with OverflowTooltip component -- Updated dependencies [76deafd31] -- Updated dependencies [01ccef4c7] -- Updated dependencies [4618774ff] - - @backstage/core-api@0.2.15 - - @backstage/theme@0.2.5 - -## 0.7.2 - -### Patch Changes - -- 8686eb38c: Add a `ResponseErrorPanel` to render `ResponseError` from `@backstage/errors` -- 9ca0e4009: use local version of lowerCase and upperCase methods -- 34ff49b0f: Allow extension components to also return `null` in addition to a `JSX.Element`. -- Updated dependencies [a51dc0006] -- Updated dependencies [e7f9b9435] -- Updated dependencies [0434853a5] -- Updated dependencies [34ff49b0f] -- Updated dependencies [d88dd219e] -- Updated dependencies [c8b54c370] - - @backstage/core-api@0.2.14 - - @backstage/config@0.1.4 - -## 0.7.1 - -### Patch Changes - -- ff4d666ab: Add support for passing a fetch function instead of data to Table `data` prop. -- 2089de76b: Deprecated `ItemCard`. Added `ItemCardGrid` and `ItemCardHeader` instead, that can be used to compose functionality around regular Material-UI `Card` components instead. -- dc1fc92c8: Add support for non external URI's in the Link component to `to` prop. For example `Slack -- Updated dependencies [13524b80b] -- Updated dependencies [e74b07578] -- Updated dependencies [6fb4258a8] -- Updated dependencies [2089de76b] -- Updated dependencies [395885905] - - @backstage/core-api@0.2.13 - - @backstage/theme@0.2.4 - -## 0.7.0 - -### Minor Changes - -- 4c049a1a1: - Adds onClick and other props to IconLinkVertical; - - - Allows TriggerButton component to render when pager duty key is missing; - - Refactors TriggerButton and PagerDutyCard not to have shared state; - - Removes the `action` prop of the IconLinkVertical component while adding `onClick`. - - Instead of having an action including a button with onClick, now the whole component can be clickable making it easier to implement and having a better UX. - - Before: - - ```ts - const myLink: IconLinkVerticalProps = { - label: 'Click me', - action: - - - - Secondary Button: - Used for actions that cancel, skip, and in general perform negative - functions, etc. -
    -
    color="secondary" variant="contained"
    -
    - - -
    - - - Tertiary Button: - Used commonly in a ButtonGroup and when the button function itself is - not a primary function on a page. -
    -
    color="default" variant="outlined"
    -
    - - -
    - - ); -}; - -export const ButtonLinks = () => { - const routeRef = createRouteRef({ - path: '/hello', - title: 'Hi there!', - }); - - const handleClick = () => { - return 'Your click worked!'; - }; - - return ( - <> - - { - // TODO: Refactor to use new routing mechanisms - } - - -   has props for both Material-UI's component as well as for - react-router-dom's Route object. - - - - -   links to a statically defined route. In general, this should be - avoided. - - - - - View URL - -   links to a defined URL using Material-UI's Button. - - - - - Trigger Event - -   triggers an onClick event using Material-UI's Button. - - - - ); -}; diff --git a/packages/core/src/components/Button/Button.test.tsx b/packages/core/src/components/Button/Button.test.tsx deleted file mode 100644 index e3a1074e6f..0000000000 --- a/packages/core/src/components/Button/Button.test.tsx +++ /dev/null @@ -1,42 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React from 'react'; -import { render, fireEvent, act } from '@testing-library/react'; -import { wrapInTestApp } from '@backstage/test-utils'; -import { Button } from './Button'; -import { Route, Routes } from 'react-router'; - -describe(' - , - ), - ); - - expect(() => getByText(testString)).toThrow(); - await act(async () => { - fireEvent.click(getByText(buttonLabel)); - }); - expect(getByText(testString)).toBeInTheDocument(); - }); -}); diff --git a/packages/core/src/components/Button/Button.tsx b/packages/core/src/components/Button/Button.tsx deleted file mode 100644 index 6dd593d776..0000000000 --- a/packages/core/src/components/Button/Button.tsx +++ /dev/null @@ -1,32 +0,0 @@ -/* - * Copyright 2020 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 { - Button as MaterialButton, - ButtonProps as MaterialButtonProps, -} from '@material-ui/core'; -import React from 'react'; -import { Link, LinkProps } from '../Link'; - -type Props = MaterialButtonProps & Omit; - -/** - * Thin wrapper on top of material-ui's Button component - * Makes the Button to utilise react-router - */ -export const Button = React.forwardRef((props, ref) => ( - -)); diff --git a/packages/core/src/components/Button/index.ts b/packages/core/src/components/Button/index.ts deleted file mode 100644 index e2aa3aff98..0000000000 --- a/packages/core/src/components/Button/index.ts +++ /dev/null @@ -1,17 +0,0 @@ -/* - * Copyright 2020 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 { Button } from './Button'; diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx deleted file mode 100644 index 48da8b92b2..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.stories.tsx +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2020 The Backstage Authors - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -import React, { useState } from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 2', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, - { - label: 'Generic subcategory name 3', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -export default { - title: 'Inputs/CheckboxTree', - component: CheckboxTree, -}; - -export const Default = () => ( - {}} - label="default" - subCategories={CHECKBOX_TREE_ITEMS} - /> -); - -export const DynamicTree = () => { - function generateTree(showMore: boolean = false) { - const t = [ - { - label: 'Show more', - options: [], - }, - ]; - - if (showMore) { - t.push({ - label: 'More', - options: [], - }); - } - - return t; - } - - const [tree, setTree] = useState(generateTree()); - - return ( - { - setTree(generateTree(state.some(c => c.category === 'Show more'))); - }} - label="default" - subCategories={tree} - /> - ); -}; diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx deleted file mode 100644 index 6c84a311d2..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.test.tsx +++ /dev/null @@ -1,59 +0,0 @@ -/* - * Copyright 2020 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 { fireEvent, render } from '@testing-library/react'; -import React from 'react'; -import { CheckboxTree } from './CheckboxTree'; - -const CHECKBOX_TREE_ITEMS = [ - { - label: 'Generic subcategory name 1', - options: [ - { - label: 'Option 1', - value: 1, - }, - { - label: 'Option 2', - value: 2, - }, - ], - }, -]; - -const minProps = { - onChange: jest.fn(), - label: 'Default', - subCategories: CHECKBOX_TREE_ITEMS, -}; - -describe('', () => { - it('renders without exploding', async () => { - const { getByText, getByTestId } = render(); - - expect(getByText('Generic subcategory name 1')).toBeInTheDocument(); - const checkbox = await getByTestId('expandable'); - - // Simulate click on expandable arrow - fireEvent.click(checkbox); - - // Simulate click on option - const option = getByText('Option 1'); - expect(getByText('Option 1')).toBeInTheDocument(); - fireEvent.click(option); - expect(minProps.onChange).toHaveBeenCalled(); - }); -}); diff --git a/packages/core/src/components/CheckboxTree/CheckboxTree.tsx b/packages/core/src/components/CheckboxTree/CheckboxTree.tsx deleted file mode 100644 index 7e7155143b..0000000000 --- a/packages/core/src/components/CheckboxTree/CheckboxTree.tsx +++ /dev/null @@ -1,358 +0,0 @@ -/* - * Copyright 2020 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. - */ -/* eslint-disable guard-for-in */ -import { - Checkbox, - Collapse, - List, - ListItem, - ListItemIcon, - ListItemText, - Typography, -} from '@material-ui/core'; -import { createStyles, makeStyles, Theme } from '@material-ui/core/styles'; -import ExpandLess from '@material-ui/icons/ExpandLess'; -import ExpandMore from '@material-ui/icons/ExpandMore'; -import produce from 'immer'; -import { isEqual } from 'lodash'; -import React, { useEffect, useReducer } from 'react'; -import { usePrevious } from 'react-use'; - -type IndexedObject = { - [key: string]: T; -}; - -const useStyles = makeStyles((theme: Theme) => - createStyles({ - root: { - width: '100%', - minWidth: 10, - maxWidth: 360, - backgroundColor: 'transparent', - '&:hover': { - backgroundColor: 'transparent', - }, - '&:active': { - animation: 'none', - transform: 'none', - }, - }, - nested: { - paddingLeft: theme.spacing(5), - height: '32px', - '&:hover': { - backgroundColor: 'transparent', - }, - }, - listItemIcon: { - minWidth: 10, - }, - listItem: { - '&:hover': { - backgroundColor: 'transparent', - }, - }, - text: { - '& span, & svg': { - fontWeight: 'normal', - fontSize: 14, - }, - }, - }), -); - -/* SUB_CATEGORY */ - -type SubCategory = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options?: Option[]; -}; - -type SubCategoryWithIndexedOptions = { - label: string; - isChecked?: boolean; - isOpen?: boolean; - options: IndexedObject