From d3db3a784997765295e9d72f3112b5c4688ea54c Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Fri, 21 Aug 2020 23:04:51 +0200 Subject: [PATCH 001/132] feat: IoC, DI, indirection, routes, tabs, catalog, app, github-actions --- packages/app/src/App.tsx | 15 +- packages/app/src/components/Root/Root.tsx | 2 +- .../app/src/components/catalog/Component.tsx | 82 +++++++++ .../app/src/components/catalog/index.tsx | 15 +- plugins/catalog/package.json | 1 + plugins/catalog/src/Router.tsx | 164 ++++++++++++++++++ .../EntityPageTabs/EntityPageTabs.tsx | 94 ++++++++++ .../src/components/EntityPageTabs}/index.ts | 3 +- plugins/catalog/src/hooks/useEntity.ts | 55 ++++++ plugins/catalog/src/index.ts | 3 + plugins/catalog/src/routes.ts | 6 +- plugins/github-actions/package.json | 1 + plugins/github-actions/src/Router.tsx | 34 ++++ .../WorkflowRunDetails/WorkflowRunDetails.tsx | 28 ++- .../WorkflowRunDetailsPage.tsx | 65 ------- .../WorkflowRunsPage/WorkflowRunsPage.tsx | 56 ------ .../WorkflowRunsTable/WorkflowRunsTable.tsx | 19 +- .../src/components/useProjectName.ts | 10 +- plugins/github-actions/src/index.ts | 2 + plugins/github-actions/src/plugin.ts | 17 +- 20 files changed, 494 insertions(+), 178 deletions(-) create mode 100644 packages/app/src/components/catalog/Component.tsx rename plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts => packages/app/src/components/catalog/index.tsx (60%) create mode 100644 plugins/catalog/src/Router.tsx create mode 100644 plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx rename plugins/{github-actions/src/components/WorkflowRunsPage => catalog/src/components/EntityPageTabs}/index.ts (91%) create mode 100644 plugins/catalog/src/hooks/useEntity.ts create mode 100644 plugins/github-actions/src/Router.tsx delete mode 100644 plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx delete mode 100644 plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 993fd77978..cb0853a013 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -26,6 +26,11 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; +import { CatalogPlugin } from '@backstage/plugin-catalog'; +// import { ExplorePlugin } from '@backstage/plugin-explore'; +import { Route, Routes } from 'react-router'; + +import { EntityPage } from './components/catalog'; const app = createApp({ apis, @@ -46,8 +51,16 @@ const app = createApp({ const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); -const AppRoutes = app.getRoutes(); +const AppRoutes = () => ( + + } + /> + {/* } /> */} + +); const App: FC<{}> = () => ( diff --git a/packages/app/src/components/Root/Root.tsx b/packages/app/src/components/Root/Root.tsx index 1d32b6e16e..c6ce87444c 100644 --- a/packages/app/src/components/Root/Root.tsx +++ b/packages/app/src/components/Root/Root.tsx @@ -89,7 +89,7 @@ const Root: FC<{}> = ({ children }) => ( {/* Global nav, not org-specific */} - + diff --git a/packages/app/src/components/catalog/Component.tsx b/packages/app/src/components/catalog/Component.tsx new file mode 100644 index 0000000000..2d2aef9f79 --- /dev/null +++ b/packages/app/src/components/catalog/Component.tsx @@ -0,0 +1,82 @@ +/* + * Copyright 2020 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 React from 'react'; +import { + GitHubActionsPlugin, + GITHUB_ACTIONS_ANNOTATION, +} from '@backstage/plugin-github-actions'; +import { + useEntity, + EntityPageTabs as Tabs, + EntityMetadataCard, +} from '@backstage/plugin-catalog'; +import { Grid } from '@material-ui/core'; + +const OverviewPage = () => { + const entity = useEntity(); + return ( + + + + ); +}; + +// Just for illustration purposes, gonna live in plugin-circle-ci +const CIRCLE_CI_ANNOTATION = 'circle-ci-slug-dummy'; + +const DefaultEntity = () => { + const entity = useEntity(); + + const isCIAvailable = [ + entity!.metadata!.annotations?.[GITHUB_ACTIONS_ANNOTATION], + entity!.metadata!.annotations?.[CIRCLE_CI_ANNOTATION], + ].some(Boolean); + + return ( + + + + + {isCIAvailable && ( + + {entity!.metadata!.annotations?.[GITHUB_ACTIONS_ANNOTATION] && ( + + )} + + )} + + {/* eslint-disable-next-line no-console */} +
{console.log('was here /docs')}Docs tab contents
+
+
+ ); +}; + +const Service = () => ; +const Website = () => ; +const UnkownEntity = () =>
Unknown entity!
; + +export const ComponentEntity = () => { + const entity = useEntity(); + switch (entity.spec!.type) { + case 'service': + return ; + case 'website': + return ; + default: + return ; + } +}; diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts b/packages/app/src/components/catalog/index.tsx similarity index 60% rename from plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts rename to packages/app/src/components/catalog/index.tsx index 443bcacc88..9aaf1a568b 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/index.ts +++ b/packages/app/src/components/catalog/index.tsx @@ -13,5 +13,18 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import React from 'react'; +import { useEntity } from '@backstage/plugin-catalog'; +import { ComponentEntity } from './Component'; -export { WorkflowRunDetailsPage } from './WorkflowRunDetailsPage'; +const UnkownEntityKind = () =>
Unknown entity kind!
; + +export const EntityPage = () => { + const entity = useEntity(); + switch (entity.kind) { + case 'Component': + return ; + default: + return ; + } +}; diff --git a/plugins/catalog/package.json b/plugins/catalog/package.json index 1e10e91478..4768fdf985 100644 --- a/plugins/catalog/package.json +++ b/plugins/catalog/package.json @@ -36,6 +36,7 @@ "moment": "^2.26.0", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-helmet": "6.1.0", "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3", diff --git a/plugins/catalog/src/Router.tsx b/plugins/catalog/src/Router.tsx new file mode 100644 index 0000000000..701b9d1045 --- /dev/null +++ b/plugins/catalog/src/Router.tsx @@ -0,0 +1,164 @@ +/* + * Copyright 2020 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 React, { FC, useState } from 'react'; +import { CatalogPage } from './components/CatalogPage/CatalogPage'; +// import { EntityPage } from './components/EntityPage/EntityPage'; +import { Route, Routes, useParams, useNavigate } from 'react-router'; +import { entityRoute, rootRoute, entityRouteDefault } from './routes'; +import { useEntityFromUrl, EntityContext, useEntity } from './hooks/useEntity'; +import { + pageTheme, + PageTheme, + Page, + Header, + HeaderLabel, + Content, + Progress, +} from '@backstage/core'; +import { Entity } from '@backstage/catalog-model'; +import { FavouriteEntity } from './components/FavouriteEntity/FavouriteEntity'; +import { Box } from '@material-ui/core'; +import { EntityContextMenu } from './components/EntityContextMenu/EntityContextMenu'; +import { UnregisterEntityDialog } from './components/UnregisterEntityDialog/UnregisterEntityDialog'; +import { Alert } from '@material-ui/lab'; +// const EntityContext = React.createContext(null); +export const getPageTheme = (entity?: Entity): PageTheme => { + const themeKey = entity?.spec?.type?.toString() ?? 'home'; + return pageTheme[themeKey] ?? pageTheme.home; +}; + +const EntityPageTitle = ({ + entity, + title, +}: { + title: string; + entity: Entity | undefined; +}) => ( + + {title} + {entity && } + +); + +function headerProps( + kind: string, + namespace: string | undefined, + name: string, + entity: Entity | undefined, +): { headerTitle: string; headerType: string } { + return { + headerTitle: `${name}${namespace ? ` in ${namespace}` : ''}`, + headerType: (() => { + let t = kind.toLowerCase(); + if (entity && entity.spec && 'type' in entity.spec) { + t += ' — '; + t += (entity.spec as { type: string }).type.toLowerCase(); + } + return t; + })(), + }; +} +const EntityPageLayout = ({ children }: { children: React.ReactNode }) => { + const { entity, loading, error } = useEntityFromUrl(); + const { optionalNamespaceAndName, kind } = useParams() as { + optionalNamespaceAndName: string; + kind: string; + }; + const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); + + const { headerTitle, headerType } = headerProps( + kind, + namespace, + name, + entity!, + ); + + const [confirmationDialogOpen, setConfirmationDialogOpen] = useState(false); + const navigate = useNavigate(); + const cleanUpAfterRemoval = async () => { + setConfirmationDialogOpen(false); + navigate('/'); + }; + + const showRemovalDialog = () => setConfirmationDialogOpen(true); + + return ( + +
} + pageTitleOverride={headerTitle} + type={headerType} + > + {entity && ( + <> + + + + + )} +
+ + {loading && } + + {entity && ( + + {children} + + )} + {error && ( + + {error.toString()} + + )} + setConfirmationDialogOpen(false)} + /> +
+ ); +}; + +export const CatalogPlugin = ({ EntityPage }) => ( + + } /> + + + + } + /> + + + + } + /> + +); + +export { EntityMetadataCard } from './components/EntityMetadataCard/EntityMetadataCard'; diff --git a/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx b/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx new file mode 100644 index 0000000000..f4f9308669 --- /dev/null +++ b/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx @@ -0,0 +1,94 @@ +/* + * Copyright 2020 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 React from 'react'; +import { + useParams, + useNavigate, + PartialRouteObject, + matchRoutes, + RouteObject, + useRoutes, + Navigate, + RouteMatch, +} from 'react-router'; +import { Tab, HeaderTabs, Content } from '@backstage/core'; +import { Grid } from '@material-ui/core'; +import { Helmet } from 'react-helmet'; + +const getSelectedIndex = (matchedRoute: RouteMatch, tabs: Tab[]) => { + if (!matchedRoute) return 0; + const tabIndex = tabs.findIndex(t => t.id === matchedRoute.route.path); + return ~tabIndex ? tabIndex : 0; +}; +export const EntityPageTabs = ({ children }: { children: React.ReactNode }) => { + const routes: PartialRouteObject[] = []; + const tabs: Tab[] = []; + const params = useParams(); + const navigate = useNavigate(); + React.Children.forEach(children, child => { + if (!React.isValidElement(child)) { + // Skip conditionals resolved to falses/nulls/undefineds etc + return; + } + const pathAndId = + (child as JSX.Element).props.path + + ((child as JSX.Element).props.exact ? '' : '/*'); + routes.push({ + path: pathAndId, + element: (child as JSX.Element).props.children, + }); + tabs.push({ + id: pathAndId, + label: (child as JSX.Element).props.title, + }); + routes.push({ + path: '/*', + element: , + }); + }); + const [matchedRoute] = + matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? []; + const selectedIndex = getSelectedIndex(matchedRoute, tabs); + const currentTab = tabs[selectedIndex]; + const title = currentTab.label; + + const onTabChange = (index: number) => navigate(tabs[index].id.slice(1, -2)); + + const currentRouteElement = useRoutes(routes); + + return ( + <> + + + + + {currentRouteElement} + + + + ); +}; +type TabProps = { + children: React.ReactNode; + title: string; + path: string; + exact?: boolean; +}; +EntityPageTabs.Tab = (_props: TabProps) => null; diff --git a/plugins/github-actions/src/components/WorkflowRunsPage/index.ts b/plugins/catalog/src/components/EntityPageTabs/index.ts similarity index 91% rename from plugins/github-actions/src/components/WorkflowRunsPage/index.ts rename to plugins/catalog/src/components/EntityPageTabs/index.ts index ef511763f7..83fc3d1095 100644 --- a/plugins/github-actions/src/components/WorkflowRunsPage/index.ts +++ b/plugins/catalog/src/components/EntityPageTabs/index.ts @@ -13,5 +13,4 @@ * See the License for the specific language governing permissions and * limitations under the License. */ - -export { WorkflowRunsPage } from './WorkflowRunsPage'; +export { EntityPageTabs } from './EntityPageTabs'; diff --git a/plugins/catalog/src/hooks/useEntity.ts b/plugins/catalog/src/hooks/useEntity.ts new file mode 100644 index 0000000000..022be1126f --- /dev/null +++ b/plugins/catalog/src/hooks/useEntity.ts @@ -0,0 +1,55 @@ +/* + * Copyright 2020 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 { useEffect, createContext, useContext } from 'react'; +import { useNavigate, useParams } from 'react-router'; +import { useApi, errorApiRef } from '@backstage/core'; +import { catalogApiRef } from '../api/types'; +import { useAsync } from 'react-use'; +import { Entity } from '@backstage/catalog-model'; + +const REDIRECT_DELAY = 2000; + +export const EntityContext = createContext((null as any) as Entity); +export const useEntityFromUrl = () => { + const { optionalNamespaceAndName, kind } = useParams(); + const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); + const navigate = useNavigate(); + const errorApi = useApi(errorApiRef); + const catalogApi = useApi(catalogApiRef); + + const { value: entity, error, loading } = useAsync( + () => catalogApi.getEntityByName({ kind, namespace, name }), + [catalogApi, kind, namespace, name], + ); + + useEffect(() => { + if (!error && !loading && !entity) { + errorApi.post(new Error('Entity not found!')); + setTimeout(() => { + navigate('/'); + }, REDIRECT_DELAY); + } + }, [errorApi, navigate, error, loading, entity]); + + if (!name) { + navigate('/catalog'); + return { entity: null, loading: null, error: new Error('No name in url') }; + } + + return { entity, loading, error }; +}; + +export const useEntity = () => useContext(EntityContext); diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 36fd69971d..ba8826aaa9 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -19,3 +19,6 @@ export * from './api/CatalogClient'; export * from './api/types'; export * from './routes'; export { useEntityCompoundName } from './components/useEntityCompoundName'; +export * from './Router'; +export { useEntity } from './hooks/useEntity'; +export { EntityPageTabs } from './components/EntityPageTabs'; diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts index a8ff8df685..c3fd97564a 100644 --- a/plugins/catalog/src/routes.ts +++ b/plugins/catalog/src/routes.ts @@ -20,16 +20,16 @@ const NoIcon = () => null; export const rootRoute = createRouteRef({ icon: NoIcon, - path: '/', + path: '', title: 'Catalog', }); export const entityRoute = createRouteRef({ icon: NoIcon, - path: '/catalog/:kind/:optionalNamespaceAndName/:selectedTabId/*', + path: ':kind/:optionalNamespaceAndName/*', title: 'Entity', }); export const entityRouteDefault = createRouteRef({ icon: NoIcon, - path: '/catalog/:kind/:optionalNamespaceAndName', + path: ':kind/:optionalNamespaceAndName', title: 'Entity', }); diff --git a/plugins/github-actions/package.json b/plugins/github-actions/package.json index db35022fae..3386ed0db2 100644 --- a/plugins/github-actions/package.json +++ b/plugins/github-actions/package.json @@ -35,6 +35,7 @@ "moment": "^2.27.0", "react": "^16.13.1", "react-dom": "^16.13.1", + "react-router": "6.0.0-beta.0", "react-router-dom": "6.0.0-beta.0", "react-use": "^15.3.3" }, diff --git a/plugins/github-actions/src/Router.tsx b/plugins/github-actions/src/Router.tsx new file mode 100644 index 0000000000..b843c594b9 --- /dev/null +++ b/plugins/github-actions/src/Router.tsx @@ -0,0 +1,34 @@ +/* + * Copyright 2020 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 React from 'react'; +import { Entity } from '@backstage/catalog-model'; +import { Routes, Route } from 'react-router'; +import { rootRouteRef, buildRouteRef } from './plugin'; +import { WorkflowRunDetails } from './components/WorkflowRunDetails'; +import { WorkflowRunsTable } from './components/WorkflowRunsTable'; + +export const GitHubActionsPlugin = ({ entity }: { entity: Entity }) => ( + + } + /> + } + /> + +); diff --git a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx index 8c445ca9f6..d8e3b02524 100644 --- a/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx +++ b/plugins/github-actions/src/components/WorkflowRunDetails/WorkflowRunDetails.tsx @@ -14,7 +14,6 @@ * limitations under the License. */ import React from 'react'; -import { useEntityCompoundName } from '@backstage/plugin-catalog'; import { useWorkflowRunsDetails } from './useWorkflowRunsDetails'; import { useWorkflowRunJobs } from './useWorkflowRunJobs'; import { useProjectName } from '../useProjectName'; @@ -35,13 +34,16 @@ import { LinearProgress, CircularProgress, Theme, - Link, + Breadcrumbs, + Link as MaterialLink, } from '@material-ui/core'; import { Jobs, Job, Step } from '../../api'; import moment from 'moment'; import { WorkflowRunStatus } from '../WorkflowRunStatus'; import ExpandMoreIcon from '@material-ui/icons/ExpandMore'; import ExternalLinkIcon from '@material-ui/icons/Launch'; +import { Entity } from '@backstage/catalog-model'; +import { Link } from '@backstage/core'; const useStyles = makeStyles(theme => ({ root: { @@ -140,18 +142,8 @@ const JobListItem = ({ job, className }: { job: Job; className: string }) => { ); }; -export const WorkflowRunDetails = () => { - let entityCompoundName = useEntityCompoundName(); - if (!entityCompoundName.name) { - // TODO(shmidt-i): remove when is fully integrated - // into the entity view - entityCompoundName = { - kind: 'Component', - name: 'backstage', - namespace: 'default', - }; - } - const projectName = useProjectName(entityCompoundName); +export const WorkflowRunDetails = ({ entity }: { entity: Entity }) => { + const projectName = useProjectName(entity); const [owner, repo] = projectName.value ? projectName.value.split('/') : []; const details = useWorkflowRunsDetails(repo, owner); @@ -170,6 +162,10 @@ export const WorkflowRunDetails = () => { } return (
+ + Workflow runs + Workflow run details + @@ -211,10 +207,10 @@ export const WorkflowRunDetails = () => { {details.value?.html_url && ( - + Workflow runs on GitHub{' '} - + )} diff --git a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx b/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx deleted file mode 100644 index ec9a3f484d..0000000000 --- a/plugins/github-actions/src/components/WorkflowRunDetailsPage/WorkflowRunDetailsPage.tsx +++ /dev/null @@ -1,65 +0,0 @@ -/* - * Copyright 2020 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 { Typography, Grid, Breadcrumbs } from '@material-ui/core'; - -import React from 'react'; -import { - Link, - Page, - Header, - HeaderLabel, - Content, - ContentHeader, - SupportButton, - pageTheme, -} from '@backstage/core'; - -import { WorkflowRunDetails } from '../WorkflowRunDetails'; - -/** - * A component for Jobs visualization. Jobs are a property of a Workflow Run. - */ -export const WorkflowRunDetailsPage = () => { - return ( - -
- - -
- - - - This plugin allows you to view and interact with your builds within - the GitHub Actions environment. - - - - Workflow runs - Workflow run details - - - - - - - -
- ); -}; diff --git a/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx b/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx deleted file mode 100644 index b75a70f326..0000000000 --- a/plugins/github-actions/src/components/WorkflowRunsPage/WorkflowRunsPage.tsx +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright 2020 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 { - Header, - HeaderLabel, - pageTheme, - Page, - Content, - ContentHeader, - SupportButton, -} from '@backstage/core'; -import { Grid } from '@material-ui/core'; -import React from 'react'; - -import { WorkflowRunsTable } from '../WorkflowRunsTable'; - -export const WorkflowRunsPage = () => { - return ( - -
- - -
- - - - This plugin allows you to view and interact with your builds within - the GitHub Actions environment. - - - - - - - - -
- ); -}; diff --git a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx index a2a6ead9b5..7e971118fc 100644 --- a/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx +++ b/plugins/github-actions/src/components/WorkflowRunsTable/WorkflowRunsTable.tsx @@ -25,6 +25,7 @@ import SyncIcon from '@material-ui/icons/Sync'; import { buildRouteRef } from '../../plugin'; import { useEntityCompoundName } from '@backstage/plugin-catalog'; import { useProjectName } from '../useProjectName'; +import { Entity } from '@backstage/catalog-model'; export type WorkflowRun = { id: string; @@ -134,6 +135,7 @@ export const WorkflowRunsTableView: FC = ({ data={runs ?? []} onChangePage={onChangePage} onChangeRowsPerPage={onChangePageSize} + style={{ width: '100%' }} title={ @@ -146,25 +148,14 @@ export const WorkflowRunsTableView: FC = ({ ); }; -export const WorkflowRunsTable = () => { - let entityCompoundName = useEntityCompoundName(); - - if (!entityCompoundName.name) { - // TODO(shmidt-i): remove when is fully integrated - // into the entity view - entityCompoundName = { - kind: 'Component', - name: 'backstage', - namespace: 'default', - }; - } - - const { value: projectName, loading } = useProjectName(entityCompoundName); +export const WorkflowRunsTable = ({ entity }: { entity: Entity }) => { + const { value: projectName, loading } = useProjectName(entity); const [owner, repo] = (projectName ?? '/').split('/'); const [tableProps, { retry, setPage, setPageSize }] = useWorkflowRuns({ owner, repo, }); + return ( { - const catalogApi = useApi(catalogApiRef); +export const GITHUB_ACTIONS_ANNOTATION = 'github.com/project-slug'; +export const useProjectName = (entity: Entity) => { const { value, loading, error } = useAsync(async () => { - const entity = await catalogApi.getEntityByName(name); - return entity?.metadata.annotations?.['github.com/project-slug'] ?? ''; + return entity?.metadata.annotations?.[GITHUB_ACTIONS_ANNOTATION] ?? ''; }); return { value, loading, error }; }; diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index 4a69c363cd..f500685507 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -17,3 +17,5 @@ export { plugin } from './plugin'; export * from './api'; export { Widget } from './components/Widget'; +export { GitHubActionsPlugin } from './Router'; +export { GITHUB_ACTIONS_ANNOTATION } from './components/useProjectName'; diff --git a/plugins/github-actions/src/plugin.ts b/plugins/github-actions/src/plugin.ts index dbc366bd71..ead33b7959 100644 --- a/plugins/github-actions/src/plugin.ts +++ b/plugins/github-actions/src/plugin.ts @@ -15,28 +15,19 @@ */ import { createPlugin, createRouteRef } from '@backstage/core'; -import { WorkflowRunDetailsPage } from './components/WorkflowRunDetailsPage'; -import { WorkflowRunsPage } from './components/WorkflowRunsPage'; // TODO(freben): This is just a demo route for now export const rootRouteRef = createRouteRef({ - path: '/github-actions', + path: '', title: 'GitHub Actions', }); -export const projectRouteRef = createRouteRef({ - path: '/github-actions/:kind/:optionalNamespaceAndName/', - title: 'GitHub Actions for project', -}); + export const buildRouteRef = createRouteRef({ - path: '/github-actions/workflow-run/:id', + path: ':id', title: 'GitHub Actions Workflow Run', }); export const plugin = createPlugin({ id: 'github-actions', - register({ router }) { - router.addRoute(rootRouteRef, WorkflowRunsPage); - router.addRoute(projectRouteRef, WorkflowRunsPage); - router.addRoute(buildRouteRef, WorkflowRunDetailsPage); - }, + register() {}, }); From e76d33e97df7195b521c13df22dde5ff05a99376 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Sat, 22 Aug 2020 10:37:02 +0200 Subject: [PATCH 002/132] fix: move catch-all route out of loop --- .../src/components/EntityPageTabs/EntityPageTabs.tsx | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx b/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx index f4f9308669..2053c05b91 100644 --- a/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx +++ b/plugins/catalog/src/components/EntityPageTabs/EntityPageTabs.tsx @@ -38,6 +38,7 @@ export const EntityPageTabs = ({ children }: { children: React.ReactNode }) => { const tabs: Tab[] = []; const params = useParams(); const navigate = useNavigate(); + React.Children.forEach(children, child => { if (!React.isValidElement(child)) { // Skip conditionals resolved to falses/nulls/undefineds etc @@ -54,11 +55,14 @@ export const EntityPageTabs = ({ children }: { children: React.ReactNode }) => { id: pathAndId, label: (child as JSX.Element).props.title, }); - routes.push({ - path: '/*', - element: , - }); }); + + // Add catch-all for incorrect sub-routes + routes.push({ + path: '/*', + element: , + }); + const [matchedRoute] = matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? []; const selectedIndex = getSelectedIndex(matchedRoute, tabs); From 118e4861be8d791119614ea8acaa7fbc51aed366 Mon Sep 17 00:00:00 2001 From: Tim Jacomb Date: Tue, 25 Aug 2020 11:27:56 +0100 Subject: [PATCH 003/132] Use cookie cutter installed on host --- plugins/scaffolder-backend/package.json | 1 + .../stages/templater/cookiecutter.ts | 43 ++++++++++++------- .../scaffolder/stages/templater/helpers.ts | 43 +++++++++++++++++++ yarn.lock | 5 +++ 4 files changed, 76 insertions(+), 16 deletions(-) diff --git a/plugins/scaffolder-backend/package.json b/plugins/scaffolder-backend/package.json index 7112d0f49d..275da3700b 100644 --- a/plugins/scaffolder-backend/package.json +++ b/plugins/scaffolder-backend/package.json @@ -27,6 +27,7 @@ "@octokit/rest": "^18.0.0", "@types/dockerode": "^2.5.32", "@types/express": "^4.17.6", + "command-exists-promise": "^2.0.2", "compression": "^1.7.4", "cors": "^2.8.5", "dockerode": "^3.2.0", diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts index fff349cf82..21b6fc3dde 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/cookiecutter.ts @@ -15,11 +15,13 @@ */ import fs from 'fs-extra'; import { JsonValue } from '@backstage/config'; -import { runDockerContainer } from './helpers'; +import { runDockerContainer, runCommand } from './helpers'; import { TemplaterBase, TemplaterRunOptions } from '.'; import path from 'path'; import { TemplaterRunResult } from './types'; +const commandExists = require('command-exists-promise'); + export class CookieCutter implements TemplaterBase { private async fetchTemplateCookieCutter( directory: string, @@ -51,21 +53,30 @@ export class CookieCutter implements TemplaterBase { const templateDir = options.directory; const resultDir = await fs.promises.mkdtemp(`${options.directory}-result`); - await runDockerContainer({ - imageName: 'spotify/backstage-cookiecutter', - args: [ - 'cookiecutter', - '--no-input', - '-o', - '/result', - '/template', - '--verbose', - ], - templateDir, - resultDir, - logStream: options.logStream, - dockerClient: options.dockerClient, - }); + const cookieCutterInstalled = await commandExists('cookiecutter'); + if (cookieCutterInstalled) { + await runCommand({ + command: 'cookiecutter', + args: ['--no-input', '-o', resultDir, templateDir, '--verbose'], + logStream: options.logStream, + }); + } else { + await runDockerContainer({ + imageName: 'spotify/backstage-cookiecutter', + args: [ + 'cookiecutter', + '--no-input', + '-o', + '/result', + '/template', + '--verbose', + ], + templateDir, + resultDir, + logStream: options.logStream, + dockerClient: options.dockerClient, + }); + } return { resultDir: path.resolve(resultDir, options.values.component_id as string), diff --git a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts index a3dea7ea5a..e71b63bfb0 100644 --- a/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts +++ b/plugins/scaffolder-backend/src/scaffolder/stages/templater/helpers.ts @@ -18,6 +18,7 @@ import Docker from 'dockerode'; import fs from 'fs'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { InputError } from '@backstage/backend-common'; +import { spawn } from 'child_process'; export type RunDockerContainerOptions = { imageName: string; @@ -29,6 +30,12 @@ export type RunDockerContainerOptions = { createOptions?: Docker.ContainerCreateOptions; }; +export type RunCommandOptions = { + command: string; + args: string[]; + logStream?: Writable; +}; + /** * Gets the templater key to use for templating from the entity * @param entity Template entity @@ -43,6 +50,42 @@ export const getTemplaterKey = (entity: TemplateEntityV1alpha1): string => { return templater; }; +/** + * + * @param options the options object + * @param options.command the command to run + * @param options.args the arguments to pass the command + * @param options.logStream the log streamer to capture log messages + */ +export const runCommand = async ({ + command, + args, + logStream = new PassThrough(), +}: RunCommandOptions) => { + await new Promise((resolve, reject) => { + const process = spawn(command, args); + + process.stdout.on('data', stream => { + logStream.write(stream); + }); + + process.stderr.on('data', stream => { + logStream.write(stream); + }); + + process.on('error', error => { + return reject(error); + }); + + process.on('close', code => { + if (code !== 0) { + return reject(`Command ${command} failed, exit code: ${code}`); + } + return resolve(); + }); + }); +}; + /** * * @param options the options object diff --git a/yarn.lock b/yarn.lock index 857b8b2cc0..6f725fe353 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8104,6 +8104,11 @@ comma-separated-tokens@^1.0.0: resolved "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-1.0.8.tgz#632b80b6117867a158f1080ad498b2fbe7e3f5ea" integrity sha512-GHuDRO12Sypu2cV70d1dkA2EUmXHgntrzbpvOB+Qy+49ypNfGgFQIC2fhhXbnyrJRynDCAARsT7Ou0M6hirpfw== +command-exists-promise@^2.0.2: + version "2.0.2" + resolved "https://registry.npmjs.org/command-exists-promise/-/command-exists-promise-2.0.2.tgz#7beecc4b218299f3c61fa69a4047aa0b36a64a99" + integrity sha512-T6PB6vdFrwnHXg/I0kivM3DqaCGZLjjYSOe0a5WgFKcz1sOnmOeIjnhQPXVXX3QjVbLyTJ85lJkX6lUpukTzaA== + commander@^2.11.0, commander@^2.19.0, commander@^2.20.0, commander@^2.20.3, commander@^2.8.1, commander@~2.20.3: version "2.20.3" resolved "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz#fd485e84c03eb4881c20722ba48035e8531aeb33" From b8858e122026c761bbaa95a128f439557e5b14b3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 15 Aug 2020 17:25:38 +0200 Subject: [PATCH 004/132] cli: add __non_webpack_require__ to backend eslint globals --- packages/cli/config/eslint.backend.js | 3 +++ 1 file changed, 3 insertions(+) diff --git a/packages/cli/config/eslint.backend.js b/packages/cli/config/eslint.backend.js index 8389f5c508..9b9efc7649 100644 --- a/packages/cli/config/eslint.backend.js +++ b/packages/cli/config/eslint.backend.js @@ -28,6 +28,9 @@ module.exports = { env: { jest: true, }, + globals: { + __non_webpack_require__: 'readonly', + }, parserOptions: { ecmaVersion: 2018, sourceType: 'module', From 9ae483312ee97f14f757fca1a91928ccd4ef2b7d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sat, 15 Aug 2020 17:53:34 +0200 Subject: [PATCH 005/132] plugins: add initial app-backend --- plugins/app-backend/.eslintrc.js | 3 + plugins/app-backend/README.md | 3 + plugins/app-backend/package.json | 41 ++++++++ plugins/app-backend/scripts/mock-data.sh | 2 + plugins/app-backend/src/index.ts | 17 ++++ plugins/app-backend/src/run.ts | 33 +++++++ .../service/__fixtures__/app-dir/.gitignore | 1 + .../__fixtures__/app-dir/dist/index.html | 1 + .../__fixtures__/app-dir/dist/other.html | 1 + .../__fixtures__/app-dir/dist/static/main.txt | 1 + .../app-backend/src/service/router.test.ts | 93 +++++++++++++++++++ plugins/app-backend/src/service/router.ts | 57 ++++++++++++ .../src/service/standaloneServer.ts | 46 +++++++++ plugins/app-backend/src/setupTests.ts | 17 ++++ 14 files changed, 316 insertions(+) create mode 100644 plugins/app-backend/.eslintrc.js create mode 100644 plugins/app-backend/README.md create mode 100644 plugins/app-backend/package.json create mode 100755 plugins/app-backend/scripts/mock-data.sh create mode 100644 plugins/app-backend/src/index.ts create mode 100644 plugins/app-backend/src/run.ts create mode 100644 plugins/app-backend/src/service/__fixtures__/app-dir/.gitignore create mode 100644 plugins/app-backend/src/service/__fixtures__/app-dir/dist/index.html create mode 100644 plugins/app-backend/src/service/__fixtures__/app-dir/dist/other.html create mode 100644 plugins/app-backend/src/service/__fixtures__/app-dir/dist/static/main.txt create mode 100644 plugins/app-backend/src/service/router.test.ts create mode 100644 plugins/app-backend/src/service/router.ts create mode 100644 plugins/app-backend/src/service/standaloneServer.ts create mode 100644 plugins/app-backend/src/setupTests.ts diff --git a/plugins/app-backend/.eslintrc.js b/plugins/app-backend/.eslintrc.js new file mode 100644 index 0000000000..16a033dbc6 --- /dev/null +++ b/plugins/app-backend/.eslintrc.js @@ -0,0 +1,3 @@ +module.exports = { + extends: [require.resolve('@backstage/cli/config/eslint.backend')], +}; diff --git a/plugins/app-backend/README.md b/plugins/app-backend/README.md new file mode 100644 index 0000000000..28480b81a1 --- /dev/null +++ b/plugins/app-backend/README.md @@ -0,0 +1,3 @@ +# App backend plugin + +This backend plugin can be installed to serve static content of a Backstage app. diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json new file mode 100644 index 0000000000..a26667ef7c --- /dev/null +++ b/plugins/app-backend/package.json @@ -0,0 +1,41 @@ +{ + "name": "@backstage/plugin-app-backend", + "version": "0.1.1-alpha.18", + "main": "src/index.ts", + "types": "src/index.ts", + "license": "Apache-2.0", + "private": true, + "publishConfig": { + "access": "public", + "main": "dist/index.cjs.js", + "types": "dist/index.d.ts" + }, + "scripts": { + "start": "backstage-cli backend:dev", + "build": "backstage-cli backend:build", + "lint": "backstage-cli lint", + "test": "backstage-cli test", + "prepack": "backstage-cli prepack", + "postpack": "backstage-cli postpack", + "clean": "backstage-cli clean", + "mock-data": "./scripts/mock-data.sh" + }, + "dependencies": { + "@backstage/backend-common": "^0.1.1-alpha.18", + "@types/express": "^4.17.6", + "express": "^4.17.1", + "express-promise-router": "^3.0.3", + "winston": "^3.2.1", + "yn": "^4.0.0" + }, + "devDependencies": { + "@backstage/cli": "^0.1.1-alpha.18", + "@types/supertest": "^2.0.8", + "msw": "^0.19.5", + "supertest": "^4.0.2" + }, + "files": [ + "dist", + "static" + ] +} diff --git a/plugins/app-backend/scripts/mock-data.sh b/plugins/app-backend/scripts/mock-data.sh new file mode 100755 index 0000000000..ff30921715 --- /dev/null +++ b/plugins/app-backend/scripts/mock-data.sh @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +echo "use this script to load your service with some mock data if needed!" diff --git a/plugins/app-backend/src/index.ts b/plugins/app-backend/src/index.ts new file mode 100644 index 0000000000..7612c392a2 --- /dev/null +++ b/plugins/app-backend/src/index.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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. + */ + +export * from './service/router'; diff --git a/plugins/app-backend/src/run.ts b/plugins/app-backend/src/run.ts new file mode 100644 index 0000000000..b96989e4b8 --- /dev/null +++ b/plugins/app-backend/src/run.ts @@ -0,0 +1,33 @@ +/* + * Copyright 2020 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 { getRootLogger } from '@backstage/backend-common'; +import yn from 'yn'; +import { startStandaloneServer } from './service/standaloneServer'; + +const port = process.env.PLUGIN_PORT ? Number(process.env.PLUGIN_PORT) : 7000; +const enableCors = yn(process.env.PLUGIN_CORS, { default: false }); +const logger = getRootLogger(); + +startStandaloneServer({ port, enableCors, logger }).catch(err => { + logger.error(err); + process.exit(1); +}); + +process.on('SIGINT', () => { + logger.info('CTRL+C pressed; exiting.'); + process.exit(0); +}); diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/.gitignore b/plugins/app-backend/src/service/__fixtures__/app-dir/.gitignore new file mode 100644 index 0000000000..cbdb9611d1 --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/.gitignore @@ -0,0 +1 @@ +!dist diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/index.html b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/index.html new file mode 100644 index 0000000000..2085b25414 --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/index.html @@ -0,0 +1 @@ +this is index.html diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/other.html b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/other.html new file mode 100644 index 0000000000..a4ea9022ac --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/other.html @@ -0,0 +1 @@ +this is other.html diff --git a/plugins/app-backend/src/service/__fixtures__/app-dir/dist/static/main.txt b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/static/main.txt new file mode 100644 index 0000000000..ec835599f6 --- /dev/null +++ b/plugins/app-backend/src/service/__fixtures__/app-dir/dist/static/main.txt @@ -0,0 +1 @@ +this is main.txt diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts new file mode 100644 index 0000000000..8910129394 --- /dev/null +++ b/plugins/app-backend/src/service/router.test.ts @@ -0,0 +1,93 @@ +/* + * Copyright 2020 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 { resolve as resolvePath } from 'path'; +import { getVoidLogger } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import request from 'supertest'; + +import { createRouter } from './router'; + +global.__non_webpack_require__ = { + resolve: () => resolvePath(__dirname, '__fixtures__/app-dir/package.json'), +}; + +describe('createRouter', () => { + let app: express.Express; + + beforeAll(async () => { + const router = await createRouter({ + logger: getVoidLogger(), + appPackageName: 'example-app', + }); + app = express().use(router); + }); + + beforeEach(() => { + jest.resetAllMocks(); + }); + + it('returns index.html', async () => { + const response = await request(app).get('/index.html'); + + expect(response.status).toBe(200); + expect(response.text).toBe('this is index.html\n'); + }); + + it('returns other.html', async () => { + const response = await request(app).get('/other.html'); + + expect(response.status).toBe(200); + expect(response.text).toBe('this is other.html\n'); + }); + + it('returns index.html if missing', async () => { + const response = await request(app).get('/missing.html'); + + expect(response.status).toBe(200); + expect(response.text).toBe('this is index.html\n'); + }); +}); + +describe('createRouter with static fallback handler', () => { + it('uses static fallback handler', async () => { + const staticFallbackHandler = Router(); + + staticFallbackHandler.get('/test.txt', (_req, res) => { + res.end('this is test.txt'); + }); + + const router = await createRouter({ + logger: getVoidLogger(), + appPackageName: 'example-app', + staticFallbackHandler, + }); + + const app = express().use(router); + + const response1 = await request(app).get('/static/main.txt'); + expect(response1.status).toBe(200); + expect(response1.text).toBe('this is main.txt\n'); + + const response2 = await request(app).get('/static/test.txt'); + expect(response2.status).toBe(200); + expect(response2.text).toBe('this is test.txt'); + + const response3 = await request(app).get('/static/missing.txt'); + expect(response3.status).toBe(404); + }); +}); diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts new file mode 100644 index 0000000000..efb6d4718e --- /dev/null +++ b/plugins/app-backend/src/service/router.ts @@ -0,0 +1,57 @@ +/* + * Copyright 2020 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 { resolve as resolvePath, dirname } from 'path'; +import { notFoundHandler } from '@backstage/backend-common'; +import express from 'express'; +import Router from 'express-promise-router'; +import { Logger } from 'winston'; + +export interface RouterOptions { + logger: Logger; + appPackageName?: string; + staticFallbackHandler?: express.Handler; +} + +export async function createRouter( + options: RouterOptions, +): Promise { + const appDistDir = resolvePath( + dirname( + __non_webpack_require__.resolve(`${options.appPackageName}/package.json`), + ), + 'dist', + ); + options.logger.info(`Serving static app content from ${appDistDir}`); + + const router = Router(); + + // Use a separate router for static content so that a fallback can be provided by backend + const staticRouter = Router(); + staticRouter.use(express.static(resolvePath(appDistDir, 'static'))); + if (options.staticFallbackHandler) { + staticRouter.use(options.staticFallbackHandler); + } + staticRouter.use(notFoundHandler()); + + router.use('/static', staticRouter); + router.use(express.static(appDistDir)); + router.get('/*', (_req, res) => { + res.sendFile(resolvePath(appDistDir, 'index.html')); + }); + + return router; +} diff --git a/plugins/app-backend/src/service/standaloneServer.ts b/plugins/app-backend/src/service/standaloneServer.ts new file mode 100644 index 0000000000..8abf3b81f2 --- /dev/null +++ b/plugins/app-backend/src/service/standaloneServer.ts @@ -0,0 +1,46 @@ +/* + * Copyright 2020 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 { createServiceBuilder } from '@backstage/backend-common'; +import { Server } from 'http'; +import { Logger } from 'winston'; +import { createRouter } from './router'; + +export interface ServerOptions { + port: number; + enableCors: boolean; + logger: Logger; +} + +export async function startStandaloneServer( + options: ServerOptions, +): Promise { + const logger = options.logger.child({ service: 'app-backend' }); + logger.debug('Starting application server...'); + const router = await createRouter({ + logger, + appPackageName: 'example-app', + }); + + const service = createServiceBuilder(module).addRouter('', router); + + return await service.start().catch(err => { + logger.error(err); + process.exit(1); + }); +} + +module.hot?.accept(); diff --git a/plugins/app-backend/src/setupTests.ts b/plugins/app-backend/src/setupTests.ts new file mode 100644 index 0000000000..ba33cf996b --- /dev/null +++ b/plugins/app-backend/src/setupTests.ts @@ -0,0 +1,17 @@ +/* + * Copyright 2020 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. + */ + +export {}; From c13de0b115907fff19753f2c439de136b9c16f46 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Sun, 16 Aug 2020 09:10:09 +0200 Subject: [PATCH 006/132] config-loader: rename readEnv to readEnvConfig and export from package --- packages/config-loader/src/index.ts | 1 + packages/config-loader/src/lib/env.test.ts | 42 +++++++++++----------- packages/config-loader/src/lib/env.ts | 2 +- packages/config-loader/src/lib/index.ts | 2 +- packages/config-loader/src/loader.ts | 4 +-- 5 files changed, 26 insertions(+), 25 deletions(-) diff --git a/packages/config-loader/src/index.ts b/packages/config-loader/src/index.ts index 6738611a17..02db134fe5 100644 --- a/packages/config-loader/src/index.ts +++ b/packages/config-loader/src/index.ts @@ -14,5 +14,6 @@ * limitations under the License. */ +export { readEnvConfig } from './lib'; export { loadConfig } from './loader'; export type { LoadConfigOptions } from './loader'; diff --git a/packages/config-loader/src/lib/env.test.ts b/packages/config-loader/src/lib/env.test.ts index 93bd962596..6b1e49365a 100644 --- a/packages/config-loader/src/lib/env.test.ts +++ b/packages/config-loader/src/lib/env.test.ts @@ -14,16 +14,16 @@ * limitations under the License. */ -import { readEnv } from './env'; +import { readEnvConfig } from './env'; -describe('readEnv', () => { +describe('readEnvConfig', () => { it('should return empty config for empty env', () => { - expect(readEnv({})).toEqual([]); + expect(readEnvConfig({})).toEqual([]); }); it('should return empty config for no matching keys', () => { expect( - readEnv({ + readEnvConfig({ NODE_ENV: 'production', NOPE_ENV: 'development', APP_CONFIG: 'foo', @@ -34,7 +34,7 @@ describe('readEnv', () => { it('should create config from env', () => { expect( - readEnv({ + readEnvConfig({ NODE_ENV: 'production', APP_CONFIG_foo: '"bar"', APP_CONFIG_numbers_a: '1', @@ -57,22 +57,22 @@ describe('readEnv', () => { }); it('should accept string values', () => { - expect(readEnv({ APP_CONFIG_foo: '"abc"', APP_CONFIG_bar: 'xyz' })).toEqual( - [ - { - data: { - foo: 'abc', - bar: 'xyz', - }, - context: 'env', + expect( + readEnvConfig({ APP_CONFIG_foo: '"abc"', APP_CONFIG_bar: 'xyz' }), + ).toEqual([ + { + data: { + foo: 'abc', + bar: 'xyz', }, - ], - ); + context: 'env', + }, + ]); }); it('should accept complex objects', () => { expect( - readEnv({ + readEnvConfig({ APP_CONFIG_foo: '{ "a": 123, "b": "123", "c": [] }', APP_CONFIG_bar: '[123, "abc", {}]', }), @@ -95,7 +95,7 @@ describe('readEnv', () => { ['APP_CONFIG_fo o'], ['APP_CONFIG_foo_(foo)_foo'], ])('should reject invalid key %p', key => { - expect(() => readEnv({ [key]: '0' })).toThrow( + expect(() => readEnvConfig({ [key]: '0' })).toThrow( `Invalid env config key '${key.replace('APP_CONFIG_', '')}'`, ); }); @@ -103,7 +103,7 @@ describe('readEnv', () => { it.each([['hello'], ['"hello'], ['{'], ['}']])( 'should fallback to string when invalid json value %p', value => { - expect(readEnv({ APP_CONFIG_foo: value })).toEqual([ + expect(readEnvConfig({ APP_CONFIG_foo: value })).toEqual([ { data: { foo: value, @@ -116,7 +116,7 @@ describe('readEnv', () => { it('should not allow null as a value', () => { expect(() => - readEnv({ + readEnvConfig({ APP_CONFIG_foo: 'null', }), ).toThrow( @@ -126,7 +126,7 @@ describe('readEnv', () => { it('should not allow duplicate values', () => { expect(() => - readEnv({ + readEnvConfig({ APP_CONFIG_foo_bar: '1', APP_CONFIG_foo_bar_baz: '2', }), @@ -137,7 +137,7 @@ describe('readEnv', () => { it('should not allow mixing of objects and other values', () => { expect(() => - readEnv({ + readEnvConfig({ APP_CONFIG_nested_foo: '1', APP_CONFIG_nested: '2', }), diff --git a/packages/config-loader/src/lib/env.ts b/packages/config-loader/src/lib/env.ts index 53c6d6c63b..84d39263e3 100644 --- a/packages/config-loader/src/lib/env.ts +++ b/packages/config-loader/src/lib/env.ts @@ -39,7 +39,7 @@ const CONFIG_KEY_PART_PATTERN = /^[a-z][a-z0-9]*(?:[-_][a-z][a-z0-9]*)*$/i; * * APP_CONFIG_app_title='"My Title"' */ -export function readEnv(env: { +export function readEnvConfig(env: { [name: string]: string | undefined; }): AppConfig[] { let data: JsonObject | undefined = undefined; diff --git a/packages/config-loader/src/lib/index.ts b/packages/config-loader/src/lib/index.ts index 226932784b..40252b9cec 100644 --- a/packages/config-loader/src/lib/index.ts +++ b/packages/config-loader/src/lib/index.ts @@ -16,5 +16,5 @@ export { resolveStaticConfig } from './resolver'; export { readConfigFile } from './reader'; -export { readEnv } from './env'; +export { readEnvConfig } from './env'; export { readSecret } from './secrets'; diff --git a/packages/config-loader/src/loader.ts b/packages/config-loader/src/loader.ts index 679ab71e45..cf5edd3ce3 100644 --- a/packages/config-loader/src/loader.ts +++ b/packages/config-loader/src/loader.ts @@ -20,7 +20,7 @@ import { AppConfig, JsonObject } from '@backstage/config'; import { resolveStaticConfig, readConfigFile, - readEnv, + readEnvConfig, readSecret, } from './lib'; @@ -102,7 +102,7 @@ export async function loadConfig( ); } - configs.push(...readEnv(process.env)); + configs.push(...readEnvConfig(process.env)); return configs; } From 35e1af6efaf31811d140f4c4eb01896d94a9063d Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Aug 2020 11:51:13 +0200 Subject: [PATCH 007/132] plugins/app-backend: add env config injection --- plugins/app-backend/package.json | 2 + plugins/app-backend/src/lib/config.test.ts | 136 ++++++++++++++++++ plugins/app-backend/src/lib/config.ts | 74 ++++++++++ .../app-backend/src/service/router.test.ts | 2 + plugins/app-backend/src/service/router.ts | 7 + 5 files changed, 221 insertions(+) create mode 100644 plugins/app-backend/src/lib/config.test.ts create mode 100644 plugins/app-backend/src/lib/config.ts diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index a26667ef7c..29902e9806 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -22,9 +22,11 @@ }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.18", + "@backstage/config-loader": "^0.1.1-alpha.18", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", + "fs-extra": "^9.0.0", "winston": "^3.2.1", "yn": "^4.0.0" }, diff --git a/plugins/app-backend/src/lib/config.test.ts b/plugins/app-backend/src/lib/config.test.ts new file mode 100644 index 0000000000..16c5600b1d --- /dev/null +++ b/plugins/app-backend/src/lib/config.test.ts @@ -0,0 +1,136 @@ +/* + * Copyright 2020 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 { resolve as resolvePath } from 'path'; +import { getVoidLogger } from '@backstage/backend-common'; +import { injectEnvConfig } from './config'; + +jest.mock('fs-extra'); + +const fsMock = fs as jest.Mocked; +const readFileMock = (fsMock.readFile as unknown) as jest.MockedFunction< + (name: string) => Promise +>; + +const MOCK_DIR = 'mock-dir'; + +const baseOptions = { + env: {}, + staticDir: MOCK_DIR, + logger: getVoidLogger(), +}; + +describe('injectEnvConfig', () => { + beforeEach(() => { + fsMock.readdir.mockResolvedValue(['main.js']); + }); + + afterEach(() => { + jest.resetAllMocks(); + }); + + it('should not inject without config', async () => { + await injectEnvConfig(baseOptions); + expect(fsMock.readdir).toHaveBeenCalledTimes(0); + expect(fsMock.readFile).toHaveBeenCalledTimes(0); + expect(fsMock.writeFile).toHaveBeenCalledTimes(0); + }); + + it('should find the correct file to inject', async () => { + fsMock.readdir.mockResolvedValue([ + 'before.js', + 'not-js.txt', + 'main.js', + 'after.js', + ]); + readFileMock.mockImplementation(async (file: string) => { + if (file.endsWith('main.js')) { + return '"__APP_INJECTED_RUNTIME_CONFIG__"'; + } + return 'NO_PLACEHOLDER_HERE'; + }); + + await injectEnvConfig({ ...baseOptions, env: { APP_CONFIG_x: '0' } }); + expect(fsMock.readFile).toHaveBeenCalledTimes(2); + expect(fsMock.readFile).toHaveBeenNthCalledWith( + 1, + resolvePath(MOCK_DIR, 'before.js'), + 'utf8', + ); + expect(fsMock.readFile).toHaveBeenNthCalledWith( + 2, + resolvePath(MOCK_DIR, 'main.js'), + 'utf8', + ); + + expect(fsMock.writeFile).toHaveBeenCalledTimes(1); + expect(fsMock.writeFile).toHaveBeenCalledWith( + resolvePath(MOCK_DIR, 'main.js'), + '/*__APP_INJECTED_CONFIG_MARKER__*/"{\\"x\\":0}"/*__INJECTED_END__*/', + 'utf8', + ); + + // eslint-disable-next-line no-eval + expect(JSON.parse(eval(fsMock.writeFile.mock.calls[0][1]))).toEqual({ + x: 0, + }); + }); + + it('should re-inject config', async () => { + fsMock.readdir.mockResolvedValue(['main.js']); + readFileMock.mockResolvedValue( + 'JSON.parse("__APP_INJECTED_RUNTIME_CONFIG__")', + ); + + await injectEnvConfig({ + ...baseOptions, + env: { + APP_CONFIG_x: '0', + }, + }); + + expect(fsMock.writeFile).toHaveBeenCalledTimes(1); + expect(fsMock.writeFile).toHaveBeenCalledWith( + resolvePath(MOCK_DIR, 'main.js'), + 'JSON.parse(/*__APP_INJECTED_CONFIG_MARKER__*/"{\\"x\\":0}"/*__INJECTED_END__*/)', + 'utf8', + ); + + // eslint-disable-next-line no-eval + expect(eval(fsMock.writeFile.mock.calls[0][1])).toEqual({ x: 0 }); + + readFileMock.mockResolvedValue(fsMock.writeFile.mock.calls[0][1]); + + await injectEnvConfig({ + ...baseOptions, + env: { + APP_CONFIG_x: '1', + APP_CONFIG_y: '2', + }, + }); + + expect(fsMock.writeFile).toHaveBeenCalledTimes(2); + expect(fsMock.writeFile).toHaveBeenLastCalledWith( + resolvePath(MOCK_DIR, 'main.js'), + 'JSON.parse(/*__APP_INJECTED_CONFIG_MARKER__*/"{\\"x\\":1,\\"y\\":2}"/*__INJECTED_END__*/)', + 'utf8', + ); + + // eslint-disable-next-line no-eval + expect(eval(fsMock.writeFile.mock.calls[1][1])).toEqual({ x: 1, y: 2 }); + }); +}); diff --git a/plugins/app-backend/src/lib/config.ts b/plugins/app-backend/src/lib/config.ts new file mode 100644 index 0000000000..0ed56ef722 --- /dev/null +++ b/plugins/app-backend/src/lib/config.ts @@ -0,0 +1,74 @@ +/* + * Copyright 2020 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 { resolve as resolvePath } from 'path'; +import { readEnvConfig } from '@backstage/config-loader'; +import { Logger } from 'winston'; + +type Options = { + // Environment to read config from + env: { [name: string]: string | undefined }; + // Directory of the static JS files to search for file to inject + staticDir: string; + logger: Logger; +}; + +/** + * Injects config from APP_CONFIG_ env vars, replacing existing + * injected config if it has already been injected. + */ +export async function injectEnvConfig(options: Options) { + const { env, staticDir, logger } = options; + + const envConfig = readEnvConfig(env); + if (envConfig.length === 0) { + return; + } + + const files = await fs.readdir(staticDir); + const jsFiles = files.filter(file => file.endsWith('.js')); + + const [{ data }] = envConfig; + const escapedData = JSON.stringify(data).replace(/("|'|\\)/g, '\\$1'); + const injected = `/*__APP_INJECTED_CONFIG_MARKER__*/"${escapedData}"/*__INJECTED_END__*/`; + + for (const jsFile of jsFiles) { + const path = resolvePath(staticDir, jsFile); + + const content = await fs.readFile(path, 'utf8'); + if (content.includes('__APP_INJECTED_RUNTIME_CONFIG__')) { + logger.info(`Injecting env config into ${jsFile}`); + + const newContent = content.replace( + '"__APP_INJECTED_RUNTIME_CONFIG__"', + injected, + ); + await fs.writeFile(path, newContent, 'utf8'); + return; + } else if (content.includes('__APP_INJECTED_CONFIG_MARKER__')) { + logger.info(`Replacing injected env config in ${jsFile}`); + + const newContent = content.replace( + /\/\*__APP_INJECTED_CONFIG_MARKER__\*\/.*\/\*__INJECTED_END__\*\//, + injected, + ); + await fs.writeFile(path, newContent, 'utf8'); + return; + } + } + logger.info('Env config not injected'); +} diff --git a/plugins/app-backend/src/service/router.test.ts b/plugins/app-backend/src/service/router.test.ts index 8910129394..4e0c27c47a 100644 --- a/plugins/app-backend/src/service/router.test.ts +++ b/plugins/app-backend/src/service/router.test.ts @@ -22,6 +22,8 @@ import request from 'supertest'; import { createRouter } from './router'; +jest.mock('../lib/config', () => ({ injectEnvConfig: jest.fn() })); + global.__non_webpack_require__ = { resolve: () => resolvePath(__dirname, '__fixtures__/app-dir/package.json'), }; diff --git a/plugins/app-backend/src/service/router.ts b/plugins/app-backend/src/service/router.ts index efb6d4718e..ecc5b4fa4b 100644 --- a/plugins/app-backend/src/service/router.ts +++ b/plugins/app-backend/src/service/router.ts @@ -19,6 +19,7 @@ import { notFoundHandler } from '@backstage/backend-common'; import express from 'express'; import Router from 'express-promise-router'; import { Logger } from 'winston'; +import { injectEnvConfig } from '../lib/config'; export interface RouterOptions { logger: Logger; @@ -37,6 +38,12 @@ export async function createRouter( ); options.logger.info(`Serving static app content from ${appDistDir}`); + await injectEnvConfig({ + env: process.env, + logger: options.logger, + staticDir: resolvePath(appDistDir, 'static'), + }); + const router = Router(); // Use a separate router for static content so that a fallback can be provided by backend From 5cb3cf7b9064e0c9ba23a540f6ed90189c3fde29 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Aug 2020 12:57:16 +0200 Subject: [PATCH 008/132] backend: install app-backend --- packages/backend/package.json | 2 ++ packages/backend/src/index.ts | 5 ++++- packages/backend/src/plugins/app.ts | 25 +++++++++++++++++++++++++ plugins/app-backend/package.json | 8 ++++---- 4 files changed, 35 insertions(+), 5 deletions(-) create mode 100644 packages/backend/src/plugins/app.ts diff --git a/packages/backend/package.json b/packages/backend/package.json index 9c3c5a5f58..7393ede81e 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -18,9 +18,11 @@ "migrate:create": "knex migrate:make -x ts" }, "dependencies": { + "example-app": "^0.1.1-alpha.20", "@backstage/backend-common": "^0.1.1-alpha.20", "@backstage/catalog-model": "^0.1.1-alpha.20", "@backstage/config": "^0.1.1-alpha.20", + "@backstage/plugin-app-backend": "^0.1.1-alpha.20", "@backstage/plugin-auth-backend": "^0.1.1-alpha.20", "@backstage/plugin-catalog-backend": "^0.1.1-alpha.20", "@backstage/plugin-graphql-backend": "^0.1.1-alpha.20", diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 50f59795fc..21c8794b28 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -40,6 +40,7 @@ import sentry from './plugins/sentry'; import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; import graphql from './plugins/graphql'; +import app from './plugins/app'; import { PluginEnvironment } from './types'; function makeCreateEnv(loadedConfigs: AppConfig[]) { @@ -71,6 +72,7 @@ async function main() { const sentryEnv = useHotMemoize(module, () => createEnv('sentry')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); const graphqlEnv = useHotMemoize(module, () => createEnv('graphql')); + const appEnv = useHotMemoize(module, () => createEnv('app')); const service = createServiceBuilder(module) .loadConfig(configReader) @@ -83,7 +85,8 @@ async function main() { .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) .addRouter('/proxy', await proxy(proxyEnv)) - .addRouter('/graphql', await graphql(graphqlEnv)); + .addRouter('/graphql', await graphql(graphqlEnv)) + .addRouter('', await app(appEnv)); await service.start().catch(err => { console.log(err); diff --git a/packages/backend/src/plugins/app.ts b/packages/backend/src/plugins/app.ts new file mode 100644 index 0000000000..c9f7c0622a --- /dev/null +++ b/packages/backend/src/plugins/app.ts @@ -0,0 +1,25 @@ +/* + * Copyright 2020 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 { createRouter } from '@backstage/plugin-app-backend'; +import { PluginEnvironment } from '../types'; + +export default async function createPlugin({ logger }: PluginEnvironment) { + return await createRouter({ + logger, + appPackageName: 'example-app', + }); +} diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 29902e9806..61977200d5 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -1,6 +1,6 @@ { "name": "@backstage/plugin-app-backend", - "version": "0.1.1-alpha.18", + "version": "0.1.1-alpha.20", "main": "src/index.ts", "types": "src/index.ts", "license": "Apache-2.0", @@ -21,8 +21,8 @@ "mock-data": "./scripts/mock-data.sh" }, "dependencies": { - "@backstage/backend-common": "^0.1.1-alpha.18", - "@backstage/config-loader": "^0.1.1-alpha.18", + "@backstage/backend-common": "^0.1.1-alpha.20", + "@backstage/config-loader": "^0.1.1-alpha.20", "@types/express": "^4.17.6", "express": "^4.17.1", "express-promise-router": "^3.0.3", @@ -31,7 +31,7 @@ "yn": "^4.0.0" }, "devDependencies": { - "@backstage/cli": "^0.1.1-alpha.18", + "@backstage/cli": "^0.1.1-alpha.20", "@types/supertest": "^2.0.8", "msw": "^0.19.5", "supertest": "^4.0.2" From 5cc4c55e3899b54dfa263f56c06df9e6d882ed02 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 17 Aug 2020 13:20:25 +0200 Subject: [PATCH 009/132] plugins/app-backend: add installation instructions --- plugins/app-backend/README.md | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/plugins/app-backend/README.md b/plugins/app-backend/README.md index 28480b81a1..5d290093d7 100644 --- a/plugins/app-backend/README.md +++ b/plugins/app-backend/README.md @@ -1,3 +1,32 @@ # App backend plugin This backend plugin can be installed to serve static content of a Backstage app. + +## Installation + +Add both this package and your local frontend app package as dependencies to your backend, for example + +```bash +yarn add @backstage/plugin-app-backend example-app +``` + +By adding the app package as a dependency we ensure that it is build as part of the backend, and that it can be resolved at runtime. + +Now add the plugin router to your app, creating it for example like this: + +```ts +const router = await createRouter({ + logger, + appPackageName: 'example-app', +}); +``` + +And registering it like this: + +```ts +createServiceBuilder(module) + ... + .addRouter('', router); +``` + +Be sure to register the app router last, as it serves content for HTML5-mode navigation, i.e. falling back to serving `index.html` for any route that can't be found. From e405fff068c38788dfebd1e0ef9f41db903574dd Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 27 Aug 2020 07:25:16 +0200 Subject: [PATCH 010/132] plugins/app-backend: remove mock-data script --- plugins/app-backend/package.json | 3 +-- plugins/app-backend/scripts/mock-data.sh | 2 -- 2 files changed, 1 insertion(+), 4 deletions(-) delete mode 100755 plugins/app-backend/scripts/mock-data.sh diff --git a/plugins/app-backend/package.json b/plugins/app-backend/package.json index 61977200d5..46cd8dcf25 100644 --- a/plugins/app-backend/package.json +++ b/plugins/app-backend/package.json @@ -17,8 +17,7 @@ "test": "backstage-cli test", "prepack": "backstage-cli prepack", "postpack": "backstage-cli postpack", - "clean": "backstage-cli clean", - "mock-data": "./scripts/mock-data.sh" + "clean": "backstage-cli clean" }, "dependencies": { "@backstage/backend-common": "^0.1.1-alpha.20", diff --git a/plugins/app-backend/scripts/mock-data.sh b/plugins/app-backend/scripts/mock-data.sh deleted file mode 100755 index ff30921715..0000000000 --- a/plugins/app-backend/scripts/mock-data.sh +++ /dev/null @@ -1,2 +0,0 @@ -#!/usr/bin/env bash -echo "use this script to load your service with some mock data if needed!" From 8c33d30705ca327aa4d9e345d272dea14d2f40e1 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 27 Aug 2020 07:28:27 +0200 Subject: [PATCH 011/132] plugins/app-backend: fix typo --- plugins/app-backend/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/app-backend/README.md b/plugins/app-backend/README.md index 5d290093d7..2dc302accb 100644 --- a/plugins/app-backend/README.md +++ b/plugins/app-backend/README.md @@ -10,7 +10,7 @@ Add both this package and your local frontend app package as dependencies to you yarn add @backstage/plugin-app-backend example-app ``` -By adding the app package as a dependency we ensure that it is build as part of the backend, and that it can be resolved at runtime. +By adding the app package as a dependency we ensure that it is built as part of the backend, and that it can be resolved at runtime. Now add the plugin router to your app, creating it for example like this: From ffa9886051b3bb1aed694531898c8f9227da7ccd Mon Sep 17 00:00:00 2001 From: ebarrios Date: Thu, 27 Aug 2020 18:30:59 +0200 Subject: [PATCH 012/132] Added Widget for listing workflows run to catalog under CI/CD tab --- .../src/components/EntityPage/EntityPage.tsx | 2 ++ .../components/EntityPageCi/EntityPageCi.tsx | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx diff --git a/plugins/catalog/src/components/EntityPage/EntityPage.tsx b/plugins/catalog/src/components/EntityPage/EntityPage.tsx index adbc758067..558f4b6551 100644 --- a/plugins/catalog/src/components/EntityPage/EntityPage.tsx +++ b/plugins/catalog/src/components/EntityPage/EntityPage.tsx @@ -36,6 +36,7 @@ import { catalogApiRef } from '../..'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; import { EntityPageDocs } from '../EntityPageDocs/EntityDocsPage'; import { EntityPageApi } from '../EntityPageApi/EntityPageApi'; +import { EntityPageCi } from '../EntityPageCi/EntityPageCi'; import { EntityPageOverview } from '../EntityPageOverview/EntityPageOverview'; import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; @@ -129,6 +130,7 @@ export const EntityPage: FC<{}> = () => { { id: 'ci', label: 'CI/CD', + content: (e: Entity) => , }, { id: 'tests', diff --git a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx new file mode 100644 index 0000000000..01de6dc9a2 --- /dev/null +++ b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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 { Entity } from '@backstage/catalog-model'; +import { Content } from '@backstage/core'; +import { WidgetList as GithubActionsListWidget } from '@backstage/plugin-github-actions'; +import { Grid } from '@material-ui/core'; +import React, { FC } from 'react'; + +export const EntityPageCi: FC<{ entity: Entity }> = ({ entity }) => { + return ( + + + {entity.metadata?.annotations?.['backstage.io/github-actions-id'] && ( + + + + )} + + + ); +}; From de7c7268b3746e5f6abff90d5e84e1022ac0db85 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Thu, 27 Aug 2020 18:30:59 +0200 Subject: [PATCH 013/132] Added Widget for listing workflows run to catalog under CI/CD tab --- .../src/components/EntityPage/EntityPage.tsx | 2 ++ .../components/EntityPageCi/EntityPageCi.tsx | 35 +++++++++++++++++++ 2 files changed, 37 insertions(+) create mode 100644 plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx diff --git a/plugins/catalog/src/components/EntityPage/EntityPage.tsx b/plugins/catalog/src/components/EntityPage/EntityPage.tsx index adbc758067..558f4b6551 100644 --- a/plugins/catalog/src/components/EntityPage/EntityPage.tsx +++ b/plugins/catalog/src/components/EntityPage/EntityPage.tsx @@ -36,6 +36,7 @@ import { catalogApiRef } from '../..'; import { EntityContextMenu } from '../EntityContextMenu/EntityContextMenu'; import { EntityPageDocs } from '../EntityPageDocs/EntityDocsPage'; import { EntityPageApi } from '../EntityPageApi/EntityPageApi'; +import { EntityPageCi } from '../EntityPageCi/EntityPageCi'; import { EntityPageOverview } from '../EntityPageOverview/EntityPageOverview'; import { FavouriteEntity } from '../FavouriteEntity/FavouriteEntity'; import { UnregisterEntityDialog } from '../UnregisterEntityDialog/UnregisterEntityDialog'; @@ -129,6 +130,7 @@ export const EntityPage: FC<{}> = () => { { id: 'ci', label: 'CI/CD', + content: (e: Entity) => , }, { id: 'tests', diff --git a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx new file mode 100644 index 0000000000..01de6dc9a2 --- /dev/null +++ b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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 { Entity } from '@backstage/catalog-model'; +import { Content } from '@backstage/core'; +import { WidgetList as GithubActionsListWidget } from '@backstage/plugin-github-actions'; +import { Grid } from '@material-ui/core'; +import React, { FC } from 'react'; + +export const EntityPageCi: FC<{ entity: Entity }> = ({ entity }) => { + return ( + + + {entity.metadata?.annotations?.['backstage.io/github-actions-id'] && ( + + + + )} + + + ); +}; From 6d774764cd6e444cfe672af46478ca28781fd92c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Wed, 26 Aug 2020 16:18:21 +0200 Subject: [PATCH 014/132] feat(proxy-backend): more proxy config tweaks and docs --- app-config.yaml | 12 ++--- packages/backend/src/index.ts | 2 +- packages/backend/src/plugins/proxy.ts | 11 +++-- plugins/proxy-backend/README.md | 46 ++++++++++++++++++- .../proxy-backend/src/service/router.test.ts | 1 + plugins/proxy-backend/src/service/router.ts | 42 +++++++++++++++-- .../src/service/standaloneServer.ts | 1 + 7 files changed, 95 insertions(+), 20 deletions(-) diff --git a/app-config.yaml b/app-config.yaml index 0a85198630..2efe6adc32 100644 --- a/app-config.yaml +++ b/app-config.yaml @@ -14,21 +14,15 @@ backend: client: sqlite3 connection: ':memory:' +# See README.md in the proxy-backend plugin for information on the configuration format proxy: - '/circleci/api': - target: 'https://circleci.com/api/v1.1' - changeOrigin: true - pathRewrite: - '^/proxy/circleci/api/': '/' + '/circleci/api': https://circleci.com/api/v1.1 '/jenkins/api': - target: 'http://localhost:8080' - changeOrigin: true + target: http://localhost:8080 headers: Authorization: $secret: env: JENKINS_BASIC_AUTH_HEADER - pathRewrite: - '^/proxy/jenkins/api/': '/' organization: name: Spotify diff --git a/packages/backend/src/index.ts b/packages/backend/src/index.ts index 50f59795fc..91b1af06fb 100644 --- a/packages/backend/src/index.ts +++ b/packages/backend/src/index.ts @@ -82,7 +82,7 @@ async function main() { .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) - .addRouter('/proxy', await proxy(proxyEnv)) + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')) .addRouter('/graphql', await graphql(graphqlEnv)); await service.start().catch(err => { diff --git a/packages/backend/src/plugins/proxy.ts b/packages/backend/src/plugins/proxy.ts index 4964de130e..e96acf69d3 100644 --- a/packages/backend/src/plugins/proxy.ts +++ b/packages/backend/src/plugins/proxy.ts @@ -13,13 +13,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + // @ts-ignore import { createRouter } from '@backstage/plugin-proxy-backend'; import { PluginEnvironment } from '../types'; -export default async function createPlugin({ - logger, - config, -}: PluginEnvironment) { - return await createRouter({ logger, config }); +export default async function createPlugin( + { logger, config }: PluginEnvironment, + pathPrefix: string, +) { + return await createRouter({ logger, config, pathPrefix }); } diff --git a/plugins/proxy-backend/README.md b/plugins/proxy-backend/README.md index d12031f495..baa2709820 100644 --- a/plugins/proxy-backend/README.md +++ b/plugins/proxy-backend/README.md @@ -19,8 +19,8 @@ const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const service = createServiceBuilder(module) .loadConfig(configReader) - /** several different routers */ - .addRouter('/', await proxy(proxyEnv)); + /** ... other routers ... */ + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')); ``` 2. Start the backend @@ -31,6 +31,48 @@ yarn workspace example-backend start This will launch the full example backend. +## Configuration + +Example: + +```yaml +# in app-config.yaml +proxy: + '/simple-example': http://simple.example.com:8080 + '/larger-example/v1': + target: http://larger.example.com:8080/svc.v1 + headers: + Authorization: + $secret: + env: EXAMPLE_AUTH_HEADER +``` + +Each key under the proxy configuration entry is a route to match, below the prefix that the proxy +plugin is mounted on. For example, if the backend mounts the proxy plugin as `/proxy`, +the above configuration will lead to the proxy acting on backend requests to +`/proxy/simple-example/...` and `/proxy/larger-example/v1/...`. + +The value inside each route is either a simple URL string, or an object on the format accepted by +[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). + +If the value is a string, it is assumed to correspond to: + +```yaml +target: +changeOrigin: true +pathRewrite: + '^/': '/' +``` + +When the target is an object, it is given verbatim to `http-proxy-middleware` except with the +following caveats for convenience: + +- If `changeOrigin` is not specified, it is set to `true`. This is the most commonly useful value. +- If `pathRewrite` is not specified, it is set to a single rewrite that removes the entire route. In the + above example, a rewrite of `'^/proxy/larger-example/v1/': '/'` is added. That means that a request to + `/proxy/larger-example/v1/some/path` will be translated to a request to + `http://larger.example.com:8080/svc.v1/some/path`. + ## Links - [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware) diff --git a/plugins/proxy-backend/src/service/router.test.ts b/plugins/proxy-backend/src/service/router.test.ts index 085647bc4e..c7d7fff1d4 100644 --- a/plugins/proxy-backend/src/service/router.test.ts +++ b/plugins/proxy-backend/src/service/router.test.ts @@ -26,6 +26,7 @@ describe('createRouter', () => { const router = await createRouter({ config, logger, + pathPrefix: '/proxy', }); expect(router).toBeDefined(); }); diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index b71299ef7e..5273b2c806 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -17,21 +17,57 @@ import { Config } from '@backstage/config'; import express from 'express'; import Router from 'express-promise-router'; -import createProxyMiddleware from 'http-proxy-middleware'; +import createProxyMiddleware, { + Config as ProxyConfig, + Proxy, +} from 'http-proxy-middleware'; import { Logger } from 'winston'; export interface RouterOptions { logger: Logger; config: Config; + // The URL path prefix that the router itself is mounted as, commonly "/proxy" + pathPrefix: string; +} + +// Creates a proxy middleware, possibly with defaults added on top of the +// given config. +function buildMiddleware( + pathPrefix: string, + route: string, + config: string | ProxyConfig, +): Proxy { + const fullConfig = + typeof config === 'string' ? { target: config } : { ...config }; + + // Default is to do a path rewrite that strips out the proxy's path prefix + // and the rest of the route. + if (fullConfig.pathRewrite === undefined) { + const routeWithSlash = route.endsWith('/') ? route : `${route}/`; + fullConfig.pathRewrite = { + [`^${pathPrefix}${routeWithSlash}`]: '/', + }; + } + + // Default is to update the Host header to the target + if (fullConfig.changeOrigin === undefined) { + fullConfig.changeOrigin = true; + } + + return createProxyMiddleware(fullConfig); } export async function createRouter( options: RouterOptions, ): Promise { const router = Router(); - const proxyConfig = options.config.get('proxy') ?? {}; + + const proxyConfig = options.config.getOptional('proxy') ?? {}; Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { - router.use(route, createProxyMiddleware(proxyRouteConfig)); + router.use( + route, + buildMiddleware(options.pathPrefix, route, proxyRouteConfig), + ); }); return router; diff --git a/plugins/proxy-backend/src/service/standaloneServer.ts b/plugins/proxy-backend/src/service/standaloneServer.ts index f75c044f0f..68e9ade183 100644 --- a/plugins/proxy-backend/src/service/standaloneServer.ts +++ b/plugins/proxy-backend/src/service/standaloneServer.ts @@ -40,6 +40,7 @@ export async function startStandaloneServer( const router = await createRouter({ config, logger, + pathPrefix: '/proxy', }); const service = createServiceBuilder(module) .enableCors({ origin: 'http://localhost:3000' }) From b2f62347e8ebb80cee183931de34cedc04d4b65e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 27 Aug 2020 09:26:06 +0200 Subject: [PATCH 015/132] fix tests --- .../default-app/packages/backend/src/index.ts | 2 +- .../default-app/packages/backend/src/plugins/proxy.ts | 10 +++++----- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 0e1a85f0a3..d6fba2478a 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -55,7 +55,7 @@ async function main() { .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) - .addRouter('/proxy', await proxy(proxyEnv)); + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')); await service.start().catch(err => { console.log(err); diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/proxy.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/proxy.ts index 574d0c17a7..b20265c2cd 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/proxy.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/plugins/proxy.ts @@ -2,9 +2,9 @@ import { createRouter } from '@backstage/plugin-proxy-backend'; import { PluginEnvironment } from '../types'; -export default async function createPlugin({ - logger, - config, -}: PluginEnvironment) { - return await createRouter({ logger, config }); +export default async function createPlugin( + { logger, config }: PluginEnvironment, + pathPrefix: string, +) { + return await createRouter({ logger, config, pathPrefix }); } From 9a64135bf7d66642da35b574070828e2d7d0c0a1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Thu, 27 Aug 2020 12:06:53 +0200 Subject: [PATCH 016/132] move docs to the microsite --- docs/plugins/call-existing-api.md | 11 ++--- docs/plugins/proxying.md | 71 ++++++++++++++++++++++++++++++- plugins/proxy-backend/README.md | 66 +++++----------------------- 3 files changed, 83 insertions(+), 65 deletions(-) diff --git a/docs/plugins/call-existing-api.md b/docs/plugins/call-existing-api.md index f6c9990b90..9658176224 100644 --- a/docs/plugins/call-existing-api.md +++ b/docs/plugins/call-existing-api.md @@ -71,11 +71,7 @@ Example: ```yaml # In app-config.yaml proxy: - '/frobs': - target: 'http://api.frobsco.com/v1' - changeOrigin: true - pathRewrite: - '^/proxy/frobs/': '/' + '/frobs': http://api.frobsco.com/v1 ``` ```ts @@ -86,9 +82,8 @@ fetch(`${backendUrl}/proxy/frobs/list`) .then(payload => setFrobs(payload as Frob[])); ``` -The proxy is powered by the `http-proxy-middleware` package, and supports all of -its -[configuration options](https://github.com/chimurai/http-proxy-middleware#options). +The proxy is powered by the `http-proxy-middleware` package. See +[Proxying](proxying.md) for a full description of its configuration options. Internally at Spotify, the proxy option has been the overwhelmingly most popular choice for plugin makers. Since we have DNS based service discovery in place and diff --git a/docs/plugins/proxying.md b/docs/plugins/proxying.md index 4cf5b4c025..658df3dda1 100644 --- a/docs/plugins/proxying.md +++ b/docs/plugins/proxying.md @@ -3,4 +3,73 @@ id: proxying title: Proxying --- -## TODO +## Overview + +The Backstage backend comes packaged with a basic HTTP proxy, that can aid in +reaching backend service APIs from frontend plugin code. See +[Call Existing API](call-existing-api.md) for a description of when the proxy +can be the best choice for communicating with an API. + +## Getting Started + +The plugin is already added to a default Backstage project. + +In `packages/backend/src/index.ts`: + +```ts +const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); + +const service = createServiceBuilder(module) + .loadConfig(configReader) + /** ... other routers ... */ + .addRouter('/proxy', await proxy(proxyEnv, '/proxy')); +``` + +## Configuration + +Configuration for the proxy plugin lives under a `proxy` root key of your +`app-config.yaml` file. + +Example: + +```yaml +# in app-config.yaml +proxy: + '/simple-example': http://simple.example.com:8080 + '/larger-example/v1': + target: http://larger.example.com:8080/svc.v1 + headers: + Authorization: + $secret: + env: EXAMPLE_AUTH_HEADER +``` + +Each key under the proxy configuration entry is a route to match, below the +prefix that the proxy plugin is mounted on. It must start with a slash. For +example, if the backend mounts the proxy plugin as `/proxy`, the above +configuration will lead to the proxy acting on backend requests to +`/proxy/simple-example/...` and `/proxy/larger-example/v1/...`. + +The value inside each route is either a simple URL string, or an object on the +format accepted by +[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). + +If the value is a string, it is assumed to correspond to: + +```yaml +target: +changeOrigin: true +pathRewrite: + '^/': '/' +``` + +When the target is an object, it is given verbatim to `http-proxy-middleware` +except with the following caveats for convenience: + +- If `changeOrigin` is not specified, it is set to `true`. This is the most + commonly useful value. +- If `pathRewrite` is not specified, it is set to a single rewrite that removes + the entire prefix and route. In the above example, a rewrite of + `'^/proxy/larger-example/v1/': '/'` is added. That means that a request to + `/proxy/larger-example/v1/some/path` will be translated to a request to + `http://larger.example.com:8080/svc.v1/some/path`. diff --git a/plugins/proxy-backend/README.md b/plugins/proxy-backend/README.md index baa2709820..f67449dda3 100644 --- a/plugins/proxy-backend/README.md +++ b/plugins/proxy-backend/README.md @@ -1,6 +1,7 @@ # Proxy backend plugin -This is the backend plugin that enables proxy definitions to be declared in and read from app-config.yaml. +This is the backend plugin that enables proxy definitions to be declared in, +and read from, `app-config.yaml`. Relies on the `http-proxy-middleware` package. @@ -10,69 +11,22 @@ This backend plugin can be started in a standalone mode from directly in this pa with `yarn start`. However, it will have limited functionality and that process is most convenient when developing the plugin itself. -To run it within the backend do: - -1. Register the router in `packages/backend/src/index.ts`: - -```ts -const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); - -const service = createServiceBuilder(module) - .loadConfig(configReader) - /** ... other routers ... */ - .addRouter('/proxy', await proxy(proxyEnv, '/proxy')); -``` - -2. Start the backend +The proxy is already installed in the Backstage backend per default, so you can also +start up the full example backend to experiment with the proxy. ```bash yarn workspace example-backend start ``` -This will launch the full example backend. - ## Configuration -Example: - -```yaml -# in app-config.yaml -proxy: - '/simple-example': http://simple.example.com:8080 - '/larger-example/v1': - target: http://larger.example.com:8080/svc.v1 - headers: - Authorization: - $secret: - env: EXAMPLE_AUTH_HEADER -``` - -Each key under the proxy configuration entry is a route to match, below the prefix that the proxy -plugin is mounted on. For example, if the backend mounts the proxy plugin as `/proxy`, -the above configuration will lead to the proxy acting on backend requests to -`/proxy/simple-example/...` and `/proxy/larger-example/v1/...`. - -The value inside each route is either a simple URL string, or an object on the format accepted by -[http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware). - -If the value is a string, it is assumed to correspond to: - -```yaml -target: -changeOrigin: true -pathRewrite: - '^/': '/' -``` - -When the target is an object, it is given verbatim to `http-proxy-middleware` except with the -following caveats for convenience: - -- If `changeOrigin` is not specified, it is set to `true`. This is the most commonly useful value. -- If `pathRewrite` is not specified, it is set to a single rewrite that removes the entire route. In the - above example, a rewrite of `'^/proxy/larger-example/v1/': '/'` is added. That means that a request to - `/proxy/larger-example/v1/some/path` will be translated to a request to - `http://larger.example.com:8080/svc.v1/some/path`. +See [the proxy docs](https://backstage.io/docs/plugins/proxying). ## Links +- [Call Existing API](https://backstage.io/docs/plugins/call-existing-api) helps the + decision process of what method of communication to use from a frontend plugin to + your API +- [The proxy plugin documentation](https://backstage.io/docs/plugins/proxying) describes + configuration options and more - [http-proxy-middleware](https://www.npmjs.com/package/http-proxy-middleware) From 86aff7a6cfd87bd8c8ef5b46d7fac57007a13fc0 Mon Sep 17 00:00:00 2001 From: ebarrios Date: Fri, 28 Aug 2020 14:39:36 +0200 Subject: [PATCH 017/132] Added github as CI/CD in catalog plugin --- plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx | 2 +- plugins/github-actions/src/index.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx index 01de6dc9a2..d39617da25 100644 --- a/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx +++ b/plugins/catalog/src/components/EntityPageCi/EntityPageCi.tsx @@ -16,7 +16,7 @@ import { Entity } from '@backstage/catalog-model'; import { Content } from '@backstage/core'; -import { WidgetList as GithubActionsListWidget } from '@backstage/plugin-github-actions'; +import { RecentWorkflowRunsCard as GithubActionsListWidget } from '@backstage/plugin-github-actions'; import { Grid } from '@material-ui/core'; import React, { FC } from 'react'; diff --git a/plugins/github-actions/src/index.ts b/plugins/github-actions/src/index.ts index 4a69c363cd..4e383fdd3d 100644 --- a/plugins/github-actions/src/index.ts +++ b/plugins/github-actions/src/index.ts @@ -16,4 +16,4 @@ export { plugin } from './plugin'; export * from './api'; -export { Widget } from './components/Widget'; +export { Widget, RecentWorkflowRunsCard } from './components/Widget'; From 63339d2692b550035fa26b7e680a03d66987acc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Fri, 28 Aug 2020 14:50:41 +0200 Subject: [PATCH 018/132] chore(register-component): just a few little tweaks --- .../RegisterComponentForm.test.tsx | 6 +++--- .../RegisterComponentForm/RegisterComponentForm.tsx | 10 +++++----- plugins/register-component/src/util/validate.ts | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx index e1226bbf32..bb7faabc9d 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.test.tsx @@ -14,10 +14,10 @@ * limitations under the License. */ +import { cleanup, fireEvent, render } from '@testing-library/react'; import React from 'react'; -import { render, fireEvent, cleanup } from '@testing-library/react'; -import RegisterComponentForm, { Props } from './RegisterComponentForm'; import { act } from 'react-dom/test-utils'; +import RegisterComponentForm, { Props } from './RegisterComponentForm'; const setup = (props?: Partial) => { return { @@ -37,7 +37,7 @@ describe('RegisterComponentForm', () => { const { rendered } = setup(); expect( await rendered.findByText( - 'Enter the full path to the component.yaml file in GitHub, GitLab, Bitbucket or Azure to start tracking your component. For private repo provide authentication information via config.', + 'Enter the full path to the component.yaml file in GitHub, GitLab, Bitbucket or Azure to start tracking your component.', ), ).toBeInTheDocument(); diff --git a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx index de3c54610d..1b84b79234 100644 --- a/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx +++ b/plugins/register-component/src/components/RegisterComponentForm/RegisterComponentForm.tsx @@ -14,17 +14,17 @@ * limitations under the License. */ -import React, { FC } from 'react'; +import { BackstageTheme } from '@backstage/theme'; import { Button, FormControl, FormHelperText, - TextField, LinearProgress, + TextField, } from '@material-ui/core'; -import { useForm } from 'react-hook-form'; import { makeStyles } from '@material-ui/core/styles'; -import { BackstageTheme } from '@backstage/theme'; +import React, { FC } from 'react'; +import { useForm } from 'react-hook-form'; import { ComponentIdValidators } from '../../util/validate'; const useStyles = makeStyles(theme => ({ @@ -71,7 +71,7 @@ const RegisterComponentForm: FC = ({ onSubmit, submitting }) => { name="componentLocation" required margin="normal" - helperText="Enter the full path to the component.yaml file in GitHub, GitLab, Bitbucket or Azure to start tracking your component. For private repo provide authentication information via config." + helperText="Enter the full path to the component.yaml file in GitHub, GitLab, Bitbucket or Azure to start tracking your component." inputRef={register({ required: true, validate: ComponentIdValidators, diff --git a/plugins/register-component/src/util/validate.ts b/plugins/register-component/src/util/validate.ts index 8552872015..9afb5ebd97 100644 --- a/plugins/register-component/src/util/validate.ts +++ b/plugins/register-component/src/util/validate.ts @@ -19,6 +19,6 @@ export const ComponentIdValidators = { (typeof value === 'string' && value.match(/^https:\/\//) !== null) || 'Must start with https://.', yamlValidator: (value: any) => - (typeof value === 'string' && value.match(/.yaml/) !== null) || + (typeof value === 'string' && value.match(/\.yaml/) !== null) || "Must contain '.yaml'.", }; From 92d6f9bacffe019bc13024960c0f6ea846d490a0 Mon Sep 17 00:00:00 2001 From: Ashok Manji <1902568+ashokm@users.noreply.github.com> Date: Fri, 28 Aug 2020 15:31:29 +0200 Subject: [PATCH 019/132] Fix typo on Backstage Helm Chart README --- install/kubernetes/backstage/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/install/kubernetes/backstage/README.md b/install/kubernetes/backstage/README.md index e8d809c228..fc3d20733b 100644 --- a/install/kubernetes/backstage/README.md +++ b/install/kubernetes/backstage/README.md @@ -23,7 +23,7 @@ | app.resources | Kubernetes Pod resource requests/limits | `{}` | | app.nodeSelector | Node selectors for scheduling app/frontend pods | `{}` | | app.tolerations | Tolerations for scheduling app/frontend pods | `{}` | -| app.affinity | Affinity setttings for scheduling app/frontend pods | `{}` | +| app.affinity | Affinity settings for scheduling app/frontend pods | `{}` | ## Backend Values @@ -48,4 +48,4 @@ | backend.resources | Kubernetes Pod resource requests/limits | `{}` | | backend.nodeSelector | Node selectors for scheduling backend pods | `{}` | | backend.tolerations | Tolerations for scheduling backend pods | `{}` | -| backend.affinity | Affinity setttings for scheduling backend pods | `{}` | +| backend.affinity | Affinity settings for scheduling backend pods | `{}` | From 314eeb31d77897603791c2331a4719cac8bd4a38 Mon Sep 17 00:00:00 2001 From: Jesse Alan Johnson Date: Fri, 28 Aug 2020 11:57:04 -0400 Subject: [PATCH 020/132] documentation update (#2147) --- docs/getting-started/index.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/getting-started/index.md b/docs/getting-started/index.md index bfd3c70913..abe7776371 100644 --- a/docs/getting-started/index.md +++ b/docs/getting-started/index.md @@ -3,9 +3,22 @@ id: index title: Running Backstage Locally --- +First make sure you are using NodeJS with an Active LTS Release, currently v12. +This is made easy with a version manager such as nvm which allows for version switching. + +```bash +# Checking your version +node --version +> v14.7.0 + +# Adding a second node version +nvm install 12 +> Downloading and installing node v12.18.3... +> Now using node v12.18.3 (npm v6.14.6) +``` + To get up and running with a local Backstage to evaluate it, let's clone it off -of GitHub and run an initial build. First make sure that you have at least node -version 12 installed locally. +of GitHub and run an initial build. ```bash # Start from your local development folder From 57c3e2ad5baf30f6b2bc732ad79492e997c7af9f Mon Sep 17 00:00:00 2001 From: Rod Machen Date: Fri, 28 Aug 2020 14:07:25 -0500 Subject: [PATCH 021/132] update software template docs --- .../software-templates/adding-templates.md | 6 +++--- .../extending/create-your-own-publisher.md | 6 +++--- .../extending/create-your-own-templater.md | 12 +++++------ .../software-templates/extending/index.md | 20 +++++++++---------- docs/features/software-templates/index.md | 12 +++++------ .../src/scaffolder/stages/templater/types.ts | 2 +- .../src/techdocs/stages/generate/types.ts | 2 +- 7 files changed, 30 insertions(+), 30 deletions(-) diff --git a/docs/features/software-templates/adding-templates.md b/docs/features/software-templates/adding-templates.md index 67e9c23605..67467bd98e 100644 --- a/docs/features/software-templates/adding-templates.md +++ b/docs/features/software-templates/adding-templates.md @@ -58,7 +58,7 @@ Currently the catalog supports loading definitions from GitHub + Local Files. To load from other places, not only will there need to be another preparer, but the support to load the location will also need to be added to the Catalog. -For loading from a file the following command should work when the backend is +For loading from a file, the following command should work when the backend is running: ```sh @@ -69,7 +69,7 @@ curl \ --data-raw "{\"type\": \"file\", \"target\": \"${YOUR PATH HERE}/template.yaml\"}" ``` -If loading from a git location, you can run the following +If loading from a Git location, you can run the following ```sh curl \ @@ -83,7 +83,7 @@ This should then have added the catalog, and also should now be listed under the create page at http://localhost:3000/create. Alternatively, if you want to get setup with some mock templates that are -already provided for you, you can run the following to load those templates: +already provided, run the following to load those templates: ``` yarn lerna run mock-data diff --git a/docs/features/software-templates/extending/create-your-own-publisher.md b/docs/features/software-templates/extending/create-your-own-publisher.md index 51869dc3ad..391f6f0658 100644 --- a/docs/features/software-templates/extending/create-your-own-publisher.md +++ b/docs/features/software-templates/extending/create-your-own-publisher.md @@ -7,8 +7,8 @@ Publishers are responsible for pushing and storing the templated skeleton after the values have been templated by the `Templater`. See [Create your own templater](./create-your-own-templater.md) for more info. -They recieve a directory or location where the templater has sucessfully run on, -and is now ready to store somewhere. They also get given some other options +They receive a directory or location where the templater has sucessfully run +and is now ready to store somewhere. They also are given some other options which are sent from the frontend, such as the `storePath` which is a string of where the frontend thinks we should save this templated folder. @@ -17,7 +17,7 @@ Currently we provide the following `publishers`: - `github` This publisher is passed through to the `createRouter` function of the -`@spotify/plugin-scaffolder-backend`. Currently only one publisher is supported, +`@spotify/plugin-scaffolder-backend`. Currently, only one publisher is supported, but PR's are always welcome. An full example backend can be found diff --git a/docs/features/software-templates/extending/create-your-own-templater.md b/docs/features/software-templates/extending/create-your-own-templater.md index a39f87924b..9dee66af06 100644 --- a/docs/features/software-templates/extending/create-your-own-templater.md +++ b/docs/features/software-templates/extending/create-your-own-templater.md @@ -8,14 +8,14 @@ returned by the preparers, and then executing the templating command on top of the file and returning the completed template path. This may or may not be the same directory as the input directory. -They also recieve additional values from the frontend, which can be used to +They also receive additional values from the frontend, which can be used to interpolate into the skeleton files. Currently we provide the following templaters: - `cookiecutter` -This templater is added the `TemplaterBuilder` and then passed into the +This templater is added to the `TemplaterBuilder` and then passed into the `createRouter` function of the `@spotify/plugin-scaffolder-backend` An full example backend can be found @@ -48,7 +48,7 @@ This `TemplaterKey` is used to select the correct templater from the `spec.templater` in the [Template Entity](../../software-catalog/descriptor-format.md#kind-template). -If you wish to add a new templater you'll need to register it with the +If you wish to add a new templater, you'll need to register it with the `TemplaterBuilder`. ### Creating your own Templater to add to the `TemplaterBuilder` @@ -83,10 +83,10 @@ follows: - `dockerClient` - a [dockerode](https://github.com/apocas/dockerode) client to be able to run docker containers. -_note_ currently the templaters that we provide are basically docker action +_note_ Currently the templaters that we provide are basically Docker action containers that are run on top of the skeleton folder. This keeps dependencies to a minimal for running backstage scaffolder, but you don't /have/ to use -docker. You could create your own templater that spins up an EC2 instance and +Docker. You could create your own templater that spins up an EC2 instance and downloads the folder and does everything using an AMI if you want. It's entirely up to you! @@ -138,7 +138,7 @@ spec: description: Description of the component ``` -You see that the `spec.templater` is set as `handlebars`, you'll need to +You see that the `spec.templater` is set as `handlebars`, so you'll need to register this with the `TemplaterBuilder` like so: ```ts diff --git a/docs/features/software-templates/extending/index.md b/docs/features/software-templates/extending/index.md index 21d8befda1..02d27a5725 100644 --- a/docs/features/software-templates/extending/index.md +++ b/docs/features/software-templates/extending/index.md @@ -5,11 +5,11 @@ title: Extending the Scaffolder Welcome. Take a seat. You're at the Scaffolder Documentation. -So - You wanna create stuff inside your company from some prebaked templates? +So, you want to create stuff inside your company from some prebaked templates? You're at the right place. -This guide is gonna take you through how the Scaffolder in Backstage works. -We'll dive into some jargon and run through whats going on in the backend to be +This guide is going to take you through how the Scaffolder in Backstage works. +We'll dive into some jargon and run through what's going on in the backend to be able to create these templates. There's also more guides that you might find useful at the bottom of this document. At it's core, theres 3 simple stages. @@ -25,9 +25,9 @@ scaffolder that you will need to know: 3. Publish Each of these steps can be configured for your own use case, but we provide some -sensible defaults too. +sensible defaults, too. -Lets dive a little deeper into these phases. +Let's dive a little deeper into these phases. ### Glossary and Jargon @@ -38,8 +38,8 @@ the router to pick the correct `Preparer` to run for the `Template` entity. **Templater** - The templater is responsible for actually running the chosen templater on top of the previously returned temporary directory from the -**Preprarer**. We advise making these docker containers as it can keep all -dependencies, for example Cookiecutter, self contained and not a dependency on +**Preprarer**. We advise making these Docker containers as it can keep all +dependencies--for example Cookiecutter--self contained and not a dependency on the host machine. **Publisher** - The publisher is responsible for taking the finished directory, @@ -50,11 +50,11 @@ passed through to the scaffolder backend. ### How it works -The main of the heavy lifting is done in the +Most of the heavy lifting is done in the [router.ts](https://github.com/spotify/backstage/blob/master/plugins/scaffolder-backend/src/service/router.ts#L93) file in the `scaffolder-backend` plugin. -There are 2 routes defined in the router. `POST /v1/jobs` and +There are two routes defined in the router: `POST /v1/jobs` and `GET /v1/job/:jobId` To create a scaffolding job, a JSON object containing the @@ -78,7 +78,7 @@ additional templating values must be posted as the post body. The values should represent something that is valid with the `schema` part of the [Template Entity](../../software-catalog/descriptor-format.md#kind-template) -Once that has been posted, a job will be setup with different stages. And the +Once that has been posted, a job will be setup with different stages, and the job processor will complete each stage before moving onto the next stage, whilst collecting logs and mutating the running job. diff --git a/docs/features/software-templates/index.md b/docs/features/software-templates/index.md index 503fbb7a3d..1c67676b3e 100644 --- a/docs/features/software-templates/index.md +++ b/docs/features/software-templates/index.md @@ -4,8 +4,8 @@ title: Software Templates --- The Software Templates part of Backstage is a tool that can help you create -Components inside Backstage. It by default has the ability to load skeletons of -code, template in some variables and then publish the template to some location +Components inside Backstage. By default, it has the ability to load skeletons of +code, template in some variables, and then publish the template to some location like GitHub.
+ + + ); +}; From 7f4064975141b1b61a3d1800f3171f2350cac24f Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 2 Sep 2020 05:55:52 +0000 Subject: [PATCH 104/132] chore(deps): bump jest from 26.0.1 to 26.4.2 Bumps [jest](https://github.com/facebook/jest) from 26.0.1 to 26.4.2. - [Release notes](https://github.com/facebook/jest/releases) - [Changelog](https://github.com/facebook/jest/blob/master/CHANGELOG.md) - [Commits](https://github.com/facebook/jest/compare/v26.0.1...v26.4.2) Signed-off-by: dependabot-preview[bot] --- yarn.lock | 776 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 406 insertions(+), 370 deletions(-) diff --git a/yarn.lock b/yarn.lock index 3301f94f83..bea32a92aa 100644 --- a/yarn.lock +++ b/yarn.lock @@ -536,6 +536,13 @@ dependencies: "@babel/helper-plugin-utils" "^7.8.3" +"@babel/plugin-syntax-import-meta@^7.8.3": + version "7.10.4" + resolved "https://registry.npmjs.org/@babel/plugin-syntax-import-meta/-/plugin-syntax-import-meta-7.10.4.tgz#ee601348c370fa334d2207be158777496521fd51" + integrity sha512-Yqfm+XDx0+Prh3VSeEQCPU81yC+JWZ2pDPFSS4ZdpfZhp4MkFMaDC1UqseovEKwSUpnIL7+vK+Clp7bfh0iD7g== + dependencies: + "@babel/helper-plugin-utils" "^7.10.4" + "@babel/plugin-syntax-json-strings@^7.8.0", "@babel/plugin-syntax-json-strings@^7.8.3": version "7.8.3" resolved "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz#01ca21b668cd8218c9e640cb6dd88c5412b2c96a" @@ -1521,156 +1528,160 @@ resolved "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.2.tgz#26520bf09abe4a5644cd5414e37125a8954241dd" integrity sha512-tsAQNx32a8CoFhjhijUIhI4kccIAgmGhy8LZMZgGfmXcpMbPRUqn5LWmgRttILi6yeGmBJd2xsPkFMs0PzgPCw== -"@jest/console@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/console/-/console-26.0.1.tgz#62b3b2fa8990f3cbffbef695c42ae9ddbc8f4b39" - integrity sha512-9t1KUe/93coV1rBSxMmBAOIK3/HVpwxArCA1CxskKyRiv6o8J70V8C/V3OJminVCTa2M0hQI9AWRd5wxu2dAHw== +"@jest/console@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/console/-/console-26.3.0.tgz#ed04063efb280c88ba87388b6f16427c0a85c856" + integrity sha512-/5Pn6sJev0nPUcAdpJHMVIsA8sKizL2ZkcKPE5+dJrCccks7tcM7c9wbgHudBJbxXLoTbqsHkG1Dofoem4F09w== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" + "@types/node" "*" chalk "^4.0.0" - jest-message-util "^26.0.1" - jest-util "^26.0.1" + jest-message-util "^26.3.0" + jest-util "^26.3.0" slash "^3.0.0" -"@jest/core@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/core/-/core-26.0.1.tgz#aa538d52497dfab56735efb00e506be83d841fae" - integrity sha512-Xq3eqYnxsG9SjDC+WLeIgf7/8KU6rddBxH+SCt18gEpOhAGYC/Mq+YbtlNcIdwjnnT+wDseXSbU0e5X84Y4jTQ== +"@jest/core@^26.4.2": + version "26.4.2" + resolved "https://registry.npmjs.org/@jest/core/-/core-26.4.2.tgz#85d0894f31ac29b5bab07aa86806d03dd3d33edc" + integrity sha512-sDva7YkeNprxJfepOctzS8cAk9TOekldh+5FhVuXS40+94SHbiicRO1VV2tSoRtgIo+POs/Cdyf8p76vPTd6dg== dependencies: - "@jest/console" "^26.0.1" - "@jest/reporters" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.3.0" + "@jest/reporters" "^26.4.1" + "@jest/test-result" "^26.3.0" + "@jest/transform" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" - jest-changed-files "^26.0.1" - jest-config "^26.0.1" - jest-haste-map "^26.0.1" - jest-message-util "^26.0.1" + jest-changed-files "^26.3.0" + jest-config "^26.4.2" + jest-haste-map "^26.3.0" + jest-message-util "^26.3.0" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-resolve-dependencies "^26.0.1" - jest-runner "^26.0.1" - jest-runtime "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" - jest-watcher "^26.0.1" + jest-resolve "^26.4.0" + jest-resolve-dependencies "^26.4.2" + jest-runner "^26.4.2" + jest-runtime "^26.4.2" + jest-snapshot "^26.4.2" + jest-util "^26.3.0" + jest-validate "^26.4.2" + jest-watcher "^26.3.0" micromatch "^4.0.2" p-each-series "^2.1.0" rimraf "^3.0.0" slash "^3.0.0" strip-ansi "^6.0.0" -"@jest/environment@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.0.1.tgz#82f519bba71959be9b483675ee89de8c8f72a5c8" - integrity sha512-xBDxPe8/nx251u0VJ2dFAFz2H23Y98qdIaNwnMK6dFQr05jc+Ne/2np73lOAx+5mSBO/yuQldRrQOf6hP1h92g== +"@jest/environment@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/environment/-/environment-26.3.0.tgz#e6953ab711ae3e44754a025f838bde1a7fd236a0" + integrity sha512-EW+MFEo0DGHahf83RAaiqQx688qpXgl99wdb8Fy67ybyzHwR1a58LHcO376xQJHfmoXTu89M09dH3J509cx2AA== dependencies: - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" + "@jest/fake-timers" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" + jest-mock "^26.3.0" -"@jest/fake-timers@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.0.1.tgz#f7aeff13b9f387e9d0cac9a8de3bba538d19d796" - integrity sha512-Oj/kCBnTKhm7CR+OJSjZty6N1bRDr9pgiYQr4wY221azLz5PHi08x/U+9+QpceAYOWheauLP8MhtSVFrqXQfhg== +"@jest/fake-timers@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/fake-timers/-/fake-timers-26.3.0.tgz#f515d4667a6770f60ae06ae050f4e001126c666a" + integrity sha512-ZL9ytUiRwVP8ujfRepffokBvD2KbxbqMhrXSBhSdAhISCw3gOkuntisiSFv+A6HN0n0fF4cxzICEKZENLmW+1A== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" "@sinonjs/fake-timers" "^6.0.1" - jest-message-util "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" + "@types/node" "*" + jest-message-util "^26.3.0" + jest-mock "^26.3.0" + jest-util "^26.3.0" -"@jest/globals@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.0.1.tgz#3f67b508a7ce62b6e6efc536f3d18ec9deb19a9c" - integrity sha512-iuucxOYB7BRCvT+TYBzUqUNuxFX1hqaR6G6IcGgEqkJ5x4htNKo1r7jk1ji9Zj8ZMiMw0oB5NaA7k5Tx6MVssA== +"@jest/globals@^26.4.2": + version "26.4.2" + resolved "https://registry.npmjs.org/@jest/globals/-/globals-26.4.2.tgz#73c2a862ac691d998889a241beb3dc9cada40d4a" + integrity sha512-Ot5ouAlehhHLRhc+sDz2/9bmNv9p5ZWZ9LE1pXGGTCXBasmi5jnYjlgYcYt03FBwLmZXCZ7GrL29c33/XRQiow== dependencies: - "@jest/environment" "^26.0.1" - "@jest/types" "^26.0.1" - expect "^26.0.1" + "@jest/environment" "^26.3.0" + "@jest/types" "^26.3.0" + expect "^26.4.2" -"@jest/reporters@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.0.1.tgz#14ae00e7a93e498cec35b0c00ab21c375d9b078f" - integrity sha512-NWWy9KwRtE1iyG/m7huiFVF9YsYv/e+mbflKRV84WDoJfBqUrNRyDbL/vFxQcYLl8IRqI4P3MgPn386x76Gf2g== +"@jest/reporters@^26.4.1": + version "26.4.1" + resolved "https://registry.npmjs.org/@jest/reporters/-/reporters-26.4.1.tgz#3b4d6faf28650f3965f8b97bc3d114077fb71795" + integrity sha512-aROTkCLU8++yiRGVxLsuDmZsQEKO6LprlrxtAuzvtpbIFl3eIjgIf3EUxDKgomkS25R9ZzwGEdB5weCcBZlrpQ== dependencies: "@bcoe/v8-coverage" "^0.2.3" - "@jest/console" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.3.0" + "@jest/test-result" "^26.3.0" + "@jest/transform" "^26.3.0" + "@jest/types" "^26.3.0" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.2" graceful-fs "^4.2.4" istanbul-lib-coverage "^3.0.0" - istanbul-lib-instrument "^4.0.0" + istanbul-lib-instrument "^4.0.3" istanbul-lib-report "^3.0.0" istanbul-lib-source-maps "^4.0.0" istanbul-reports "^3.0.2" - jest-haste-map "^26.0.1" - jest-resolve "^26.0.1" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-haste-map "^26.3.0" + jest-resolve "^26.4.0" + jest-util "^26.3.0" + jest-worker "^26.3.0" slash "^3.0.0" source-map "^0.6.0" string-length "^4.0.1" terminal-link "^2.0.0" - v8-to-istanbul "^4.1.3" + v8-to-istanbul "^5.0.1" optionalDependencies: - node-notifier "^7.0.0" + node-notifier "^8.0.0" -"@jest/source-map@^26.0.0": - version "26.0.0" - resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.0.0.tgz#fd7706484a7d3faf7792ae29783933bbf48a4749" - integrity sha512-S2Z+Aj/7KOSU2TfW0dyzBze7xr95bkm5YXNUqqCek+HE0VbNNSNzrRwfIi5lf7wvzDTSS0/ib8XQ1krFNyYgbQ== +"@jest/source-map@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/source-map/-/source-map-26.3.0.tgz#0e646e519883c14c551f7b5ae4ff5f1bfe4fc3d9" + integrity sha512-hWX5IHmMDWe1kyrKl7IhFwqOuAreIwHhbe44+XH2ZRHjrKIh0LO5eLQ/vxHFeAfRwJapmxuqlGAEYLadDq6ZGQ== dependencies: callsites "^3.0.0" graceful-fs "^4.2.4" source-map "^0.6.0" -"@jest/test-result@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.0.1.tgz#1ffdc1ba4bc289919e54b9414b74c9c2f7b2b718" - integrity sha512-oKwHvOI73ICSYRPe8WwyYPTtiuOAkLSbY8/MfWF3qDEd/sa8EDyZzin3BaXTqufir/O/Gzea4E8Zl14XU4Mlyg== +"@jest/test-result@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/test-result/-/test-result-26.3.0.tgz#46cde01fa10c0aaeb7431bf71e4a20d885bc7fdb" + integrity sha512-a8rbLqzW/q7HWheFVMtghXV79Xk+GWwOK1FrtimpI5n1la2SY0qHri3/b0/1F0Ve0/yJmV8pEhxDfVwiUBGtgg== dependencies: - "@jest/console" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.3.0" + "@jest/types" "^26.3.0" "@types/istanbul-lib-coverage" "^2.0.0" collect-v8-coverage "^1.0.0" -"@jest/test-sequencer@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.0.1.tgz#b0563424728f3fe9e75d1442b9ae4c11da73f090" - integrity sha512-ssga8XlwfP8YjbDcmVhwNlrmblddMfgUeAkWIXts1V22equp2GMIHxm7cyeD5Q/B0ZgKPK/tngt45sH99yLLGg== +"@jest/test-sequencer@^26.4.2": + version "26.4.2" + resolved "https://registry.npmjs.org/@jest/test-sequencer/-/test-sequencer-26.4.2.tgz#58a3760a61eec758a2ce6080201424580d97cbba" + integrity sha512-83DRD8N3M0tOhz9h0bn6Kl6dSp+US6DazuVF8J9m21WAp5x7CqSMaNycMP0aemC/SH/pDQQddbsfHRTBXVUgog== dependencies: - "@jest/test-result" "^26.0.1" + "@jest/test-result" "^26.3.0" graceful-fs "^4.2.4" - jest-haste-map "^26.0.1" - jest-runner "^26.0.1" - jest-runtime "^26.0.1" + jest-haste-map "^26.3.0" + jest-runner "^26.4.2" + jest-runtime "^26.4.2" -"@jest/transform@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.0.1.tgz#0e3ecbb34a11cd4b2080ed0a9c4856cf0ceb0639" - integrity sha512-pPRkVkAQ91drKGbzCfDOoHN838+FSbYaEAvBXvKuWeeRRUD8FjwXkqfUNUZL6Ke48aA/1cqq/Ni7kVMCoqagWA== +"@jest/transform@^26.3.0": + version "26.3.0" + resolved "https://registry.npmjs.org/@jest/transform/-/transform-26.3.0.tgz#c393e0e01459da8a8bfc6d2a7c2ece1a13e8ba55" + integrity sha512-Isj6NB68QorGoFWvcOjlUhpkT56PqNIsXKR7XfvoDlCANn/IANlh8DrKAA2l2JKC3yWSMH5wS0GwuQM20w3b2A== dependencies: "@babel/core" "^7.1.0" - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" babel-plugin-istanbul "^6.0.0" chalk "^4.0.0" convert-source-map "^1.4.0" fast-json-stable-stringify "^2.0.0" graceful-fs "^4.2.4" - jest-haste-map "^26.0.1" + jest-haste-map "^26.3.0" jest-regex-util "^26.0.0" - jest-util "^26.0.1" + jest-util "^26.3.0" micromatch "^4.0.2" pirates "^4.0.1" slash "^3.0.0" @@ -1687,16 +1698,6 @@ "@types/yargs" "^15.0.0" chalk "^3.0.0" -"@jest/types@^26.0.1": - version "26.0.1" - resolved "https://registry.npmjs.org/@jest/types/-/types-26.0.1.tgz#b78333fbd113fa7aec8d39de24f88de8686dac67" - integrity sha512-IbtjvqI9+eS1qFnOIEL7ggWmT+iK/U+Vde9cGWtYb/b6XgKb3X44ZAe/z9YZzoAAZ/E92m0DqrilF934IGNnQA== - dependencies: - "@types/istanbul-lib-coverage" "^2.0.0" - "@types/istanbul-reports" "^1.1.1" - "@types/yargs" "^15.0.0" - chalk "^4.0.0" - "@jest/types@^26.3.0": version "26.3.0" resolved "https://registry.npmjs.org/@jest/types/-/types-26.3.0.tgz#97627bf4bdb72c55346eef98e3b3f7ddc4941f71" @@ -3984,6 +3985,17 @@ resolved "https://registry.npmjs.org/@types/aria-query/-/aria-query-4.2.0.tgz#14264692a9d6e2fa4db3df5e56e94b5e25647ac0" integrity sha512-iIgQNzCm0v7QMhhe4Jjn9uRh+I6GoPmt03CbEtwx3ao8/EfoQcmgtqH4vQ5Db/lxiIGaWDv6nwvunuh0RyX0+A== +"@types/babel__core@^7.0.0": + version "7.1.9" + resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.9.tgz#77e59d438522a6fb898fa43dc3455c6e72f3963d" + integrity sha512-sY2RsIJ5rpER1u3/aQ8OFSI7qGIy8o1NEEbgb2UaJcvOtXOMpd39ko723NBpjQFg9SIX7TXtjejZVGeIMLhoOw== + dependencies: + "@babel/parser" "^7.1.0" + "@babel/types" "^7.0.0" + "@types/babel__generator" "*" + "@types/babel__template" "*" + "@types/babel__traverse" "*" + "@types/babel__core@^7.1.7": version "7.1.7" resolved "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.1.7.tgz#1dacad8840364a57c98d0dd4855c6dd3752c6b89" @@ -6088,16 +6100,16 @@ babel-helper-to-multiple-sequence-expressions@^0.5.0: resolved "https://registry.npmjs.org/babel-helper-to-multiple-sequence-expressions/-/babel-helper-to-multiple-sequence-expressions-0.5.0.tgz#a3f924e3561882d42fcf48907aa98f7979a4588d" integrity sha512-m2CvfDW4+1qfDdsrtf4dwOslQC3yhbgyBFptncp4wvtdrDHqueW7slsYv4gArie056phvQFhT2nRcGS4bnm6mA== -babel-jest@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.0.1.tgz#450139ce4b6c17174b136425bda91885c397bc46" - integrity sha512-Z4GGmSNQ8pX3WS1O+6v3fo41YItJJZsVxG5gIQ+HuB/iuAQBJxMTHTwz292vuYws1LnHfwSRgoqI+nxdy/pcvw== +babel-jest@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/babel-jest/-/babel-jest-26.3.0.tgz#10d0ca4b529ca3e7d1417855ef7d7bd6fc0c3463" + integrity sha512-sxPnQGEyHAOPF8NcUsD0g7hDCnvLL2XyblRBcgrzTWBB/mAIpWow3n1bEL+VghnnZfreLhFSBsFluRoK2tRK4g== dependencies: - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/transform" "^26.3.0" + "@jest/types" "^26.3.0" "@types/babel__core" "^7.1.7" babel-plugin-istanbul "^6.0.0" - babel-preset-jest "^26.0.0" + babel-preset-jest "^26.3.0" chalk "^4.0.0" graceful-fs "^4.2.4" slash "^3.0.0" @@ -6141,13 +6153,14 @@ babel-plugin-istanbul@^6.0.0: istanbul-lib-instrument "^4.0.0" test-exclude "^6.0.0" -babel-plugin-jest-hoist@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.0.0.tgz#fd1d35f95cf8849fc65cb01b5e58aedd710b34a8" - integrity sha512-+AuoehOrjt9irZL7DOt2+4ZaTM6dlu1s5TTS46JBa0/qem4dy7VNW3tMb96qeEqcIh20LD73TVNtmVEeymTG7w== +babel-plugin-jest-hoist@^26.2.0: + version "26.2.0" + resolved "https://registry.npmjs.org/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-26.2.0.tgz#bdd0011df0d3d513e5e95f76bd53b51147aca2dd" + integrity sha512-B/hVMRv8Nh1sQ1a3EY8I0n4Y1Wty3NrR5ebOyVT302op+DOAau+xNEImGMsUWOC3++ZlMooCytKz+NgN8aKGbA== dependencies: "@babel/template" "^7.3.3" "@babel/types" "^7.3.3" + "@types/babel__core" "^7.0.0" "@types/babel__traverse" "^7.0.6" babel-plugin-macros@^2.0.0, babel-plugin-macros@^2.7.0: @@ -6313,14 +6326,15 @@ babel-plugin-transform-undefined-to-void@^6.9.4: resolved "https://registry.npmjs.org/babel-plugin-transform-undefined-to-void/-/babel-plugin-transform-undefined-to-void-6.9.4.tgz#be241ca81404030678b748717322b89d0c8fe280" integrity sha1-viQcqBQEAwZ4t0hxcyK4nQyP4oA= -babel-preset-current-node-syntax@^0.1.2: - version "0.1.2" - resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.2.tgz#fb4a4c51fe38ca60fede1dc74ab35eb843cb41d6" - integrity sha512-u/8cS+dEiK1SFILbOC8/rUI3ml9lboKuuMvZ/4aQnQmhecQAgPw5ew066C1ObnEAUmlx7dv/s2z52psWEtLNiw== +babel-preset-current-node-syntax@^0.1.3: + version "0.1.3" + resolved "https://registry.npmjs.org/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da" + integrity sha512-uyexu1sVwcdFnyq9o8UQYsXwXflIh8LvrF5+cKrYam93ned1CStffB3+BEcsxGSgagoA3GEyjDqO4a/58hyPYQ== dependencies: "@babel/plugin-syntax-async-generators" "^7.8.4" "@babel/plugin-syntax-bigint" "^7.8.3" "@babel/plugin-syntax-class-properties" "^7.8.3" + "@babel/plugin-syntax-import-meta" "^7.8.3" "@babel/plugin-syntax-json-strings" "^7.8.3" "@babel/plugin-syntax-logical-assignment-operators" "^7.8.3" "@babel/plugin-syntax-nullish-coalescing-operator" "^7.8.3" @@ -6329,13 +6343,13 @@ babel-preset-current-node-syntax@^0.1.2: "@babel/plugin-syntax-optional-catch-binding" "^7.8.3" "@babel/plugin-syntax-optional-chaining" "^7.8.3" -babel-preset-jest@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.0.0.tgz#1eac82f513ad36c4db2e9263d7c485c825b1faa6" - integrity sha512-9ce+DatAa31DpR4Uir8g4Ahxs5K4W4L8refzt+qHWQANb6LhGcAEfIFgLUwk67oya2cCUd6t4eUMtO/z64ocNw== +babel-preset-jest@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/babel-preset-jest/-/babel-preset-jest-26.3.0.tgz#ed6344506225c065fd8a0b53e191986f74890776" + integrity sha512-5WPdf7nyYi2/eRxCbVrE1kKCWxgWY4RsPEbdJWFm7QsesFGqjdkyLeu1zRkwM1cxK6EPIlNd6d2AxLk7J+t4pw== dependencies: - babel-plugin-jest-hoist "^26.0.0" - babel-preset-current-node-syntax "^0.1.2" + babel-plugin-jest-hoist "^26.2.0" + babel-preset-current-node-syntax "^0.1.3" "babel-preset-minify@^0.5.0 || 0.6.0-alpha.5": version "0.5.1" @@ -8997,10 +9011,10 @@ diff-sequences@^25.2.6: resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-25.2.6.tgz#5f467c00edd35352b7bca46d7927d60e687a76dd" integrity sha512-Hq8o7+6GaZeoFjtpgvRBUknSXNeJiCx7V9Fr94ZMljNiCr9n9L8H8aJqgWOQiDDGdyn29fRNcDdRVJ5fdyihfg== -diff-sequences@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.0.0.tgz#0760059a5c287637b842bd7085311db7060e88a6" - integrity sha512-JC/eHYEC3aSS0vZGjuoc4vHA0yAQTzhQQldXMeMF+JlxLGJlCO38Gma82NV9gk1jGFz8mDzUMeaKXvjRRdJ2dg== +diff-sequences@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/diff-sequences/-/diff-sequences-26.3.0.tgz#62a59b1b29ab7fd27cef2a33ae52abe73042d0a2" + integrity sha512-5j5vdRcw3CNctePNYN0Wy2e/JbWT6cAYnXv5OuqPhDpyCGc0uLu2TK0zOCJWNB9kOIfYMSpIulRaDgIi4HJ6Ig== diff@^4.0.1, diff@^4.0.2: version "4.0.2" @@ -9435,6 +9449,11 @@ emitter-component@^1.1.1: resolved "https://registry.npmjs.org/emitter-component/-/emitter-component-1.1.1.tgz#065e2dbed6959bf470679edabeaf7981d1003ab6" integrity sha1-Bl4tvtaVm/RwZ57avq95gdEAOrY= +emittery@^0.7.1: + version "0.7.1" + resolved "https://registry.npmjs.org/emittery/-/emittery-0.7.1.tgz#c02375a927a40948c0345cc903072597f5270451" + integrity sha512-d34LN4L6h18Bzz9xpoku2nPwKxCPlPMr3EEKTkoEBi+1/+b0lcRkRJ1UVyyZaKNeqGR3swcGl6s390DNO4YVgQ== + "emoji-regex@>=6.0.0 <=6.1.1": version "6.1.1" resolved "https://registry.npmjs.org/emoji-regex/-/emoji-regex-6.1.1.tgz#c6cd0ec1b0642e2a3c67a1137efc5e796da4f88e" @@ -10163,16 +10182,16 @@ expand-tilde@^2.0.0, expand-tilde@^2.0.2: dependencies: homedir-polyfill "^1.0.1" -expect@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/expect/-/expect-26.0.1.tgz#18697b9611a7e2725e20ba3ceadda49bc9865421" - integrity sha512-QcCy4nygHeqmbw564YxNbHTJlXh47dVID2BUP52cZFpLU9zHViMFK6h07cC1wf7GYCTIigTdAXhVua8Yl1FkKg== +expect@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/expect/-/expect-26.4.2.tgz#36db120928a5a2d7d9736643032de32f24e1b2a1" + integrity sha512-IlJ3X52Z0lDHm7gjEp+m76uX46ldH5VpqmU0006vqDju/285twh7zaWMRhs67VpQhBwjjMchk+p5aA0VkERCAA== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" ansi-styles "^4.0.0" - jest-get-type "^26.0.0" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" + jest-get-type "^26.3.0" + jest-matcher-utils "^26.4.2" + jest-message-util "^26.3.0" jest-regex-util "^26.0.0" express-prom-bundle@^6.1.0: @@ -13173,6 +13192,13 @@ is-wsl@^2.1.1: resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.1.1.tgz#4a1c152d429df3d441669498e2486d3596ebaf1d" integrity sha512-umZHcSrwlDHo2TGMXv0DZ8dIUGunZ2Iv68YZnrmCiBPkZ4aaOhtv7pXJKeki9k3qJ3RJr0cDyitcl5wEH3AYog== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.npmjs.org/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== + dependencies: + is-docker "^2.0.0" + is-yarn-global@^0.3.0: version "0.3.0" resolved "https://registry.npmjs.org/is-yarn-global/-/is-yarn-global-0.3.0.tgz#d502d3382590ea3004893746754c89139973e232" @@ -13249,6 +13275,16 @@ istanbul-lib-instrument@^4.0.0: istanbul-lib-coverage "^3.0.0" semver "^6.3.0" +istanbul-lib-instrument@^4.0.3: + version "4.0.3" + resolved "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz#873c6fff897450118222774696a3f28902d77c1d" + integrity sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ== + dependencies: + "@babel/core" "^7.7.5" + "@istanbuljs/schema" "^0.1.2" + istanbul-lib-coverage "^3.0.0" + semver "^6.3.0" + istanbul-lib-report@^3.0.0: version "3.0.0" resolved "https://registry.npmjs.org/istanbul-lib-report/-/istanbul-lib-report-3.0.0.tgz#7518fe52ea44de372f460a76b5ecda9ffb73d8a6" @@ -13308,57 +13344,57 @@ jenkins@^0.28.0: dependencies: papi "^0.29.0" -jest-changed-files@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.0.1.tgz#1334630c6a1ad75784120f39c3aa9278e59f349f" - integrity sha512-q8LP9Sint17HaE2LjxQXL+oYWW/WeeXMPE2+Op9X3mY8IEGFVc14xRxFjUuXUbcPAlDLhtWdIEt59GdQbn76Hw== +jest-changed-files@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-changed-files/-/jest-changed-files-26.3.0.tgz#68fb2a7eb125f50839dab1f5a17db3607fe195b1" + integrity sha512-1C4R4nijgPltX6fugKxM4oQ18zimS7LqQ+zTTY8lMCMFPrxqBFb7KJH0Z2fRQJvw2Slbaipsqq7s1mgX5Iot+g== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" execa "^4.0.0" throat "^5.0.0" -jest-cli@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.0.1.tgz#3a42399a4cbc96a519b99ad069a117d955570cac" - integrity sha512-pFLfSOBcbG9iOZWaMK4Een+tTxi/Wcm34geqZEqrst9cZDkTQ1LZ2CnBrTlHWuYAiTMFr0EQeK52ScyFU8wK+w== +jest-cli@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-cli/-/jest-cli-26.4.2.tgz#24afc6e4dfc25cde4c7ec4226fb7db5f157c21da" + integrity sha512-zb+lGd/SfrPvoRSC/0LWdaWCnscXc1mGYW//NP4/tmBvRPT3VntZ2jtKUONsRi59zc5JqmsSajA9ewJKFYp8Cw== dependencies: - "@jest/core" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/core" "^26.4.2" + "@jest/test-result" "^26.3.0" + "@jest/types" "^26.3.0" chalk "^4.0.0" exit "^0.1.2" graceful-fs "^4.2.4" import-local "^3.0.2" is-ci "^2.0.0" - jest-config "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-config "^26.4.2" + jest-util "^26.3.0" + jest-validate "^26.4.2" prompts "^2.0.1" yargs "^15.3.1" -jest-config@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.0.1.tgz#096a3d4150afadf719d1fab00e9a6fb2d6d67507" - integrity sha512-9mWKx2L1LFgOXlDsC4YSeavnblN6A4CPfXFiobq+YYLaBMymA/SczN7xYTSmLaEYHZOcB98UdoN4m5uNt6tztg== +jest-config@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-config/-/jest-config-26.4.2.tgz#da0cbb7dc2c131ffe831f0f7f2a36256e6086558" + integrity sha512-QBf7YGLuToiM8PmTnJEdRxyYy3mHWLh24LJZKVdXZ2PNdizSe1B/E8bVm+HYcjbEzGuVXDv/di+EzdO/6Gq80A== dependencies: "@babel/core" "^7.1.0" - "@jest/test-sequencer" "^26.0.1" - "@jest/types" "^26.0.1" - babel-jest "^26.0.1" + "@jest/test-sequencer" "^26.4.2" + "@jest/types" "^26.3.0" + babel-jest "^26.3.0" chalk "^4.0.0" deepmerge "^4.2.2" glob "^7.1.1" graceful-fs "^4.2.4" - jest-environment-jsdom "^26.0.1" - jest-environment-node "^26.0.1" - jest-get-type "^26.0.0" - jest-jasmine2 "^26.0.1" + jest-environment-jsdom "^26.3.0" + jest-environment-node "^26.3.0" + jest-get-type "^26.3.0" + jest-jasmine2 "^26.4.2" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-resolve "^26.4.0" + jest-util "^26.3.0" + jest-validate "^26.4.2" micromatch "^4.0.2" - pretty-format "^26.0.1" + pretty-format "^26.4.2" jest-css-modules@^2.1.0: version "2.1.0" @@ -13377,15 +13413,15 @@ jest-diff@^25.1.0, jest-diff@^25.2.1: jest-get-type "^25.2.6" pretty-format "^25.5.0" -jest-diff@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.0.1.tgz#c44ab3cdd5977d466de69c46929e0e57f89aa1de" - integrity sha512-odTcHyl5X+U+QsczJmOjWw5tPvww+y9Yim5xzqxVl/R1j4z71+fHW4g8qu1ugMmKdFdxw+AtQgs5mupPnzcIBQ== +jest-diff@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-diff/-/jest-diff-26.4.2.tgz#a1b7b303bcc534aabdb3bd4a7caf594ac059f5aa" + integrity sha512-6T1XQY8U28WH0Z5rGpQ+VqZSZz8EN8rZcBtfvXaOkbwxIEeRre6qnuZQlbY1AJ4MKDxQF8EkrCvK+hL/VkyYLQ== dependencies: chalk "^4.0.0" - diff-sequences "^26.0.0" - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + diff-sequences "^26.3.0" + jest-get-type "^26.3.0" + pretty-format "^26.4.2" jest-docblock@^26.0.0: version "26.0.0" @@ -13394,39 +13430,41 @@ jest-docblock@^26.0.0: dependencies: detect-newline "^3.0.0" -jest-each@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.0.1.tgz#633083061619302fc90dd8f58350f9d77d67be04" - integrity sha512-OTgJlwXCAR8NIWaXFL5DBbeS4QIYPuNASkzSwMCJO+ywo9BEa6TqkaSWsfR7VdbMLdgYJqSfQcIyjJCNwl5n4Q== +jest-each@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-each/-/jest-each-26.4.2.tgz#bb14f7f4304f2bb2e2b81f783f989449b8b6ffae" + integrity sha512-p15rt8r8cUcRY0Mvo1fpkOGYm7iI8S6ySxgIdfh3oOIv+gHwrHTy5VWCGOecWUhDsit4Nz8avJWdT07WLpbwDA== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" chalk "^4.0.0" - jest-get-type "^26.0.0" - jest-util "^26.0.1" - pretty-format "^26.0.1" + jest-get-type "^26.3.0" + jest-util "^26.3.0" + pretty-format "^26.4.2" -jest-environment-jsdom@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.0.1.tgz#217690852e5bdd7c846a4e3b50c8ffd441dfd249" - integrity sha512-u88NJa3aptz2Xix2pFhihRBAatwZHWwSiRLBDBQE1cdJvDjPvv7ZGA0NQBxWwDDn7D0g1uHqxM8aGgfA9Bx49g== +jest-environment-jsdom@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-environment-jsdom/-/jest-environment-jsdom-26.3.0.tgz#3b749ba0f3a78e92ba2c9ce519e16e5dd515220c" + integrity sha512-zra8He2btIMJkAzvLaiZ9QwEPGEetbxqmjEBQwhH3CA+Hhhu0jSiEJxnJMbX28TGUvPLxBt/zyaTLrOPF4yMJA== dependencies: - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" + "@jest/environment" "^26.3.0" + "@jest/fake-timers" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" + jest-mock "^26.3.0" + jest-util "^26.3.0" jsdom "^16.2.2" -jest-environment-node@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.0.1.tgz#584a9ff623124ff6eeb49e0131b5f7612b310b13" - integrity sha512-4FRBWcSn5yVo0KtNav7+5NH5Z/tEgDLp7VRQVS5tCouWORxj+nI+1tOLutM07Zb2Qi7ja+HEDoOUkjBSWZg/IQ== +jest-environment-node@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-environment-node/-/jest-environment-node-26.3.0.tgz#56c6cfb506d1597f94ee8d717072bda7228df849" + integrity sha512-c9BvYoo+FGcMj5FunbBgtBnbR5qk3uky8PKyRVpSfe2/8+LrNQMiXX53z6q2kY+j15SkjQCOSL/6LHnCPLVHNw== dependencies: - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/types" "^26.0.1" - jest-mock "^26.0.1" - jest-util "^26.0.1" + "@jest/environment" "^26.3.0" + "@jest/fake-timers" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" + jest-mock "^26.3.0" + jest-util "^26.3.0" jest-esm-transformer@^1.0.0: version "1.0.0" @@ -13449,61 +13487,63 @@ jest-get-type@^25.1.0, jest-get-type@^25.2.6: resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-25.2.6.tgz#0b0a32fab8908b44d508be81681487dbabb8d877" integrity sha512-DxjtyzOHjObRM+sM1knti6or+eOgcGU4xVSb2HNP1TqO4ahsT+rqZg+nyqHWJSvWgKC5cG3QjGFBqxLghiF/Ig== -jest-get-type@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.0.0.tgz#381e986a718998dbfafcd5ec05934be538db4039" - integrity sha512-zRc1OAPnnws1EVfykXOj19zo2EMw5Hi6HLbFCSjpuJiXtOWAYIjNsHVSbpQ8bDX7L5BGYGI8m+HmKdjHYFF0kg== +jest-get-type@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-get-type/-/jest-get-type-26.3.0.tgz#e97dc3c3f53c2b406ca7afaed4493b1d099199e0" + integrity sha512-TpfaviN1R2pQWkIihlfEanwOXK0zcxrKEE4MlU6Tn7keoXdN6/3gK/xl0yEh8DOunn5pOVGKf8hB4R9gVh04ig== -jest-haste-map@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.0.1.tgz#40dcc03c43ac94d25b8618075804d09cd5d49de7" - integrity sha512-J9kBl/EdjmDsvyv7CiyKY5+DsTvVOScenprz/fGqfLg/pm1gdjbwwQ98nW0t+OIt+f+5nAVaElvn/6wP5KO7KA== +jest-haste-map@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-haste-map/-/jest-haste-map-26.3.0.tgz#c51a3b40100d53ab777bfdad382d2e7a00e5c726" + integrity sha512-DHWBpTJgJhLLGwE5Z1ZaqLTYqeODQIZpby0zMBsCU9iRFHYyhklYqP4EiG73j5dkbaAdSZhgB938mL51Q5LeZA== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" "@types/graceful-fs" "^4.1.2" + "@types/node" "*" anymatch "^3.0.3" fb-watchman "^2.0.0" graceful-fs "^4.2.4" - jest-serializer "^26.0.0" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-regex-util "^26.0.0" + jest-serializer "^26.3.0" + jest-util "^26.3.0" + jest-worker "^26.3.0" micromatch "^4.0.2" sane "^4.0.3" walker "^1.0.7" - which "^2.0.2" optionalDependencies: fsevents "^2.1.2" -jest-jasmine2@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.0.1.tgz#947c40ee816636ba23112af3206d6fa7b23c1c1c" - integrity sha512-ILaRyiWxiXOJ+RWTKupzQWwnPaeXPIoLS5uW41h18varJzd9/7I0QJGqg69fhTT1ev9JpSSo9QtalriUN0oqOg== +jest-jasmine2@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.4.2.tgz#18a9d5bec30904267ac5e9797570932aec1e2257" + integrity sha512-z7H4EpCldHN1J8fNgsja58QftxBSL+JcwZmaXIvV9WKIM+x49F4GLHu/+BQh2kzRKHAgaN/E82od+8rTOBPyPA== dependencies: "@babel/traverse" "^7.1.0" - "@jest/environment" "^26.0.1" - "@jest/source-map" "^26.0.0" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/environment" "^26.3.0" + "@jest/source-map" "^26.3.0" + "@jest/test-result" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" chalk "^4.0.0" co "^4.6.0" - expect "^26.0.1" + expect "^26.4.2" is-generator-fn "^2.0.0" - jest-each "^26.0.1" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" - jest-runtime "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - pretty-format "^26.0.1" + jest-each "^26.4.2" + jest-matcher-utils "^26.4.2" + jest-message-util "^26.3.0" + jest-runtime "^26.4.2" + jest-snapshot "^26.4.2" + jest-util "^26.3.0" + pretty-format "^26.4.2" throat "^5.0.0" -jest-leak-detector@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.0.1.tgz#79b19ab3f41170e0a78eb8fa754a116d3447fb8c" - integrity sha512-93FR8tJhaYIWrWsbmVN1pQ9ZNlbgRpfvrnw5LmgLRX0ckOJ8ut/I35CL7awi2ecq6Ca4lL59bEK9hr7nqoHWPA== +jest-leak-detector@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-leak-detector/-/jest-leak-detector-26.4.2.tgz#c73e2fa8757bf905f6f66fb9e0070b70fa0f573f" + integrity sha512-akzGcxwxtE+9ZJZRW+M2o+nTNnmQZxrHJxX/HjgDaU5+PLmY1qnQPnMjgADPGCRPhB+Yawe1iij0REe+k/aHoA== dependencies: - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + jest-get-type "^26.3.0" + pretty-format "^26.4.2" jest-matcher-utils@^25.1.0: version "25.1.0" @@ -13515,23 +13555,23 @@ jest-matcher-utils@^25.1.0: jest-get-type "^25.1.0" pretty-format "^25.1.0" -jest-matcher-utils@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.0.1.tgz#12e1fc386fe4f14678f4cc8dbd5ba75a58092911" - integrity sha512-PUMlsLth0Azen8Q2WFTwnSkGh2JZ8FYuwijC8NR47vXKpsrKmA1wWvgcj1CquuVfcYiDEdj985u5Wmg7COEARw== +jest-matcher-utils@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-26.4.2.tgz#fa81f3693f7cb67e5fc1537317525ef3b85f4b06" + integrity sha512-KcbNqWfWUG24R7tu9WcAOKKdiXiXCbMvQYT6iodZ9k1f7065k0keUOW6XpJMMvah+hTfqkhJhRXmA3r3zMAg0Q== dependencies: chalk "^4.0.0" - jest-diff "^26.0.1" - jest-get-type "^26.0.0" - pretty-format "^26.0.1" + jest-diff "^26.4.2" + jest-get-type "^26.3.0" + pretty-format "^26.4.2" -jest-message-util@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.0.1.tgz#07af1b42fc450b4cc8e90e4c9cef11b33ce9b0ac" - integrity sha512-CbK8uQREZ8umUfo8+zgIfEt+W7HAHjQCoRaNs4WxKGhAYBGwEyvxuK81FXa7VeB9pwDEXeeKOB2qcsNVCAvB7Q== +jest-message-util@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-message-util/-/jest-message-util-26.3.0.tgz#3bdb538af27bb417f2d4d16557606fd082d5841a" + integrity sha512-xIavRYqr4/otGOiLxLZGj3ieMmjcNE73Ui+LdSW/Y790j5acqCsAdDiLIbzHCZMpN07JOENRWX5DcU+OQ+TjTA== dependencies: "@babel/code-frame" "^7.0.0" - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" "@types/stack-utils" "^1.0.1" chalk "^4.0.0" graceful-fs "^4.2.4" @@ -13539,164 +13579,169 @@ jest-message-util@^26.0.1: slash "^3.0.0" stack-utils "^2.0.2" -jest-mock@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.0.1.tgz#7fd1517ed4955397cf1620a771dc2d61fad8fd40" - integrity sha512-MpYTBqycuPYSY6xKJognV7Ja46/TeRbAZept987Zp+tuJvMN0YBWyyhG9mXyYQaU3SBI0TUlSaO5L3p49agw7Q== +jest-mock@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-mock/-/jest-mock-26.3.0.tgz#ee62207c3c5ebe5f35b760e1267fee19a1cfdeba" + integrity sha512-PeaRrg8Dc6mnS35gOo/CbZovoDPKAeB1FICZiuagAgGvbWdNNyjQjkOaGUa/3N3JtpQ/Mh9P4A2D4Fv51NnP8Q== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" + "@types/node" "*" -jest-pnp-resolver@^1.2.1: - version "1.2.1" - resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.1.tgz#ecdae604c077a7fbc70defb6d517c3c1c898923a" - integrity sha512-pgFw2tm54fzgYvc/OHrnysABEObZCUNFnhjoRjaVOCN8NYc032/gVjPaHD4Aq6ApkSieWtfKAFQtmDKAmhupnQ== +jest-pnp-resolver@^1.2.2: + version "1.2.2" + resolved "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz#b704ac0ae028a89108a4d040b3f919dfddc8e33c" + integrity sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w== jest-regex-util@^26.0.0: version "26.0.0" resolved "https://registry.npmjs.org/jest-regex-util/-/jest-regex-util-26.0.0.tgz#d25e7184b36e39fd466c3bc41be0971e821fee28" integrity sha512-Gv3ZIs/nA48/Zvjrl34bf+oD76JHiGDUxNOVgUjh3j890sblXryjY4rss71fPtD/njchl6PSE2hIhvyWa1eT0A== -jest-resolve-dependencies@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.0.1.tgz#607ba7ccc32151d185a477cff45bf33bce417f0b" - integrity sha512-9d5/RS/ft0vB/qy7jct/qAhzJsr6fRQJyGAFigK3XD4hf9kIbEH5gks4t4Z7kyMRhowU6HWm/o8ILqhaHdSqLw== +jest-resolve-dependencies@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-resolve-dependencies/-/jest-resolve-dependencies-26.4.2.tgz#739bdb027c14befb2fe5aabbd03f7bab355f1dc5" + integrity sha512-ADHaOwqEcVc71uTfySzSowA/RdxUpCxhxa2FNLiin9vWLB1uLPad3we+JSSROq5+SrL9iYPdZZF8bdKM7XABTQ== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" jest-regex-util "^26.0.0" - jest-snapshot "^26.0.1" + jest-snapshot "^26.4.2" -jest-resolve@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.0.1.tgz#21d1ee06f9ea270a343a8893051aeed940cde736" - integrity sha512-6jWxk0IKZkPIVTvq6s72RH735P8f9eCJW3IM5CX/SJFeKq1p2cZx0U49wf/SdMlhaB/anann5J2nCJj6HrbezQ== +jest-resolve@^26.4.0: + version "26.4.0" + resolved "https://registry.npmjs.org/jest-resolve/-/jest-resolve-26.4.0.tgz#6dc0af7fb93e65b73fec0368ca2b76f3eb59a6d7" + integrity sha512-bn/JoZTEXRSlEx3+SfgZcJAVuTMOksYq9xe9O6s4Ekg84aKBObEaVXKOEilULRqviSLAYJldnoWV9c07kwtiCg== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" chalk "^4.0.0" graceful-fs "^4.2.4" - jest-pnp-resolver "^1.2.1" - jest-util "^26.0.1" + jest-pnp-resolver "^1.2.2" + jest-util "^26.3.0" read-pkg-up "^7.0.1" resolve "^1.17.0" slash "^3.0.0" -jest-runner@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.0.1.tgz#ea03584b7ae4bacfb7e533d680a575a49ae35d50" - integrity sha512-CApm0g81b49Znm4cZekYQK67zY7kkB4umOlI2Dx5CwKAzdgw75EN+ozBHRvxBzwo1ZLYZ07TFxkaPm+1t4d8jA== +jest-runner@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-runner/-/jest-runner-26.4.2.tgz#c3ec5482c8edd31973bd3935df5a449a45b5b853" + integrity sha512-FgjDHeVknDjw1gRAYaoUoShe1K3XUuFMkIaXbdhEys+1O4bEJS8Avmn4lBwoMfL8O5oFTdWYKcf3tEJyyYyk8g== dependencies: - "@jest/console" "^26.0.1" - "@jest/environment" "^26.0.1" - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.3.0" + "@jest/environment" "^26.3.0" + "@jest/test-result" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" chalk "^4.0.0" + emittery "^0.7.1" exit "^0.1.2" graceful-fs "^4.2.4" - jest-config "^26.0.1" + jest-config "^26.4.2" jest-docblock "^26.0.0" - jest-haste-map "^26.0.1" - jest-jasmine2 "^26.0.1" - jest-leak-detector "^26.0.1" - jest-message-util "^26.0.1" - jest-resolve "^26.0.1" - jest-runtime "^26.0.1" - jest-util "^26.0.1" - jest-worker "^26.0.0" + jest-haste-map "^26.3.0" + jest-leak-detector "^26.4.2" + jest-message-util "^26.3.0" + jest-resolve "^26.4.0" + jest-runtime "^26.4.2" + jest-util "^26.3.0" + jest-worker "^26.3.0" source-map-support "^0.5.6" throat "^5.0.0" -jest-runtime@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.0.1.tgz#a121a6321235987d294168e282d52b364d7d3f89" - integrity sha512-Ci2QhYFmANg5qaXWf78T2Pfo6GtmIBn2rRaLnklRyEucmPccmCKvS9JPljcmtVamsdMmkyNkVFb9pBTD6si9Lw== +jest-runtime@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-runtime/-/jest-runtime-26.4.2.tgz#94ce17890353c92e4206580c73a8f0c024c33c42" + integrity sha512-4Pe7Uk5a80FnbHwSOk7ojNCJvz3Ks2CNQWT5Z7MJo4tX0jb3V/LThKvD9tKPNVNyeMH98J/nzGlcwc00R2dSHQ== dependencies: - "@jest/console" "^26.0.1" - "@jest/environment" "^26.0.1" - "@jest/fake-timers" "^26.0.1" - "@jest/globals" "^26.0.1" - "@jest/source-map" "^26.0.0" - "@jest/test-result" "^26.0.1" - "@jest/transform" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/console" "^26.3.0" + "@jest/environment" "^26.3.0" + "@jest/fake-timers" "^26.3.0" + "@jest/globals" "^26.4.2" + "@jest/source-map" "^26.3.0" + "@jest/test-result" "^26.3.0" + "@jest/transform" "^26.3.0" + "@jest/types" "^26.3.0" "@types/yargs" "^15.0.0" chalk "^4.0.0" collect-v8-coverage "^1.0.0" exit "^0.1.2" glob "^7.1.3" graceful-fs "^4.2.4" - jest-config "^26.0.1" - jest-haste-map "^26.0.1" - jest-message-util "^26.0.1" - jest-mock "^26.0.1" + jest-config "^26.4.2" + jest-haste-map "^26.3.0" + jest-message-util "^26.3.0" + jest-mock "^26.3.0" jest-regex-util "^26.0.0" - jest-resolve "^26.0.1" - jest-snapshot "^26.0.1" - jest-util "^26.0.1" - jest-validate "^26.0.1" + jest-resolve "^26.4.0" + jest-snapshot "^26.4.2" + jest-util "^26.3.0" + jest-validate "^26.4.2" slash "^3.0.0" strip-bom "^4.0.0" yargs "^15.3.1" -jest-serializer@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.0.0.tgz#f6c521ddb976943b93e662c0d4d79245abec72a3" - integrity sha512-sQGXLdEGWFAE4wIJ2ZaIDb+ikETlUirEOBsLXdoBbeLhTHkZUJwgk3+M8eyFizhM6le43PDCCKPA1hzkSDo4cQ== +jest-serializer@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-serializer/-/jest-serializer-26.3.0.tgz#1c9d5e1b74d6e5f7e7f9627080fa205d976c33ef" + integrity sha512-IDRBQBLPlKa4flg77fqg0n/pH87tcRKwe8zxOVTWISxGpPHYkRZ1dXKyh04JOja7gppc60+soKVZ791mruVdow== dependencies: + "@types/node" "*" graceful-fs "^4.2.4" -jest-snapshot@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.0.1.tgz#1baa942bd83d47b837a84af7fcf5fd4a236da399" - integrity sha512-jxd+cF7+LL+a80qh6TAnTLUZHyQoWwEHSUFJjkw35u3Gx+BZUNuXhYvDqHXr62UQPnWo2P6fvQlLjsU93UKyxA== +jest-snapshot@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-snapshot/-/jest-snapshot-26.4.2.tgz#87d3ac2f2bd87ea8003602fbebd8fcb9e94104f6" + integrity sha512-N6Uub8FccKlf5SBFnL2Ri/xofbaA68Cc3MGjP/NuwgnsvWh+9hLIR/DhrxbSiKXMY9vUW5dI6EW1eHaDHqe9sg== dependencies: "@babel/types" "^7.0.0" - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" "@types/prettier" "^2.0.0" chalk "^4.0.0" - expect "^26.0.1" + expect "^26.4.2" graceful-fs "^4.2.4" - jest-diff "^26.0.1" - jest-get-type "^26.0.0" - jest-matcher-utils "^26.0.1" - jest-message-util "^26.0.1" - jest-resolve "^26.0.1" - make-dir "^3.0.0" + jest-diff "^26.4.2" + jest-get-type "^26.3.0" + jest-haste-map "^26.3.0" + jest-matcher-utils "^26.4.2" + jest-message-util "^26.3.0" + jest-resolve "^26.4.0" natural-compare "^1.4.0" - pretty-format "^26.0.1" + pretty-format "^26.4.2" semver "^7.3.2" -jest-util@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.0.1.tgz#72c4c51177b695fdd795ca072a6f94e3d7cef00a" - integrity sha512-byQ3n7ad1BO/WyFkYvlWQHTsomB6GIewBh8tlGtusiylAlaxQ1UpS0XYH0ngOyhZuHVLN79Qvl6/pMiDMSSG1g== +jest-util@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-util/-/jest-util-26.3.0.tgz#a8974b191df30e2bf523ebbfdbaeb8efca535b3e" + integrity sha512-4zpn6bwV0+AMFN0IYhH/wnzIQzRaYVrz1A8sYnRnj4UXDXbOVtWmlaZkO9mipFqZ13okIfN87aDoJWB7VH6hcw== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" + "@types/node" "*" chalk "^4.0.0" graceful-fs "^4.2.4" is-ci "^2.0.0" - make-dir "^3.0.0" + micromatch "^4.0.2" -jest-validate@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.0.1.tgz#a62987e1da5b7f724130f904725e22f4e5b2e23c" - integrity sha512-u0xRc+rbmov/VqXnX3DlkxD74rHI/CfS5xaV2VpeaVySjbb1JioNVOyly5b56q2l9ZKe7bVG5qWmjfctkQb0bA== +jest-validate@^26.4.2: + version "26.4.2" + resolved "https://registry.npmjs.org/jest-validate/-/jest-validate-26.4.2.tgz#e871b0dfe97747133014dcf6445ee8018398f39c" + integrity sha512-blft+xDX7XXghfhY0mrsBCYhX365n8K5wNDC4XAcNKqqjEzsRUSXP44m6PL0QJEW2crxQFLLztVnJ4j7oPlQrQ== dependencies: - "@jest/types" "^26.0.1" + "@jest/types" "^26.3.0" camelcase "^6.0.0" chalk "^4.0.0" - jest-get-type "^26.0.0" + jest-get-type "^26.3.0" leven "^3.1.0" - pretty-format "^26.0.1" + pretty-format "^26.4.2" -jest-watcher@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.0.1.tgz#5b5e3ebbdf10c240e22a98af66d645631afda770" - integrity sha512-pdZPydsS8475f89kGswaNsN3rhP6lnC3/QDCppP7bg1L9JQz7oU9Mb/5xPETk1RHDCWeqmVC47M4K5RR7ejxFw== +jest-watcher@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-watcher/-/jest-watcher-26.3.0.tgz#f8ef3068ddb8af160ef868400318dc4a898eed08" + integrity sha512-XnLdKmyCGJ3VoF6G/p5ohbJ04q/vv5aH9ENI+i6BL0uu9WWB6Z7Z2lhQQk0d2AVZcRGp1yW+/TsoToMhBFPRdQ== dependencies: - "@jest/test-result" "^26.0.1" - "@jest/types" "^26.0.1" + "@jest/test-result" "^26.3.0" + "@jest/types" "^26.3.0" + "@types/node" "*" ansi-escapes "^4.2.1" chalk "^4.0.0" - jest-util "^26.0.1" + jest-util "^26.3.0" string-length "^4.0.1" jest-worker@^25.1.0: @@ -13707,22 +13752,23 @@ jest-worker@^25.1.0: merge-stream "^2.0.0" supports-color "^7.0.0" -jest-worker@^26.0.0: - version "26.0.0" - resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.0.0.tgz#4920c7714f0a96c6412464718d0c58a3df3fb066" - integrity sha512-pPaYa2+JnwmiZjK9x7p9BoZht+47ecFCDFA/CJxspHzeDvQcfVBLWzCiWyo+EGrSiQMWZtCFo9iSvMZnAAo8vw== +jest-worker@^26.3.0: + version "26.3.0" + resolved "https://registry.npmjs.org/jest-worker/-/jest-worker-26.3.0.tgz#7c8a97e4f4364b4f05ed8bca8ca0c24de091871f" + integrity sha512-Vmpn2F6IASefL+DVBhPzI2J9/GJUsqzomdeN+P+dK8/jKxbh8R3BtFnx3FIta7wYlPU62cpJMJQo4kuOowcMnw== dependencies: + "@types/node" "*" merge-stream "^2.0.0" supports-color "^7.0.0" jest@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/jest/-/jest-26.0.1.tgz#5c51a2e58dff7525b65f169721767173bf832694" - integrity sha512-29Q54kn5Bm7ZGKIuH2JRmnKl85YRigp0o0asTc6Sb6l2ch1DCXIeZTLLFy9ultJvhkTqbswF5DEx4+RlkmCxWg== + version "26.4.2" + resolved "https://registry.npmjs.org/jest/-/jest-26.4.2.tgz#7e8bfb348ec33f5459adeaffc1a25d5752d9d312" + integrity sha512-LLCjPrUh98Ik8CzW8LLVnSCfLaiY+wbK53U7VxnFSX7Q+kWC4noVeDvGWIFw0Amfq1lq2VfGm7YHWSLBV62MJw== dependencies: - "@jest/core" "^26.0.1" + "@jest/core" "^26.4.2" import-local "^3.0.2" - jest-cli "^26.0.1" + jest-cli "^26.4.2" jose@^1.27.1: version "1.27.1" @@ -15839,16 +15885,16 @@ node-modules-regexp@^1.0.0: resolved "https://registry.npmjs.org/node-modules-regexp/-/node-modules-regexp-1.0.0.tgz#8d9dbe28964a4ac5712e9131642107c71e90ec40" integrity sha1-jZ2+KJZKSsVxLpExZCEHxx6Q7EA= -node-notifier@^7.0.0: - version "7.0.0" - resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-7.0.0.tgz#513bc42f2aa3a49fce1980a7ff375957c71f718a" - integrity sha512-y8ThJESxsHcak81PGpzWwQKxzk+5YtP3IxR8AYdpXQ1IB6FmcVzFdZXrkPin49F/DKUCfeeiziB8ptY9npzGuA== +node-notifier@^8.0.0: + version "8.0.0" + resolved "https://registry.npmjs.org/node-notifier/-/node-notifier-8.0.0.tgz#a7eee2d51da6d0f7ff5094bc7108c911240c1620" + integrity sha512-46z7DUmcjoYdaWyXouuFNNfUo6eFa94t23c53c+lG/9Cvauk4a98rAUp9672X5dxGdQmLpPzTxzu8f/OeEPaFA== dependencies: growly "^1.3.0" - is-wsl "^2.1.1" - semver "^7.2.1" + is-wsl "^2.2.0" + semver "^7.3.2" shellwords "^0.1.1" - uuid "^7.0.3" + uuid "^8.3.0" which "^2.0.2" node-pre-gyp@^0.11.0: @@ -17817,16 +17863,6 @@ pretty-format@^25.1.0, pretty-format@^25.2.1, pretty-format@^25.5.0: ansi-styles "^4.0.0" react-is "^16.12.0" -pretty-format@^26.0.1: - version "26.0.1" - resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.0.1.tgz#a4fe54fe428ad2fd3413ca6bbd1ec8c2e277e197" - integrity sha512-SWxz6MbupT3ZSlL0Po4WF/KujhQaVehijR2blyRDCzk9e45EaYMVhMBn49fnRuHxtkSpXTes1GxNpVmH86Bxfw== - dependencies: - "@jest/types" "^26.0.1" - ansi-regex "^5.0.0" - ansi-styles "^4.0.0" - react-is "^16.12.0" - pretty-format@^26.4.2: version "26.4.2" resolved "https://registry.npmjs.org/pretty-format/-/pretty-format-26.4.2.tgz#d081d032b398e801e2012af2df1214ef75a81237" @@ -22312,7 +22348,7 @@ uuid@^7.0.3: resolved "https://registry.npmjs.org/uuid/-/uuid-7.0.3.tgz#c5c9f2c8cf25dc0a372c4df1441c41f5bd0c680b" integrity sha512-DPSke0pXhTZgoF/d+WSt2QaKMCFSfx7QegxEWT+JOuHF5aWrKEn0G+ztjuJg/gG8/ItK+rbPCD/yNv8yyih6Cg== -uuid@^8.0.0, uuid@^8.2.0: +uuid@^8.0.0, uuid@^8.2.0, uuid@^8.3.0: version "8.3.0" resolved "https://registry.npmjs.org/uuid/-/uuid-8.3.0.tgz#ab738085ca22dc9a8c92725e459b1d507df5d6ea" integrity sha512-fX6Z5o4m6XsXBdli9g7DtWgAx+osMsRRZFKma1mIUsLCz6vRvv+pz5VNbyu9UEDzpMWulZfvpgb/cmDXVulYFQ== @@ -22322,10 +22358,10 @@ v8-compile-cache@^2.0.3: resolved "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.0.tgz#e14de37b31a6d194f5690d67efc4e7f6fc6ab30e" integrity sha512-usZBT3PW+LOjM25wbqIlZwPeJV+3OSz3M1k1Ws8snlW39dZyYL9lOGC5FgPVHfk0jKmjiDV8Z0mIbVQPiwFs7g== -v8-to-istanbul@^4.1.3: - version "4.1.4" - resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-4.1.4.tgz#b97936f21c0e2d9996d4985e5c5156e9d4e49cd6" - integrity sha512-Rw6vJHj1mbdK8edjR7+zuJrpDtKIgNdAvTSAcpYfgMIw+u2dPDntD3dgN4XQFLU2/fvFQdzj+EeSGfd/jnY5fQ== +v8-to-istanbul@^5.0.1: + version "5.0.1" + resolved "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-5.0.1.tgz#0608f5b49a481458625edb058488607f25498ba5" + integrity sha512-mbDNjuDajqYe3TXFk5qxcQy8L1msXNE37WTlLoqqpBfRsimbNcrlhQlDPntmECEcUvdC+AQ8CyMMf6EUx1r74Q== dependencies: "@types/istanbul-lib-coverage" "^2.0.1" convert-source-map "^1.6.0" From a7e922250aa1a63798a07a1798896b02793461ad Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Tue, 1 Sep 2020 14:21:05 +0200 Subject: [PATCH 105/132] Add instructions on how to refresh existing release PR --- docs/plugins/publishing.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/plugins/publishing.md b/docs/plugins/publishing.md index 1d16ebb047..6fee4f9b1d 100644 --- a/docs/plugins/publishing.md +++ b/docs/plugins/publishing.md @@ -39,4 +39,18 @@ $ git push origin -u new-release And then create a PR. Once the PR is approved and merged into master, the master build will publish new versions of all bumped packages. +### Include new changes in existing release PR + +If you want to include some last minute changes to an existing release PR, +follow these instructions: + +```sh +$ git checkout master +$ git pull +$ git checkout new-release +$ git reset --hard master +$ yarn release +$ git push --force +``` + [Back to Docs](../README.md) From 9f59ee821a98ad66d7193d88dca22894100006e7 Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Wed, 2 Sep 2020 11:07:33 +0200 Subject: [PATCH 106/132] Add new log format --- packages/backend-common/package.json | 3 +- .../backend-common/src/logging/formats.ts | 35 +++++++++++++++++++ .../backend-common/src/logging/rootLogger.ts | 7 ++-- 3 files changed, 39 insertions(+), 6 deletions(-) create mode 100644 packages/backend-common/src/logging/formats.ts diff --git a/packages/backend-common/package.json b/packages/backend-common/package.json index 92d301d7e3..248e638ead 100644 --- a/packages/backend-common/package.json +++ b/packages/backend-common/package.json @@ -46,7 +46,8 @@ "prom-client": "^12.0.0", "selfsigned": "^1.10.7", "stoppable": "^1.1.0", - "winston": "^3.2.1" + "winston": "^3.2.1", + "logform": "^2.1.1" }, "peerDependencies": { "pg-connection-string": "^2.3.0" diff --git a/packages/backend-common/src/logging/formats.ts b/packages/backend-common/src/logging/formats.ts new file mode 100644 index 0000000000..5f00448744 --- /dev/null +++ b/packages/backend-common/src/logging/formats.ts @@ -0,0 +1,35 @@ +/* + * Copyright 2020 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 * as winston from 'winston'; +import { TransformableInfo } from 'logform'; + +const coloredTemplate = (info: TransformableInfo) => { + const { timestamp, level, message, plugin, service } = info; + const colorizer = winston.format.colorize(); + const prefix = plugin || service; + const timestampColor = colorizer.colorize('timestamp', timestamp); + const prefixColor = colorizer.colorize('prefix', prefix); + + return `${timestampColor} ${prefixColor} ${level} ${message}`; +}; + +export const coloredFormat = winston.format.combine( + winston.format.timestamp(), + winston.format.colorize({ + colors: { timestamp: 'dim', prefix: 'blue' }, + }), + winston.format.printf(coloredTemplate), +); diff --git a/packages/backend-common/src/logging/rootLogger.ts b/packages/backend-common/src/logging/rootLogger.ts index fd2f3a1c8b..306ed23444 100644 --- a/packages/backend-common/src/logging/rootLogger.ts +++ b/packages/backend-common/src/logging/rootLogger.ts @@ -14,17 +14,14 @@ * limitations under the License. */ import * as winston from 'winston'; +import { coloredFormat } from './formats'; let rootLogger: winston.Logger = winston.createLogger({ level: process.env.LOG_LEVEL || 'info', format: process.env.NODE_ENV === 'production' ? winston.format.json() - : winston.format.combine( - winston.format.colorize(), - winston.format.timestamp(), - winston.format.simple(), - ), + : coloredFormat, defaultMeta: { service: 'backstage' }, transports: [ new winston.transports.Console({ From 7d8058a1ef0fab41a6801e8f3ecbf616098a2faf Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Wed, 2 Sep 2020 11:08:40 +0200 Subject: [PATCH 107/132] Make http-proxy-middleware log to supplied logger --- plugins/proxy-backend/src/service/router.ts | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/plugins/proxy-backend/src/service/router.ts b/plugins/proxy-backend/src/service/router.ts index 5273b2c806..5c5e17b872 100644 --- a/plugins/proxy-backend/src/service/router.ts +++ b/plugins/proxy-backend/src/service/router.ts @@ -34,6 +34,7 @@ export interface RouterOptions { // given config. function buildMiddleware( pathPrefix: string, + logger: Logger, route: string, config: string | ProxyConfig, ): Proxy { @@ -54,6 +55,9 @@ function buildMiddleware( fullConfig.changeOrigin = true; } + // Attach the logger to the proxy config + fullConfig.logProvider = () => logger; + return createProxyMiddleware(fullConfig); } @@ -66,7 +70,12 @@ export async function createRouter( Object.entries(proxyConfig).forEach(([route, proxyRouteConfig]) => { router.use( route, - buildMiddleware(options.pathPrefix, route, proxyRouteConfig), + buildMiddleware( + options.pathPrefix, + options.logger, + route, + proxyRouteConfig, + ), ); }); From dac617b436090565a89a0f5f55b0a7471e3284f3 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 2 Sep 2020 11:39:59 +0200 Subject: [PATCH 108/132] create-app: remove duplicate techdocs plugin from app package --- .../templates/default-app/packages/app/package.json.hbs | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index 57ec119ed5..c432f27a3a 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -17,7 +17,6 @@ "@backstage/plugin-lighthouse": "^{{version}}", "@backstage/plugin-tech-radar": "^{{version}}", "@backstage/plugin-github-actions": "^{{version}}", - "@backstage/plugin-techdocs": "^{{version}}", "@backstage/plugin-sentry": "^{{version}}", "@backstage/test-utils": "^{{version}}", "@backstage/theme": "^{{version}}", From 992dfd545212f82cdeb83e2fc48921a4faa45e99 Mon Sep 17 00:00:00 2001 From: "althaf.hameez" Date: Wed, 2 Sep 2020 18:13:25 +0800 Subject: [PATCH 109/132] rebase --- ADOPTERS.md | 1 + 1 file changed, 1 insertion(+) diff --git a/ADOPTERS.md b/ADOPTERS.md index dc02ad3b85..0ba3e3196b 100644 --- a/ADOPTERS.md +++ b/ADOPTERS.md @@ -12,3 +12,4 @@ | [Voi](https://www.voiscooters.com/) | [@K-Phoen](https://github.com/K-Phoen) | Developer portal, main gateway to our infrastructure, documentation and internal tooling. | | [Talkdesk](https://www.talkdesk.com) | [@jaime-talkdesk](https://github.com/jaime-talkdesk) | Initial work for Engineering Portal and Self Provisioning to R&D | | [Wealthsimple](https://www.wealthsimple.com) | [@andrewthauer](https://github.com/andrewthauer) | Developer portal, service catalog, documentation and tooling | +| [Grab](https://www.grab.com) | [@althafh](https://github.com/althafh) | Initial work as a unified interface for all of Grab's internal tooling | From 0af1ce25caf544271e186f74f9429eecc99e1cd6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 2 Sep 2020 13:01:27 +0200 Subject: [PATCH 110/132] techdocs-backend: use resolvePackagePath instead of __dirname --- plugins/techdocs-backend/src/service/router.ts | 12 +++++++----- .../src/techdocs/stages/publish/local.ts | 8 ++++---- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/plugins/techdocs-backend/src/service/router.ts b/plugins/techdocs-backend/src/service/router.ts index f93d182edc..bb26ea19b8 100644 --- a/plugins/techdocs-backend/src/service/router.ts +++ b/plugins/techdocs-backend/src/service/router.ts @@ -19,7 +19,6 @@ import express from 'express'; import Knex from 'knex'; import fetch from 'node-fetch'; import { Config } from '@backstage/config'; -import path from 'path'; import Docker from 'dockerode'; import { GeneratorBuilder, @@ -27,6 +26,7 @@ import { PublisherBase, LocalPublish, } from '../techdocs'; +import { resolvePackagePath } from '@backstage/backend-common'; import { Entity } from '@backstage/catalog-model'; type RouterOptions = { @@ -39,6 +39,11 @@ type RouterOptions = { dockerClient: Docker; }; +const staticDocsDir = resolvePackagePath( + '@backstage/plugin-techdocs-backend', + 'static/docs', +); + export async function createRouter({ preparers, generators, @@ -102,10 +107,7 @@ export async function createRouter({ }); if (publisher instanceof LocalPublish) { - router.use( - '/static/docs/', - express.static(path.resolve(__dirname, `../../static/docs`)), - ); + router.use('/static/docs/', express.static(staticDocsDir)); router.use( '/static/docs/:kind/:namespace/:name', async (req, res, next) => { diff --git a/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts index be9e1876d8..e33c6fa17d 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/publish/local.ts @@ -14,10 +14,10 @@ * limitations under the License. */ import fs from 'fs-extra'; -import path from 'path'; import { Logger } from 'winston'; import { Entity } from '@backstage/catalog-model'; import { PublisherBase } from './types'; +import { resolvePackagePath } from '@backstage/backend-common'; export class LocalPublish implements PublisherBase { private readonly logger: Logger; @@ -39,9 +39,9 @@ export class LocalPublish implements PublisherBase { | { remoteUrl: string } { const entityNamespace = entity.metadata.namespace ?? 'default'; - const publishDir = path.resolve( - __dirname, - '../../../../static/docs/', + const publishDir = resolvePackagePath( + '@backstage/plugin-techdocs-backend', + 'static/docs', entity.kind, entityNamespace, entity.metadata.name, From 8ac23713029bfbab7d36261f62de8468a5ef1f9b Mon Sep 17 00:00:00 2001 From: Marcus Eide Date: Wed, 2 Sep 2020 14:03:35 +0200 Subject: [PATCH 111/132] Remove sentry from create-app --- .../templates/default-app/app-config.yaml.hbs | 3 --- .../default-app/packages/app/package.json.hbs | 1 - .../default-app/packages/app/src/plugins.ts | 1 - .../packages/backend/package.json.hbs | 1 - .../default-app/packages/backend/src/index.ts | 3 --- .../packages/backend/src/plugins/sentry.ts | 22 ------------------- 6 files changed, 31 deletions(-) delete mode 100644 packages/create-app/templates/default-app/packages/backend/src/plugins/sentry.ts diff --git a/packages/create-app/templates/default-app/app-config.yaml.hbs b/packages/create-app/templates/default-app/app-config.yaml.hbs index 64185e43d3..d5a8e9b200 100644 --- a/packages/create-app/templates/default-app/app-config.yaml.hbs +++ b/packages/create-app/templates/default-app/app-config.yaml.hbs @@ -44,9 +44,6 @@ proxy: techdocs: storageUrl: http://localhost:7000/techdocs/static/docs -sentry: - organization: spotify - lighthouse: baseUrl: http://localhost:3003 diff --git a/packages/create-app/templates/default-app/packages/app/package.json.hbs b/packages/create-app/templates/default-app/packages/app/package.json.hbs index c432f27a3a..9d4993c4dc 100644 --- a/packages/create-app/templates/default-app/packages/app/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/app/package.json.hbs @@ -17,7 +17,6 @@ "@backstage/plugin-lighthouse": "^{{version}}", "@backstage/plugin-tech-radar": "^{{version}}", "@backstage/plugin-github-actions": "^{{version}}", - "@backstage/plugin-sentry": "^{{version}}", "@backstage/test-utils": "^{{version}}", "@backstage/theme": "^{{version}}", "history": "^5.0.0", diff --git a/packages/create-app/templates/default-app/packages/app/src/plugins.ts b/packages/create-app/templates/default-app/packages/app/src/plugins.ts index 0d54c55649..c787ac2166 100644 --- a/packages/create-app/templates/default-app/packages/app/src/plugins.ts +++ b/packages/create-app/templates/default-app/packages/app/src/plugins.ts @@ -7,4 +7,3 @@ export { plugin as Circleci } from '@backstage/plugin-circleci'; export { plugin as LighthousePlugin } from '@backstage/plugin-lighthouse'; export { plugin as TechRadar } from '@backstage/plugin-tech-radar'; export { plugin as GithubActions } from '@backstage/plugin-github-actions'; -export { plugin as Sentry } from '@backstage/plugin-sentry'; diff --git a/packages/create-app/templates/default-app/packages/backend/package.json.hbs b/packages/create-app/templates/default-app/packages/backend/package.json.hbs index a978349b02..53bfd6d990 100644 --- a/packages/create-app/templates/default-app/packages/backend/package.json.hbs +++ b/packages/create-app/templates/default-app/packages/backend/package.json.hbs @@ -26,7 +26,6 @@ "@backstage/plugin-proxy-backend": "^{{version}}", "@backstage/plugin-rollbar-backend": "^{{version}}", "@backstage/plugin-scaffolder-backend": "^{{version}}", - "@backstage/plugin-sentry-backend": "^{{version}}", "@backstage/plugin-techdocs-backend": "^{{version}}", "@octokit/rest": "^18.0.0", "dockerode": "^3.2.0", diff --git a/packages/create-app/templates/default-app/packages/backend/src/index.ts b/packages/create-app/templates/default-app/packages/backend/src/index.ts index 6ba6c4dc8e..6a014727e9 100644 --- a/packages/create-app/templates/default-app/packages/backend/src/index.ts +++ b/packages/create-app/templates/default-app/packages/backend/src/index.ts @@ -20,7 +20,6 @@ import identity from './plugins/identity'; import scaffolder from './plugins/scaffolder'; import proxy from './plugins/proxy'; import techdocs from './plugins/techdocs'; -import sentry from './plugins/sentry'; import { PluginEnvironment } from './types'; function makeCreateEnv(loadedConfigs: AppConfig[]) { @@ -51,13 +50,11 @@ async function main() { const identityEnv = useHotMemoize(module, () => createEnv('identity')); const proxyEnv = useHotMemoize(module, () => createEnv('proxy')); const techdocsEnv = useHotMemoize(module, () => createEnv('techdocs')); - const sentryEnv = useHotMemoize(module, () => createEnv('sentry')); const service = createServiceBuilder(module) .loadConfig(configReader) .addRouter('/catalog', await catalog(catalogEnv)) .addRouter('/scaffolder', await scaffolder(scaffolderEnv)) - .addRouter('/sentry', await sentry(sentryEnv)) .addRouter('/auth', await auth(authEnv)) .addRouter('/identity', await identity(identityEnv)) .addRouter('/techdocs', await techdocs(techdocsEnv)) diff --git a/packages/create-app/templates/default-app/packages/backend/src/plugins/sentry.ts b/packages/create-app/templates/default-app/packages/backend/src/plugins/sentry.ts deleted file mode 100644 index 5cd0e55761..0000000000 --- a/packages/create-app/templates/default-app/packages/backend/src/plugins/sentry.ts +++ /dev/null @@ -1,22 +0,0 @@ -/* - * Copyright 2020 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 { createRouter } from '@backstage/plugin-sentry-backend'; -import type { PluginEnvironment } from '../types'; - -export default async function createPlugin({ logger }: PluginEnvironment) { - return await createRouter(logger); -} From c08a83260e7da58b4458965e4e6022756f1ba6f6 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 2 Sep 2020 14:21:58 +0200 Subject: [PATCH 112/132] auth-backend: use correct interface in factory return type --- plugins/auth-backend/src/providers/types.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/plugins/auth-backend/src/providers/types.ts b/plugins/auth-backend/src/providers/types.ts index 80b8899d94..b3b7e518cc 100644 --- a/plugins/auth-backend/src/providers/types.ts +++ b/plugins/auth-backend/src/providers/types.ts @@ -18,8 +18,6 @@ import express from 'express'; import { Logger } from 'winston'; import { TokenIssuer } from '../identity'; import { Config } from '@backstage/config'; -import { OAuthProvider } from '../lib/OAuthProvider'; -import { SamlAuthProvider } from './saml/provider'; export type OAuthProviderOptions = { /** @@ -174,7 +172,7 @@ export type AuthProviderFactory = ( envConfig: Config, logger: Logger, issuer: TokenIssuer, -) => OAuthProvider | SamlAuthProvider | undefined; +) => AuthProviderRouteHandlers | undefined; export type AuthResponse = { providerInfo: ProviderInfo; From 07c9a9474a111f8e8dcd1787734135dd52f19b29 Mon Sep 17 00:00:00 2001 From: Sebastian Qvarfordt Date: Wed, 2 Sep 2020 15:30:35 +0200 Subject: [PATCH 113/132] Get branchname from repo instance instead of location url (#2242) --- .../techdocs-backend/src/techdocs/stages/prepare/helpers.ts | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts index b2f04bbbbf..27e746590a 100644 --- a/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts +++ b/plugins/techdocs-backend/src/techdocs/stages/prepare/helpers.ts @@ -79,9 +79,10 @@ export const checkoutGitRepository = async ( if (fs.existsSync(repositoryTmpPath)) { const repository = await Repository.open(repositoryTmpPath); + const currentBranchName = (await repository.getCurrentBranch()).shorthand(); await repository.mergeBranches( - parsedGitLocation.ref, - `origin/${parsedGitLocation.ref}`, + currentBranchName, + `origin/${currentBranchName}`, ); return repositoryTmpPath; } From 9988479c61cfac34e6e5089797d5316790858b51 Mon Sep 17 00:00:00 2001 From: Ivan Shmidt Date: Wed, 2 Sep 2020 15:56:14 +0200 Subject: [PATCH 114/132] refactor: address PR comments --- docs/plugins/index.md | 4 +-- ...integrating-plugin-into-service-catalog.md | 5 ++- packages/app/src/App.tsx | 8 ++--- .../app/src/components/catalog/EntityPage.tsx | 7 ++++- packages/core-api/src/app/App.tsx | 11 ++----- packages/core-api/src/app/types.ts | 2 +- packages/dev-utils/src/devApp/render.tsx | 4 +-- packages/e2e-test/src/e2e-test.ts | 13 ++++---- .../EntityPageLayout/EntityPageLayout.tsx | 9 +++--- .../EntityPageLayout/Tabbed/Tabbed.test.tsx | 15 +++++++++ .../EntityPageLayout/Tabbed/Tabbed.tsx | 15 +++++---- .../catalog/src/{ => components}/Router.tsx | 31 ++++++++++--------- plugins/catalog/src/hooks/useEntity.ts | 29 +++++++---------- plugins/catalog/src/index.ts | 2 +- plugins/catalog/src/routes.ts | 5 --- .../JobStatusModal/JobStatusModal.tsx | 4 +-- 16 files changed, 86 insertions(+), 78 deletions(-) rename plugins/catalog/src/{ => components}/Router.tsx (71%) diff --git a/docs/plugins/index.md b/docs/plugins/index.md index 6941ff3e18..41ad082d7c 100644 --- a/docs/plugins/index.md +++ b/docs/plugins/index.md @@ -27,9 +27,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 catalog service +## Integrate into the Service Catalog If your plugin isn't supposed to live as a standalone page, but rather needs to -be presented as a part of a catalog service (e.g. a separate tab or a card on an +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. diff --git a/docs/plugins/integrating-plugin-into-service-catalog.md b/docs/plugins/integrating-plugin-into-service-catalog.md index 7819737c52..4408c27ccc 100644 --- a/docs/plugins/integrating-plugin-into-service-catalog.md +++ b/docs/plugins/integrating-plugin-into-service-catalog.md @@ -1,6 +1,6 @@ --- id: integrating-plugin-into-service-catalog -title: Integrate into the catalog service +title: Integrate into the Service Catalog --- > This is an advanced use case and currently is an experimental feature. Expect @@ -119,7 +119,6 @@ type EntityPageLayoutContentProps = { }; ``` -> The recommended pattern is to get the `entity` in the App and then pass it to -> the plugin's Router component as a prop. However if it inconvenient for you, +> You can either pass the entity from App to the plugin's router as a prop or > use `useEntity` hook from `@backstage/plugin-catalog` directly inside your > plugin. diff --git a/packages/app/src/App.tsx b/packages/app/src/App.tsx index 642eb7b004..cb38726716 100644 --- a/packages/app/src/App.tsx +++ b/packages/app/src/App.tsx @@ -26,8 +26,7 @@ import * as plugins from './plugins'; import { apis } from './apis'; import { hot } from 'react-hot-loader/root'; import { providers } from './identityProviders'; -import { CatalogRouter } from '@backstage/plugin-catalog'; -// import { ExplorePlugin } from '@backstage/plugin-explore'; +import { Router as CatalogRouter } from '@backstage/plugin-catalog'; import { Route, Routes, Navigate } from 'react-router'; import { EntityPage } from './components/catalog/EntityPage'; @@ -51,6 +50,7 @@ const app = createApp({ const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); +const deprecatedAppRoutes = app.getRoutes(); const AppRoutes = () => ( @@ -58,8 +58,8 @@ const AppRoutes = () => ( path="/catalog/*" element={} /> - } /> - {/* } /> */} + + {...deprecatedAppRoutes} ); diff --git a/packages/app/src/components/catalog/EntityPage.tsx b/packages/app/src/components/catalog/EntityPage.tsx index b56268036f..913c063f90 100644 --- a/packages/app/src/components/catalog/EntityPage.tsx +++ b/packages/app/src/components/catalog/EntityPage.tsx @@ -21,9 +21,14 @@ import { AboutCard, } from '@backstage/plugin-catalog'; import { Entity } from '@backstage/catalog-model'; +import { Grid } from '@material-ui/core'; const OverviewPage = ({ entity }: { entity: Entity }) => ( - + + + + + ); const ServiceEntityPage = ({ entity }: { entity: Entity }) => ( diff --git a/packages/core-api/src/app/App.tsx b/packages/core-api/src/app/App.tsx index 27a63ea04a..bfa26b7547 100644 --- a/packages/core-api/src/app/App.tsx +++ b/packages/core-api/src/app/App.tsx @@ -136,7 +136,7 @@ export class PrivateAppImpl implements BackstageApp { return this.icons[key]; } - getRoutes(): ComponentType<{}> { + getRoutes(): JSX.Element[] { const routes = new Array(); const registeredFeatureFlags = new Array(); @@ -191,14 +191,9 @@ export class PrivateAppImpl implements BackstageApp { FeatureFlags.registeredFeatureFlags = registeredFeatureFlags; } - const rendered = ( - - {routes} - } /> - - ); + routes.push(} />); - return () => rendered; + return routes; } getProvider(): ComponentType<{}> { diff --git a/packages/core-api/src/app/types.ts b/packages/core-api/src/app/types.ts index 6b022c2736..db7a0897b1 100644 --- a/packages/core-api/src/app/types.ts +++ b/packages/core-api/src/app/types.ts @@ -168,5 +168,5 @@ export type BackstageApp = { /** * Routes component that contains all routes for plugin pages in the app. */ - getRoutes(): ComponentType<{}>; + getRoutes(): JSX.Element[]; }; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 410f7592a5..cde39fdd05 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -85,7 +85,7 @@ class DevAppBuilder { const AppProvider = app.getProvider(); const AppRouter = app.getRouter(); - const AppRoutes = app.getRoutes(); + const deprecatedAppRoutes = app.getRoutes(); const sidebar = this.setupSidebar(this.plugins); @@ -99,7 +99,7 @@ class DevAppBuilder { {sidebar} - + {deprecatedAppRoutes} diff --git a/packages/e2e-test/src/e2e-test.ts b/packages/e2e-test/src/e2e-test.ts index f74c3f7166..5dfac68d6a 100644 --- a/packages/e2e-test/src/e2e-test.ts +++ b/packages/e2e-test/src/e2e-test.ts @@ -269,7 +269,7 @@ async function createPlugin(pluginName: string, appDir: string) { /** * Start serving the newly created app and make sure that the create plugin is rendering correctly */ -async function testAppServe(_pluginName: string, appDir: string) { +async function testAppServe(pluginName: string, appDir: string) { const startApp = spawnPiped(['yarn', 'start'], { cwd: appDir, }); @@ -280,12 +280,11 @@ async function testAppServe(_pluginName: string, appDir: string) { const browser = new Browser(); await waitForPageWithText(browser, '/', 'Backstage Service Catalog'); - // TODO(shmidt-i): adjust the plugin creation flow with new routing patterns - // await waitForPageWithText( - // browser, - // `/${pluginName}`, - // `Welcome to ${pluginName}!`, - // ); + await waitForPageWithText( + browser, + `/${pluginName}`, + `Welcome to ${pluginName}!`, + ); print('Both App and Plugin loaded correctly'); successful = true; diff --git a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx index 17f9f46830..6a1b80150e 100644 --- a/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/EntityPageLayout.tsx @@ -13,10 +13,10 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -import React, { useState } from 'react'; +import React, { useState, useContext } from 'react'; import { useParams, useNavigate } from 'react-router'; -import { useEntity } from '../../hooks/useEntity'; +import { EntityContext } from '../../hooks/useEntity'; import { pageTheme, PageTheme, @@ -74,7 +74,7 @@ function headerProps( export const EntityPageLayout = ({ children, }: { - children: React.ReactNode; + children?: React.ReactNode; }) => { const { optionalNamespaceAndName, kind } = useParams() as { optionalNamespaceAndName: string; @@ -82,7 +82,7 @@ export const EntityPageLayout = ({ }; const [name, namespace] = optionalNamespaceAndName.split(':').reverse(); - const { entity, loading, error } = useEntity(); + const { entity, loading, error } = useContext(EntityContext); const { headerTitle, headerType } = headerProps( kind, namespace, @@ -139,5 +139,4 @@ export const EntityPageLayout = ({ ); }; - EntityPageLayout.Content = Tabbed.Content; diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx index 3eccafbf7a..da1e696fde 100644 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.test.tsx @@ -36,6 +36,21 @@ describe('Tabbed layout', () => { expect(rendered.getByText('tabbed-test-content')).toBeInTheDocument(); }); + it('throws if any other component is a child of Tabbed.Layout', async () => { + await expect( + renderInTestApp( + + tabbed-test-content} + /> +
This will cause app to throw
+
, + ), + ).rejects.toThrow(/EntityPageLayout component only accepts/); + }); + it('navigates when user clicks different tab', async () => { const rendered = await renderInTestApp( diff --git a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx index 693ee10c78..096b2fa708 100644 --- a/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx +++ b/plugins/catalog/src/components/EntityPageLayout/Tabbed/Tabbed.tsx @@ -25,7 +25,6 @@ import { RouteMatch, } from 'react-router'; import { Tab, HeaderTabs, Content } from '@backstage/core'; -import { Grid } from '@material-ui/core'; import { Helmet } from 'react-helmet'; const getSelectedIndexOrDefault = ( @@ -67,6 +66,11 @@ export const Tabbed = { // Skip conditionals resolved to falses/nulls/undefineds etc return; } + if (child.type !== Tabbed.Content) { + throw new Error( + 'This component only accepts Content elements as direct children. Check the code of the EntityPage.', + ); + } const pathAndId = (child as JSX.Element).props.path; // Child here must be then always a functional component without any wrappers @@ -92,7 +96,7 @@ export const Tabbed = { matchRoutes(routes as RouteObject[], `/${params['*']}`) ?? []; const selectedIndex = getSelectedIndexOrDefault(matchedRoute, tabs); const currentTab = tabs[selectedIndex]; - const title = currentTab.label; + const title = currentTab?.label; const onTabChange = (index: number) => // Remove trailing /* @@ -103,6 +107,7 @@ export const Tabbed = { const currentRouteElement = useRoutes(routes); + if (!currentTab) return null; return ( <> - - - {currentRouteElement} - + + {currentRouteElement} ); diff --git a/plugins/catalog/src/Router.tsx b/plugins/catalog/src/components/Router.tsx similarity index 71% rename from plugins/catalog/src/Router.tsx rename to plugins/catalog/src/components/Router.tsx index 161536c714..61855f56d9 100644 --- a/plugins/catalog/src/Router.tsx +++ b/plugins/catalog/src/components/Router.tsx @@ -14,13 +14,14 @@ * limitations under the License. */ import React, { ComponentType } from 'react'; -import { CatalogPage } from './components/CatalogPage'; -import { EntityPageLayout } from './components/EntityPageLayout'; +import { CatalogPage } from './CatalogPage'; +import { EntityPageLayout } from './EntityPageLayout'; import { Route, Routes } from 'react-router'; -import { entityRoute, rootRoute, entityRouteDefault } from './routes'; +import { entityRoute, rootRoute } from '../routes'; import { Content } from '@backstage/core'; import { Typography, Link } from '@material-ui/core'; -import { EntityProvider } from './components/EntityProvider'; +import { EntityProvider } from './EntityProvider'; +import { useEntity } from '../hooks/useEntity'; const DefaultEntityPage = () => ( @@ -43,7 +44,17 @@ const DefaultEntityPage = () => ( ); -export const CatalogRouter = ({ +const EntityPageSwitch = ({ EntityPage }: { EntityPage: ComponentType }) => { + const { entity } = useEntity(); + // Loading and error states + if (!entity) return ; + + // Otherwise EntityPage provided from the App + // Note that EntityPage will include EntityPageLayout already + return ; +}; + +export const Router = ({ EntityPage = DefaultEntityPage, }: { EntityPage?: ComponentType; @@ -54,15 +65,7 @@ export const CatalogRouter = ({ path={`/${entityRoute.path}`} element={ - - - } - /> - - + } /> diff --git a/plugins/catalog/src/hooks/useEntity.ts b/plugins/catalog/src/hooks/useEntity.ts index ede45efeef..53b1cff73a 100644 --- a/plugins/catalog/src/hooks/useEntity.ts +++ b/plugins/catalog/src/hooks/useEntity.ts @@ -24,13 +24,13 @@ const REDIRECT_DELAY = 2000; type EntityLoadingStatus = { entity?: Entity; - loading: boolean | null; + loading: boolean; error?: Error; }; export const EntityContext = createContext({ - entity: undefined as any, - loading: null, + entity: undefined, + loading: true, error: undefined, }); @@ -53,25 +53,20 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { navigate('/'); }, REDIRECT_DELAY); } - }, [errorApi, navigate, error, loading, entity]); - if (!name) { - navigate('/catalog'); - return { - entity: undefined, - loading: null, - error: new Error('No name in url'), - } as never; - } + if (!name) { + errorApi.post(new Error('No name provided!')); + navigate('/'); + } + }, [errorApi, navigate, error, loading, entity, name]); return { entity, loading, error }; }; /** * Always going to return an entity, or throw an error if not a descendant of a EntityProvider. - * Otherwise the useEntityFromUrl will take care of the `undefined` entity */ -export const useEntity = () => - useContext & { entity: Entity }>( - EntityContext as any, - ); +export const useEntity = () => { + const { entity } = useContext<{ entity: Entity }>(EntityContext as any); + return { entity }; +}; diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index 26285b4814..a6d86e8102 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -19,7 +19,7 @@ export * from './api/CatalogClient'; export * from './api/types'; export * from './routes'; export { useEntityCompoundName } from './components/useEntityCompoundName'; -export * from './Router'; +export { Router } from './components/Router'; export { useEntity } from './hooks/useEntity'; export { AboutCard } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; diff --git a/plugins/catalog/src/routes.ts b/plugins/catalog/src/routes.ts index a9606760fb..14e23ff73b 100644 --- a/plugins/catalog/src/routes.ts +++ b/plugins/catalog/src/routes.ts @@ -28,8 +28,3 @@ export const entityRoute = createRouteRef({ path: ':kind/:optionalNamespaceAndName/*', title: 'Entity', }); -export const entityRouteDefault = createRouteRef({ - icon: NoIcon, - path: ':kind/:optionalNamespaceAndName/*', - title: 'Entity', -}); diff --git a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx index f5a592b7b7..c6ed55c92c 100644 --- a/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx +++ b/plugins/scaffolder/src/components/JobStatusModal/JobStatusModal.tsx @@ -26,7 +26,7 @@ import { useJobPolling } from './useJobPolling'; import { Job } from '../../types'; import { TemplateEntityV1alpha1 } from '@backstage/catalog-model'; import { Button } from '@backstage/core'; -import { entityRouteDefault } from '@backstage/plugin-catalog'; +import { entityRoute } from '@backstage/plugin-catalog'; import { generatePath } from 'react-router-dom'; type Props = { @@ -72,7 +72,7 @@ export const JobStatusModal = ({ {entity && (