From 907fac9d9272444a40ff46a979de2e47b4088b71 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 22 Dec 2020 09:45:25 -0600 Subject: [PATCH 01/23] eliminate racey code --- packages/core-api/src/lib/loginPopup.test.ts | 80 -------------------- packages/core-api/src/lib/loginPopup.ts | 18 ----- 2 files changed, 98 deletions(-) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index 98541c268e..f645b75af7 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -135,84 +135,4 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(1); }); - - it('should fail if popup is closed', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(0); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://localhost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, popup was closed', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); - - it('should indicate if origin does not match', async () => { - const openSpy = jest - .spyOn(window, 'open') - .mockReturnValue({ closed: false } as Window); - const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); - const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); - const popupMock = { closed: false }; - - openSpy.mockReturnValue(popupMock as Window); - - const payloadPromise = showLoginPopup({ - url: 'url', - name: 'name', - origin: 'origin', - }); - - const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; - listener({ - source: popupMock, - origin: 'origin', - data: { - type: 'config_info', - targetOrigin: 'http://differenthost', - }, - } as MessageEvent); - - setTimeout(() => { - popupMock.closed = true; - }, 150); - await expect(payloadPromise).rejects.toThrow( - 'Login failed, Incorrect app origin, expected http://differenthost', - ); - - expect(openSpy).toBeCalledTimes(1); - expect(addEventListenerSpy).toBeCalledTimes(1); - expect(removeEventListenerSpy).toBeCalledTimes(1); - }); }); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 2e14447882..1741d845e8 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -79,8 +79,6 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, ); - let targetOrigin = ''; - if (!popup || typeof popup.closed === 'undefined' || popup.closed) { reject(new Error('Failed to open auth popup.')); return; @@ -96,7 +94,6 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { const { data } = event; if (data.type === 'config_info') { - targetOrigin = data.targetOrigin; return; } @@ -117,23 +114,8 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { done(); }; - const intervalId = setInterval(() => { - if (popup.closed) { - const errMessage = `Login failed, ${ - targetOrigin !== window.location.origin - ? `Incorrect app origin, expected ${targetOrigin}` - : 'popup was closed' - }`; - const error = new Error(errMessage); - error.name = 'PopupClosedError'; - reject(error); - done(); - } - }, 100); - function done() { window.removeEventListener('message', messageListener); - clearInterval(intervalId); } window.addEventListener('message', messageListener); From 27f2af9354baf792291ab77714f2c25ea2e1cc62 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 22 Dec 2020 09:55:51 -0600 Subject: [PATCH 02/23] change log --- .changeset/purple-turtles-float.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/purple-turtles-float.md diff --git a/.changeset/purple-turtles-float.md b/.changeset/purple-turtles-float.md new file mode 100644 index 0000000000..2e5688bf11 --- /dev/null +++ b/.changeset/purple-turtles-float.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +Remove race condition in loginPopup From 0836691020370a127a2847cd38f16cc6356e8779 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 22 Dec 2020 15:44:42 -0600 Subject: [PATCH 03/23] Revert "eliminate racey code" This reverts commit 907fac9d9272444a40ff46a979de2e47b4088b71. --- packages/core-api/src/lib/loginPopup.test.ts | 80 ++++++++++++++++++++ packages/core-api/src/lib/loginPopup.ts | 18 +++++ 2 files changed, 98 insertions(+) diff --git a/packages/core-api/src/lib/loginPopup.test.ts b/packages/core-api/src/lib/loginPopup.test.ts index f645b75af7..98541c268e 100644 --- a/packages/core-api/src/lib/loginPopup.test.ts +++ b/packages/core-api/src/lib/loginPopup.test.ts @@ -135,4 +135,84 @@ describe('showLoginPopup', () => { expect(addEventListenerSpy).toBeCalledTimes(1); expect(removeEventListenerSpy).toBeCalledTimes(1); }); + + it('should fail if popup is closed', async () => { + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue({ closed: false } as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const popupMock = { closed: false }; + + openSpy.mockReturnValue(popupMock as Window); + + const payloadPromise = showLoginPopup({ + url: 'url', + name: 'name', + origin: 'origin', + }); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(0); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + type: 'config_info', + targetOrigin: 'http://localhost', + }, + } as MessageEvent); + + setTimeout(() => { + popupMock.closed = true; + }, 150); + await expect(payloadPromise).rejects.toThrow( + 'Login failed, popup was closed', + ); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); + + it('should indicate if origin does not match', async () => { + const openSpy = jest + .spyOn(window, 'open') + .mockReturnValue({ closed: false } as Window); + const addEventListenerSpy = jest.spyOn(window, 'addEventListener'); + const removeEventListenerSpy = jest.spyOn(window, 'removeEventListener'); + const popupMock = { closed: false }; + + openSpy.mockReturnValue(popupMock as Window); + + const payloadPromise = showLoginPopup({ + url: 'url', + name: 'name', + origin: 'origin', + }); + + const listener = addEventListenerSpy.mock.calls[0][1] as EventListener; + listener({ + source: popupMock, + origin: 'origin', + data: { + type: 'config_info', + targetOrigin: 'http://differenthost', + }, + } as MessageEvent); + + setTimeout(() => { + popupMock.closed = true; + }, 150); + await expect(payloadPromise).rejects.toThrow( + 'Login failed, Incorrect app origin, expected http://differenthost', + ); + + expect(openSpy).toBeCalledTimes(1); + expect(addEventListenerSpy).toBeCalledTimes(1); + expect(removeEventListenerSpy).toBeCalledTimes(1); + }); }); diff --git a/packages/core-api/src/lib/loginPopup.ts b/packages/core-api/src/lib/loginPopup.ts index 1741d845e8..2e14447882 100644 --- a/packages/core-api/src/lib/loginPopup.ts +++ b/packages/core-api/src/lib/loginPopup.ts @@ -79,6 +79,8 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { `menubar=no,location=no,resizable=no,scrollbars=no,status=no,width=${width},height=${height},top=${top},left=${left}`, ); + let targetOrigin = ''; + if (!popup || typeof popup.closed === 'undefined' || popup.closed) { reject(new Error('Failed to open auth popup.')); return; @@ -94,6 +96,7 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { const { data } = event; if (data.type === 'config_info') { + targetOrigin = data.targetOrigin; return; } @@ -114,8 +117,23 @@ export function showLoginPopup(options: LoginPopupOptions): Promise { done(); }; + const intervalId = setInterval(() => { + if (popup.closed) { + const errMessage = `Login failed, ${ + targetOrigin !== window.location.origin + ? `Incorrect app origin, expected ${targetOrigin}` + : 'popup was closed' + }`; + const error = new Error(errMessage); + error.name = 'PopupClosedError'; + reject(error); + done(); + } + }, 100); + function done() { window.removeEventListener('message', messageListener); + clearInterval(intervalId); } window.addEventListener('message', messageListener); From 29b4056f2c6fbc32029a567572669b9711806538 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Tue, 22 Dec 2020 16:03:21 -0600 Subject: [PATCH 04/23] delay window close by 200 ms --- .changeset/purple-turtles-float.md | 2 +- plugins/auth-backend/src/lib/flow/authFlowHelpers.ts | 4 +++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.changeset/purple-turtles-float.md b/.changeset/purple-turtles-float.md index 2e5688bf11..8736a7315f 100644 --- a/.changeset/purple-turtles-float.md +++ b/.changeset/purple-turtles-float.md @@ -2,4 +2,4 @@ '@backstage/core-api': patch --- -Remove race condition in loginPopup +Delay auth loginPopup close to avoid race condition with callers of authFlowHelpers. diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index 22ffc7af88..86a615e721 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -56,7 +56,9 @@ export const postMessageResponse = ( var originInfo = {'type': 'config_info', 'targetOrigin': origin}; (window.opener || window.parent).postMessage(originInfo, '*'); (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); - window.close(); + setTimeout(() => { + window.close(); + }, 100 * 2); // double the interval of the core-api lib/loginPopup.ts `; const hash = crypto.createHash('sha256').update(script).digest('base64'); From 4b896485e4d2ac423fdf348ca5222546066c108f Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 13:56:24 +0100 Subject: [PATCH 05/23] core-api: deprecate RouteRef path and remove deprecated createSubRoute --- packages/core-api/src/routing/RouteRef.ts | 14 ++------------ packages/core-api/src/routing/types.ts | 9 +++------ 2 files changed, 5 insertions(+), 18 deletions(-) diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index f89634e0ec..164722766b 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -25,25 +25,15 @@ export class AbsoluteRouteRef { // TODO(Rugvip): Remove this, routes are looked up via the registry instead get path() { - return this.config.path; + return this.config.path ?? ''; } get title() { return this.config.title; } - /** - * This function should not be used, create a separate RouteRef instead - * @deprecated - */ - createSubRoute(): any { - throw new Error( - 'This method should not be called, create a separate RouteRef instead', - ); - } - toString() { - return `routeRef{path=${this.path}}`; + return `routeRef{title=${this.title}}`; } } diff --git a/packages/core-api/src/routing/types.ts b/packages/core-api/src/routing/types.ts index c6d79fd991..99a20c31e9 100644 --- a/packages/core-api/src/routing/types.ts +++ b/packages/core-api/src/routing/types.ts @@ -19,14 +19,10 @@ import { IconComponent } from '../icons'; // @ts-ignore, we're just embedding the Params type for usage in other places export type RouteRef = { // TODO(Rugvip): Remove path, look up via registry instead + /** @deprecated paths are no longer accessed directly from RouteRefs, use useRouteRef instead */ path: string; icon?: IconComponent; title: string; - /** - * This function should not be used, create a separate RouteRef instead - * @deprecated - */ - createSubRoute(): any; }; export type AnyRouteRef = RouteRef; @@ -51,7 +47,8 @@ export type MutableRouteRef = RouteRef<{}>; export type RouteRefConfig = { params?: Array; - path: string; + /** @deprecated Route refs no longer decide their own path */ + path?: string; icon?: IconComponent; title: string; }; From 99205ec62590ac43e76c8bb949078f45fcf06ea5 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 14:29:13 +0100 Subject: [PATCH 06/23] core-api: use typescript workaround for using private constructor instead of a runtime one --- packages/core-api/src/routing/RouteRef.ts | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/packages/core-api/src/routing/RouteRef.ts b/packages/core-api/src/routing/RouteRef.ts index 164722766b..4c2da34098 100644 --- a/packages/core-api/src/routing/RouteRef.ts +++ b/packages/core-api/src/routing/RouteRef.ts @@ -44,13 +44,7 @@ export function createRouteRef< return new AbsoluteRouteRef(config); } -const create = Symbol('create-external-route-ref'); - export class ExternalRouteRef { - static [create]() { - return new ExternalRouteRef(); - } - private constructor() {} toString() { @@ -59,5 +53,5 @@ export class ExternalRouteRef { } export function createExternalRouteRef(): ExternalRouteRef { - return ExternalRouteRef[create](); + return new ((ExternalRouteRef as unknown) as { new (): ExternalRouteRef })(); } From 86c3c652a8ced9e31dbed1d47f85c8ee72d8017e Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 14:32:04 +0100 Subject: [PATCH 07/23] changeset: add changeset for core-api RouteRef deprecations --- .changeset/polite-turtles-prove.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/polite-turtles-prove.md diff --git a/.changeset/polite-turtles-prove.md b/.changeset/polite-turtles-prove.md new file mode 100644 index 0000000000..094617026c --- /dev/null +++ b/.changeset/polite-turtles-prove.md @@ -0,0 +1,5 @@ +--- +'@backstage/core-api': patch +--- + +Deprecate `RouteRef` path parameter and member, and remove deprecated `routeRef.createSubRouteRef`. From 7dfcdc1720e96f6d61d873ab550678d142f25139 Mon Sep 17 00:00:00 2001 From: Fidel Coria Date: Wed, 23 Dec 2020 11:15:36 -0600 Subject: [PATCH 08/23] reduce close timeout to 100 ms --- plugins/auth-backend/src/lib/flow/authFlowHelpers.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts index 86a615e721..e70aa0bfee 100644 --- a/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts +++ b/plugins/auth-backend/src/lib/flow/authFlowHelpers.ts @@ -58,7 +58,7 @@ export const postMessageResponse = ( (window.opener || window.parent).postMessage(JSON.parse(authResponse), origin); setTimeout(() => { window.close(); - }, 100 * 2); // double the interval of the core-api lib/loginPopup.ts + }, 100); // same as the interval of the core-api lib/loginPopup.ts (to address race conditions) `; const hash = crypto.createHash('sha256').update(script).digest('base64'); From d814e3b88ec0ee64dcbd7eff8f6b7be5c6a94d04 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 18:27:45 +0100 Subject: [PATCH 09/23] catalog: rename EntityProvider to EntityLoaderProvider, and add new EntityProvider Co-authored-by: blam --- .../CatalogEntityPage/CatalogEntityPage.tsx | 6 ++--- .../EntityLoaderProvider.tsx | 27 +++++++++++++++++++ .../components/EntityLoaderProvider/index.ts | 16 +++++++++++ .../EntityProvider/EntityProvider.tsx | 26 +++++++++++------- plugins/catalog/src/components/Router.tsx | 6 ++--- plugins/catalog/src/hooks/useEntity.ts | 2 +- plugins/catalog/src/index.ts | 1 + 7 files changed, 68 insertions(+), 16 deletions(-) create mode 100644 plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx create mode 100644 plugins/catalog/src/components/EntityLoaderProvider/index.ts diff --git a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx index e0a314a391..bf5fb1d9d6 100644 --- a/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx +++ b/plugins/catalog/src/components/CatalogEntityPage/CatalogEntityPage.tsx @@ -16,12 +16,12 @@ import React from 'react'; import { Outlet } from 'react-router'; -import { EntityProvider } from '../EntityProvider'; +import { EntityLoaderProvider } from '../EntityLoaderProvider'; export const CatalogEntityPage = () => { return ( - + - + ); }; diff --git a/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx new file mode 100644 index 0000000000..98b8295047 --- /dev/null +++ b/plugins/catalog/src/components/EntityLoaderProvider/EntityLoaderProvider.tsx @@ -0,0 +1,27 @@ +/* + * 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, { ReactNode } from 'react'; +import { useEntityFromUrl, EntityContext } from '../../hooks/useEntity'; + +export const EntityLoaderProvider = ({ children }: { children: ReactNode }) => { + const { entity, loading, error } = useEntityFromUrl(); + + return ( + + {children} + + ); +}; diff --git a/plugins/catalog/src/components/EntityLoaderProvider/index.ts b/plugins/catalog/src/components/EntityLoaderProvider/index.ts new file mode 100644 index 0000000000..925c927ec9 --- /dev/null +++ b/plugins/catalog/src/components/EntityLoaderProvider/index.ts @@ -0,0 +1,16 @@ +/* + * 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 { EntityLoaderProvider } from './EntityLoaderProvider'; diff --git a/plugins/catalog/src/components/EntityProvider/EntityProvider.tsx b/plugins/catalog/src/components/EntityProvider/EntityProvider.tsx index e328b64d44..9135b54638 100644 --- a/plugins/catalog/src/components/EntityProvider/EntityProvider.tsx +++ b/plugins/catalog/src/components/EntityProvider/EntityProvider.tsx @@ -13,15 +13,23 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +import { Entity } from '@backstage/catalog-model'; import React, { ReactNode } from 'react'; -import { useEntityFromUrl, EntityContext } from '../../hooks/useEntity'; +import { EntityContext } from '../../hooks/useEntity'; -export const EntityProvider = ({ children }: { children: ReactNode }) => { - const { entity, loading, error } = useEntityFromUrl(); - - return ( - - {children} - - ); +type EntityProviderProps = { + entity: Entity; + children: ReactNode; }; + +export const EntityProvider = ({ entity, children }: EntityProviderProps) => ( + + {children} + +); diff --git a/plugins/catalog/src/components/Router.tsx b/plugins/catalog/src/components/Router.tsx index 298aa28d52..6a4593fb80 100644 --- a/plugins/catalog/src/components/Router.tsx +++ b/plugins/catalog/src/components/Router.tsx @@ -23,7 +23,7 @@ import { entityRoute, rootRoute } from '../routes'; import { CatalogPage } from './CatalogPage'; import { EntityNotFound } from './EntityNotFound'; import { EntityPageLayout } from './EntityPageLayout'; -import { EntityProvider } from './EntityProvider'; +import { EntityLoaderProvider } from './EntityLoaderProvider'; const DefaultEntityPage = () => ( @@ -79,9 +79,9 @@ export const Router = ({ + - + } /> { }; /** - * Always going to return an entity, or throw an error if not a descendant of a EntityProvider. + * Grab Entity from the context and it's current loading state. */ export const useEntity = () => { const { entity, loading, error } = useContext<{ diff --git a/plugins/catalog/src/index.ts b/plugins/catalog/src/index.ts index d8686a18b0..2152933daf 100644 --- a/plugins/catalog/src/index.ts +++ b/plugins/catalog/src/index.ts @@ -18,6 +18,7 @@ export * from '@backstage/catalog-client'; export { AboutCard } from './components/AboutCard'; export { EntityPageLayout } from './components/EntityPageLayout'; export { EntityLayout } from './components/EntityLayout'; +export { EntityProvider } from './components/EntityProvider'; export * from './components/EntitySwitch'; export { Router } from './components/Router'; export { useEntityCompoundName } from './components/useEntityCompoundName'; From 91518a253f5eaac7c993093f69bfa5f1482f8576 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 17:56:01 +0100 Subject: [PATCH 10/23] dev-utils: add .registerPage for use with extensions Co-authored-by: blam --- packages/dev-utils/src/devApp/render.tsx | 43 ++++++++++++++++++++++-- 1 file changed, 40 insertions(+), 3 deletions(-) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index 86a8b2f7cb..b14fbbf776 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -29,9 +29,25 @@ import { AlertDisplay, OAuthRequestDialog, AnyApiFactory, + IconComponent, + BackstageRoutes, + attachComponentData, } from '@backstage/core'; import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied'; -import { Routes } from 'react-router'; +import { Outlet } from 'react-router'; + +const GatheringRoute: (props: { + path: string; + children: JSX.Element; +}) => JSX.Element = () => ; + +attachComponentData(GatheringRoute, 'core.gatherMountPoints', true); + +type RegisterPageOptions = { + element: JSX.Element; + title?: string; + icon?: IconComponent; +}; // TODO(rugvip): export proper plugin type from core that isn't the plugin class type BackstagePlugin = ReturnType; @@ -44,6 +60,8 @@ class DevAppBuilder { private readonly plugins = new Array(); private readonly apis = new Array(); private readonly rootChildren = new Array(); + private readonly routes = new Array(); + private readonly sidebarItems = new Array(); /** * Register one or more plugins to render in the dev app @@ -75,6 +93,21 @@ class DevAppBuilder { return this; } + addPage({ element, title, icon }: RegisterPageOptions): DevAppBuilder { + const path = `/page-${this.routes.length + 1}`; + this.sidebarItems.push( + , + ); + this.routes.push( + , + ); + return this; + } /** * Build a DevApp component using the resources registered so far */ @@ -100,7 +133,10 @@ class DevAppBuilder { {sidebar} - {deprecatedAppRoutes} + + {this.routes} + {deprecatedAppRoutes} + @@ -166,6 +202,7 @@ class DevAppBuilder { return ( + {this.sidebarItems} {sidebarItems} ); @@ -199,7 +236,7 @@ class DevAppBuilder { // this to provide their own plugin dev wrappers. /** - * Creates a dev app for rendering one or more plugins and exposing the touchpoints of the plugin. + * Creates a dev app for rendering one or more plugins and exposing the touch points of the plugin. */ export function createDevApp() { return new DevAppBuilder(); From 4f467143c32cc0dac3d17a60dd1d8f25c3c24773 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 17:56:43 +0100 Subject: [PATCH 11/23] dev-utils: use dev index module as root for hot reloading Co-authored-by: blam --- packages/dev-utils/src/devApp/render.tsx | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index b14fbbf776..f15a4c4078 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -14,7 +14,7 @@ * limitations under the License. */ -import { hot } from 'react-hot-loader/root'; +import { hot } from 'react-hot-loader'; import React, { ComponentType, ReactNode } from 'react'; import ReactDOM from 'react-dom'; import BookmarkIcon from '@material-ui/icons/Bookmark'; @@ -150,7 +150,12 @@ class DevAppBuilder { * Build and render directory to #root element, with react hot loading. */ render(): void { - const DevApp = hot(this.build()); + const hotModule = + require.cache['./dev/index.tsx'] ?? + require.cache['./dev/index.ts'] ?? + module; + + const DevApp = hot(hotModule)(this.build()); const paths = this.findPluginPaths(this.plugins); From 36ffbd71b705de38ca864c5946abcbc72cf04bc7 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 17:57:55 +0100 Subject: [PATCH 12/23] dev-utils: add EntityGridItem for easily displaying various entity cards Co-authored-by: blam --- packages/dev-utils/package.json | 2 ++ .../EntityGridItem/EntityGridItem.tsx | 31 +++++++++++++++++++ .../src/components/EntityGridItem/index.ts | 17 ++++++++++ packages/dev-utils/src/components/index.ts | 17 ++++++++++ packages/dev-utils/src/index.ts | 2 ++ 5 files changed, 69 insertions(+) create mode 100644 packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx create mode 100644 packages/dev-utils/src/components/EntityGridItem/index.ts create mode 100644 packages/dev-utils/src/components/index.ts diff --git a/packages/dev-utils/package.json b/packages/dev-utils/package.json index d52e14a728..1e0304a5a9 100644 --- a/packages/dev-utils/package.json +++ b/packages/dev-utils/package.json @@ -30,6 +30,8 @@ }, "dependencies": { "@backstage/core": "^0.4.0", + "@backstage/catalog-model": "^0.6.0", + "@backstage/plugin-catalog": "^0.2.8", "@backstage/test-utils": "^0.1.5", "@backstage/theme": "^0.2.2", "@material-ui/core": "^4.11.0", diff --git a/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx new file mode 100644 index 0000000000..a83d1d2ffc --- /dev/null +++ b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx @@ -0,0 +1,31 @@ +/* + * 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 { Grid, GridProps } from '@material-ui/core'; +import { Entity } from '@backstage/catalog-model'; +import { EntityProvider } from '@backstage/plugin-catalog'; + +export const EntityGridItem = ({ + entity, + ...rest +}: Omit & { entity: Entity }): JSX.Element => { + return ( + + + + ); +}; diff --git a/packages/dev-utils/src/components/EntityGridItem/index.ts b/packages/dev-utils/src/components/EntityGridItem/index.ts new file mode 100644 index 0000000000..e0f07b13ba --- /dev/null +++ b/packages/dev-utils/src/components/EntityGridItem/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 { EntityGridItem } from './EntityGridItem'; diff --git a/packages/dev-utils/src/components/index.ts b/packages/dev-utils/src/components/index.ts new file mode 100644 index 0000000000..34224f09fd --- /dev/null +++ b/packages/dev-utils/src/components/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 './EntityGridItem'; diff --git a/packages/dev-utils/src/index.ts b/packages/dev-utils/src/index.ts index c05e67ddbc..97add5dd86 100644 --- a/packages/dev-utils/src/index.ts +++ b/packages/dev-utils/src/index.ts @@ -13,4 +13,6 @@ * See the License for the specific language governing permissions and * limitations under the License. */ + +export * from './components'; export * from './devApp'; From 920b5a4f54433bc7fa4b6a39075918e72d7e2d84 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 18:17:50 +0100 Subject: [PATCH 13/23] dev-utils: add a small label with the entity name to EntityGridItems --- .../EntityGridItem/EntityGridItem.tsx | 22 +++++++++++++++++-- 1 file changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx index a83d1d2ffc..077a8f85e8 100644 --- a/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx +++ b/packages/dev-utils/src/components/EntityGridItem/EntityGridItem.tsx @@ -15,17 +15,35 @@ */ import React from 'react'; -import { Grid, GridProps } from '@material-ui/core'; +import { Grid, GridProps, makeStyles } from '@material-ui/core'; import { Entity } from '@backstage/catalog-model'; import { EntityProvider } from '@backstage/plugin-catalog'; +import { BackstageTheme } from '@backstage/theme'; + +const useStyles = makeStyles(theme => ({ + root: ({ entity }) => ({ + position: 'relative', + + '&::before': { + content: `"${entity.metadata.name}"`, + top: -theme.typography.fontSize + 4, + display: 'block', + position: 'absolute', + color: theme.palette.textSubtle, + }, + }), +})); export const EntityGridItem = ({ entity, + classes, ...rest }: Omit & { entity: Entity }): JSX.Element => { + const itemClasses = useStyles({ entity }); + return ( - + ); }; From 87c0c53c2f2018b2d1878bad01940d0f765a3bf4 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 18:45:43 +0100 Subject: [PATCH 14/23] Create polite-glasses-occur.md --- .changeset/polite-glasses-occur.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/polite-glasses-occur.md diff --git a/.changeset/polite-glasses-occur.md b/.changeset/polite-glasses-occur.md new file mode 100644 index 0000000000..bec533caad --- /dev/null +++ b/.changeset/polite-glasses-occur.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-catalog': patch +--- + +Add new `EntityProvider` component, which can be used to provide an entity for the `useEntity` hook. From 696b8ce74a864d37106d637f7b3554bd39d6d9ed Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Wed, 23 Dec 2020 18:46:42 +0100 Subject: [PATCH 15/23] Create loud-days-breathe.md --- .changeset/loud-days-breathe.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/loud-days-breathe.md diff --git a/.changeset/loud-days-breathe.md b/.changeset/loud-days-breathe.md new file mode 100644 index 0000000000..f8eb561f31 --- /dev/null +++ b/.changeset/loud-days-breathe.md @@ -0,0 +1,5 @@ +--- +'@backstage/dev-utils': patch +--- + +Add new `addPage` method for use with extensions, as well as an `EntityGridItem` to easily create different test cases for entity overview cards. From d63cc1fe5267b1d01eb9dffc4ea330f7daafea80 Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Sun, 27 Dec 2020 09:45:06 +0100 Subject: [PATCH 16/23] fix: token expiration in s, not ms --- plugins/auth-backend/src/identity/TokenFactory.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/identity/TokenFactory.ts b/plugins/auth-backend/src/identity/TokenFactory.ts index c4c259ed22..36622332e0 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.ts @@ -69,7 +69,7 @@ export class TokenFactory implements TokenIssuer { const sub = params.claims.sub; const aud = 'backstage'; const iat = Math.floor(Date.now() / MS_IN_S); - const exp = iat + this.keyDurationSeconds * MS_IN_S; + const exp = iat + this.keyDurationSeconds; this.logger.info(`Issuing token for ${sub}`); From cc046682e2ef1ad5ab9ccd1a834704f64c8d197f Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Sun, 27 Dec 2020 09:55:19 +0100 Subject: [PATCH 17/23] Add changeset --- .changeset/warm-pets-sin.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/warm-pets-sin.md diff --git a/.changeset/warm-pets-sin.md b/.changeset/warm-pets-sin.md new file mode 100644 index 0000000000..3a9197e7f2 --- /dev/null +++ b/.changeset/warm-pets-sin.md @@ -0,0 +1,5 @@ +--- +'@backstage/plugin-auth-backend': patch +--- + +fix bug in token expiration date From 4975224d8ec65bd725cca8a55b67cf48bccabafb Mon Sep 17 00:00:00 2001 From: Erik Larsson Date: Sun, 27 Dec 2020 10:52:18 +0100 Subject: [PATCH 18/23] Adjust test --- plugins/auth-backend/src/identity/TokenFactory.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/auth-backend/src/identity/TokenFactory.test.ts b/plugins/auth-backend/src/identity/TokenFactory.test.ts index 4303401ee6..8b719799b1 100644 --- a/plugins/auth-backend/src/identity/TokenFactory.test.ts +++ b/plugins/auth-backend/src/identity/TokenFactory.test.ts @@ -87,7 +87,7 @@ describe('TokenFactory', () => { iat: expect.any(Number), exp: expect.any(Number), }); - expect(payload.exp).toBe(payload.iat + keyDurationSeconds * 1000); + expect(payload.exp).toBe(payload.iat + keyDurationSeconds); }); it('should generate new signing keys when the current one expires', async () => { From cff6a9e70f0d1cd74b0a924c324812807fb000a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Kalv=C4=81ns?= Date: Mon, 28 Dec 2020 10:38:49 +0100 Subject: [PATCH 19/23] Fix parameter name in docstring for duration#inclusiveStartDateOf --- plugins/cost-insights/src/utils/duration.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/cost-insights/src/utils/duration.ts b/plugins/cost-insights/src/utils/duration.ts index 7a330b6f91..6eebead1d7 100644 --- a/plugins/cost-insights/src/utils/duration.ts +++ b/plugins/cost-insights/src/utils/duration.ts @@ -24,7 +24,7 @@ export const DEFAULT_DURATION = Duration.P30D; * Derive the start date of a given period, assuming two repeating intervals. * * @param duration see comment on Duration enum - * @param endDate from CostInsightsApi.getLastCompleteBillingDate + 1 day + * @param exclusiveEndDate from CostInsightsApi.getLastCompleteBillingDate + 1 day */ export function inclusiveStartDateOf( duration: Duration, From 63f40032ca03049f9f7a942e76c565d60993e6ab Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Mon, 28 Dec 2020 11:13:50 +0100 Subject: [PATCH 20/23] Update plugins/catalog/src/hooks/useEntity.ts MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Fredrik Adelöw --- plugins/catalog/src/hooks/useEntity.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/plugins/catalog/src/hooks/useEntity.ts b/plugins/catalog/src/hooks/useEntity.ts index 7b689e8730..289c4e26aa 100644 --- a/plugins/catalog/src/hooks/useEntity.ts +++ b/plugins/catalog/src/hooks/useEntity.ts @@ -55,7 +55,7 @@ export const useEntityFromUrl = (): EntityLoadingStatus => { }; /** - * Grab Entity from the context and it's current loading state. + * Grab Entity from the context and its current loading state. */ export const useEntity = () => { const { entity, loading, error } = useContext<{ From 5b6533eb76aea627c295b23d40b68664e6a31c15 Mon Sep 17 00:00:00 2001 From: Patrik Oldsberg Date: Thu, 24 Dec 2020 12:53:02 +0100 Subject: [PATCH 21/23] core-api: rename BackstageRoutes to FlatRoutes --- .changeset/dull-seals-march.md | 2 +- .../src/routing/{BackstageRoutes.tsx => FlatRoutes.tsx} | 6 ++---- packages/core-api/src/routing/index.ts | 2 +- packages/dev-utils/src/devApp/render.tsx | 6 +++--- 4 files changed, 7 insertions(+), 9 deletions(-) rename packages/core-api/src/routing/{BackstageRoutes.tsx => FlatRoutes.tsx} (94%) diff --git a/.changeset/dull-seals-march.md b/.changeset/dull-seals-march.md index 714c5c5cad..49e1f7f2e6 100644 --- a/.changeset/dull-seals-march.md +++ b/.changeset/dull-seals-march.md @@ -3,4 +3,4 @@ '@backstage/core': patch --- -Add `BackstageRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes. +Add `FlatRoutes` component to replace the top-level `Routes` component from `react-router` within apps, removing the need for manually appending `/*` to paths or sorting routes. diff --git a/packages/core-api/src/routing/BackstageRoutes.tsx b/packages/core-api/src/routing/FlatRoutes.tsx similarity index 94% rename from packages/core-api/src/routing/BackstageRoutes.tsx rename to packages/core-api/src/routing/FlatRoutes.tsx index 6c042af27d..a6783964a7 100644 --- a/packages/core-api/src/routing/BackstageRoutes.tsx +++ b/packages/core-api/src/routing/FlatRoutes.tsx @@ -66,13 +66,11 @@ function createRoutesFromChildren(children: ReactNode): RouteObject[] { }); } -type BackstageRoutesProps = { +type FlatRoutesProps = { children: ReactNode; }; -export const BackstageRoutes = ( - props: BackstageRoutesProps, -): JSX.Element | null => { +export const FlatRoutes = (props: FlatRoutesProps): JSX.Element | null => { const routes = createRoutesFromChildren(props.children); return useRoutes(routes); }; diff --git a/packages/core-api/src/routing/index.ts b/packages/core-api/src/routing/index.ts index 2bdf0aa5e8..9564b3b225 100644 --- a/packages/core-api/src/routing/index.ts +++ b/packages/core-api/src/routing/index.ts @@ -21,6 +21,6 @@ export type { ConcreteRoute, MutableRouteRef, } from './types'; -export { BackstageRoutes } from './BackstageRoutes'; +export { FlatRoutes } from './FlatRoutes'; export { createRouteRef } from './RouteRef'; export { useRouteRef } from './hooks'; diff --git a/packages/dev-utils/src/devApp/render.tsx b/packages/dev-utils/src/devApp/render.tsx index f15a4c4078..f1a6d30b4c 100644 --- a/packages/dev-utils/src/devApp/render.tsx +++ b/packages/dev-utils/src/devApp/render.tsx @@ -30,7 +30,7 @@ import { OAuthRequestDialog, AnyApiFactory, IconComponent, - BackstageRoutes, + FlatRoutes, attachComponentData, } from '@backstage/core'; import SentimentDissatisfiedIcon from '@material-ui/icons/SentimentDissatisfied'; @@ -133,10 +133,10 @@ class DevAppBuilder { {sidebar} - + {this.routes} {deprecatedAppRoutes} - + From 37b28bad44acb2003735cacee80275f147547b1d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?M=C4=81rti=C5=86=C5=A1=20Kalv=C4=81ns?= Date: Mon, 28 Dec 2020 11:34:56 +0100 Subject: [PATCH 22/23] Introduce slight bias towards positive trend in cost insights mock data --- plugins/cost-insights/src/utils/mockData.ts | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/plugins/cost-insights/src/utils/mockData.ts b/plugins/cost-insights/src/utils/mockData.ts index ce9e65f1c3..19f9676eea 100644 --- a/plugins/cost-insights/src/utils/mockData.ts +++ b/plugins/cost-insights/src/utils/mockData.ts @@ -234,14 +234,24 @@ export function aggregationFor( 'day', ); + function nextDelta(): number { + const varianceFromBaseline = 0.15; + // Let's give positive vibes in trendlines - higher change for positive delta with >0.5 value + const positiveTrendChance = 0.55; + const normalization = positiveTrendChance - 1; + return baseline * (Math.random() + normalization) * varianceFromBaseline; + } + return [...Array(days).keys()].reduce( (values: DateAggregation[], i: number): DateAggregation[] => { const last = values.length ? values[values.length - 1].amount : baseline; + const date = dayjs(inclusiveStartDateOf(duration, endDate)) + .add(i, 'day') + .format(DEFAULT_DATE_FORMAT); + const amount = Math.max(0, last + nextDelta()); values.push({ - date: dayjs(inclusiveStartDateOf(duration, endDate)) - .add(i, 'day') - .format(DEFAULT_DATE_FORMAT), - amount: Math.max(0, last + (baseline / 20) * (Math.random() * 2 - 1)), + date: date, + amount: amount, }); return values; }, From 0574a27b7ce32daa0f3038a01e666cee91227084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Fredrik=20Adel=C3=B6w?= Date: Mon, 28 Dec 2020 10:02:34 +0100 Subject: [PATCH 23/23] cli: fix windows tests --- .../LinkedPackageResolvePlugin.test.ts | 63 +++++++++++-------- .../src/stages/publish/helpers.test.ts | 12 ++-- 2 files changed, 46 insertions(+), 29 deletions(-) diff --git a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts index 53512567eb..b4580ea281 100644 --- a/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts +++ b/packages/cli/src/lib/bundler/LinkedPackageResolvePlugin.test.ts @@ -14,20 +14,27 @@ * limitations under the License. */ +import * as os from 'os'; +import * as path from 'path'; import { LinkedPackageResolvePlugin } from './LinkedPackageResolvePlugin'; describe('LinkedPackageResolvePlugin', () => { + const root = os.platform() === 'win32' ? 'C:\\root' : '/root'; + it('should re-write paths for external packages', () => { - const plugin = new LinkedPackageResolvePlugin('/root/repo/node_modules', [ - { - name: 'a', - location: '/root/external-a', - }, - { - name: '@s/b', - location: '/root/external-b', - }, - ]); + const plugin = new LinkedPackageResolvePlugin( + path.resolve(root, 'repo/node_modules'), + [ + { + name: 'a', + location: path.resolve(root, 'external-a'), + }, + { + name: '@s/b', + location: path.resolve(root, 'external-b'), + }, + ], + ); const tapAsync = jest.fn(); const doResolve = jest.fn(); @@ -50,10 +57,10 @@ describe('LinkedPackageResolvePlugin', () => { const callbackX = jest.fn(); tap( { - request: '/root/repo/package/x/src/module.ts', - path: '/root/repo/package/x/src', + request: path.resolve(root, 'repo/package/x/src/module.ts'), + path: path.resolve(root, 'repo/package/x/src'), context: { - issuer: '/root/repo/package/x/src/index.ts', + issuer: path.resolve(root, 'repo/package/x/src/index.ts'), }, }, 'some-context', @@ -81,10 +88,10 @@ describe('LinkedPackageResolvePlugin', () => { const callbackA = jest.fn(); tap( { - request: '/root/external-a/src/module.ts', - path: '/root/external-a/src', + request: path.resolve(root, 'external-a/src/module.ts'), + path: path.resolve(root, 'external-a/src'), context: { - issuer: '/root/external-a/src/index.ts', + issuer: path.resolve(root, 'external-a/src/index.ts'), }, }, 'some-context', @@ -95,13 +102,16 @@ describe('LinkedPackageResolvePlugin', () => { expect(doResolve).toHaveBeenCalledWith( resolver.hooks.resolve, { - request: '/root/external-a/src/module.ts', - path: '/root/repo/node_modules/a/src', + request: path.resolve(root, 'external-a/src/module.ts'), + path: path.resolve(root, 'repo/node_modules/a/src'), context: { - issuer: '/root/repo/node_modules/a/src/index.ts', + issuer: path.resolve(root, 'repo/node_modules/a/src/index.ts'), }, }, - 'resolve /root/external-a/src/module.ts in /root/repo/node_modules/a', + `resolve ${path.resolve( + root, + 'external-a/src/module.ts', + )} in ${path.resolve(root, 'repo/node_modules/a')}`, 'some-context', callbackA, ); @@ -110,8 +120,8 @@ describe('LinkedPackageResolvePlugin', () => { const callbackB = jest.fn(); tap( { - request: '/root/external-b/src/module.ts', - path: '/root/external-b/src', + request: path.resolve(root, 'external-b/src/module.ts'), + path: path.resolve(root, 'external-b/src'), context: { issuer: false, }, @@ -124,13 +134,16 @@ describe('LinkedPackageResolvePlugin', () => { expect(doResolve).toHaveBeenLastCalledWith( resolver.hooks.resolve, { - request: '/root/external-b/src/module.ts', - path: '/root/repo/node_modules/@s/b/src', + request: path.resolve(root, 'external-b/src/module.ts'), + path: path.resolve(root, 'repo/node_modules/@s/b/src'), context: { issuer: false, }, }, - 'resolve /root/external-b/src/module.ts in /root/repo/node_modules/@s/b', + `resolve ${path.resolve( + root, + 'external-b/src/module.ts', + )} in ${path.resolve(root, 'repo/node_modules/@s/b')}`, 'some-context', callbackB, ); diff --git a/packages/techdocs-common/src/stages/publish/helpers.test.ts b/packages/techdocs-common/src/stages/publish/helpers.test.ts index 3b01f5ac43..1fae66f3d2 100644 --- a/packages/techdocs-common/src/stages/publish/helpers.test.ts +++ b/packages/techdocs-common/src/stages/publish/helpers.test.ts @@ -14,6 +14,8 @@ * limitations under the License. */ import mockFs from 'mock-fs'; +import * as os from 'os'; +import * as path from 'path'; import { getFileTreeRecursively, getHeadersForFileExtension } from './helpers'; describe('getHeadersForFileExtension', () => { @@ -39,9 +41,11 @@ describe('getHeadersForFileExtension', () => { }); describe('getFileTreeRecursively', () => { + const root = os.platform() === 'win32' ? 'C:\\rootDir' : '/rootDir'; + beforeEach(() => { mockFs({ - '/rootDir': { + [root]: { file1: '', subDirA: { file2: '', @@ -57,9 +61,9 @@ describe('getFileTreeRecursively', () => { }); it('returns complete file tree of a path', async () => { - const fileList = await getFileTreeRecursively('/rootDir'); + const fileList = await getFileTreeRecursively(root); expect(fileList.length).toBe(2); - expect(fileList).toContain('/rootDir/file1'); - expect(fileList).toContain('/rootDir/subDirA/file2'); + expect(fileList).toContain(path.resolve(root, 'file1')); + expect(fileList).toContain(path.resolve(root, 'subDirA/file2')); }); });